Running Linux Applications as Unikernels with K8S by@eyberg

Running Linux Applications as Unikernels with K8S


If you've read some of my prior articles you might've thought I'd never write this one huh? :) Well here goes.

A common question that we get is "Can you use unikernels with K8S?" The answer is yes, however, there are caveats. Namely, unikernels come packaged as virtual machines and in many cases k8s is provisioned on the public cloud on top of virtual machines. Also, you should be aware that provisioning unikernels under k8s incurs security risks that you would otherwise not need to deal with. These are greatly diminished as the guests are unikernels, not linux guests, but still.

Now, if you have your own servers or you are running k8s on bare metal this is how you'd go about running Nanos unikernels under k8s.

For this article you need a real physical machine and OPS. While you can use nested virtualization I wouldn't because you are going to take a pretty significant performance hit. Google Cloud has this feature on some of their instances and if you are on Amazon you might be able to perform this example on the "metal" instances (I haven't checked), although, keep in mind both of these options will not be cheap compared to simply spinning up a t2 nano or micro instance of which you can do easily with unikernels.

We are going to run a Go unikernel for this example but you can use any OPS example to follow along. Here we have a simple go webserver that sits on port 8083:

package main

import (

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Welcome to my website!")

	fs := http.FileServer(http.Dir("static/"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	http.ListenAndServe(":8083", nil)

Ok - looks good. We can quickly build the image and ensure everything is working alright like so. We are using the 'nightly' build option here:

ops run -n -p 8083 goweb

Ops build works here as well but run will autorun it for you to ensure it works locally first. Now we'll need to put it into a format for k8s to use. First, we compress it with XZ (sudo apt-get install xz-utils):

cp .ops/images/goweb.img .
xz goweb.img

From there we need to put it into a place for k8s to import it. I tossed it into a cloud bucket and to keep this article as simple as possible have left it open. (Obviously, you don't want to do this in a real life production scenario.)

Now let's install kubectl:

  curl -LO`curl -s`/bin/linux/amd64/kubectl
  chmod +x ./kubectl
  mv kubectl /usr/local/bin/.
  sudo mv kubectl /usr/local/bin/.
  kubectl version --client

Now let's install minikube. I'm using minikube here to hopefully minimize the number of steps you need to do from a fresh install but feel free to use whatever you want.

  curl -Lo minikube && chmod +x minikube
  minikube start --vm-driver=kvm2

Then install the kvm2 driver. For this box I needed to install the libvirt suite of tooling:

sudo apt-get install libvirt-daemon-system libvirt-clients bridge-utils

Libvirt is this rather old and nasty library used to interact w/KVM although it is a ton of integrations and there aren't that many alternatives.

If you are having trouble after this step you can run this quick validation check to ensure everything is setup:


Also, ensure you are in the right group to interact with KVM:


After getting all of this installed you might find the need to reset your session (quickest way is to just logout/login again).

Next up - let's install the kubevirt operator. This is what really ties the room together.


  export KUBEVIRT_VERSION=$(curl -s | grep tag_name | grep -v -- - | sort -V | tail -1 | awk -F':' '{print $2}' | sed 's/,//' | xargs)

  kubectl create -f${KUBEVIRT_VERSION}/kubevirt-operator.yaml

Then let's create a resource:

  kubectl create -f${KUBEVIRT_VERSION}/kubevirt-cr.yaml

Now let's install virtctl. Are we getting tired yet?

  curl -L -o virtctl${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64
  chmod +x virtctl

Then we'll import with CDI.

  kubectl create -f storage-setup.yml
  export VERSION=$(curl -s | grep -o "v[0-9]\.[0-9]*\.[0-9]*")
  kubectl create -f$VERSION/cdi-operator.yaml
  kubectl create -f$VERSION/cdi-cr.yaml

  kubectl get pods -n cdi

Ok! Whooh! If you got through all of that we are almost to the finish line. Let's grab a template for our persistent volume claim:


Now, edit the line to show where you stuffed the original disk image. In my example it looks like this (again this is just an example to keep things easy - you wouldn't/shouldn't do this in real life): ""

Let's create it:

kubectl create -f pvc_fedora.yml
kubectl get pvc fedora -o yaml

You can check out the import as it happens but wait until you see the success message: Succeeded

Now we can create the actual vm:

  kubectl create -f vm1_pvc.yml

Now if you:

kubectl get vmi

You should see your instance running.

If you have minikube you can now do this:


Wow! We just deployed a unikernel to K8S. Easy? Well, I'll let you decide that.

Of course, if you are using the public cloud like AWS or GCP and you don't want to have to go through all the hassle these 2 commands will get the same webserver deployed just as easily with a lot less hassle, more security and more performance with less waste:

ops image create -c config.json -a goweb
ops instance create -z us-west2-a -i goweb-image

Until next time.


Signup or Login to Join the Discussion


Related Stories