Release 13ARM support, Karpenter, a JS SDK, Kernel-less images, and more

Read the release notes

Millisecond, microVM Kubernetes

How we brought microVM-level isolation and speed to Kubernetes: the architectural journey behind Kraftlet, the ideas we explored, the dead ends we hit, and how you can run it on your existing cluster.

Petar Cvitanović
Petar Cvitanović
Software Engineer

Running applications isn’t just about getting them to work. It’s how fast they start, how well they’re isolated, and how much of your infrastructure bill you can actually justify.

At Unikraft, we are all-in on microVMs: lightweight, strongly isolated virtual machines that you can snapshot, boot in milliseconds, and scale to zero at any point.

We built our own runtime around that idea, and it works great. Then both we and our clients started asking:

Can I use it in my Kubernetes cluster?

No surprises there, since Kubernetes is the leading orchestrator on the market. We felt a strong demand for pairing our runtime and the benefits of microVMs with the declarative model of Kubernetes and its ecosystem.

In this blog post, we walk through the architectural journey of our Kubernetes integration: the ideas we explored, the dead ends we hit, and how you can bring microVM-level isolation and speed to your existing cluster.

Architecture decisions

We had a couple of directions to consider when working out how our Kubernetes integration should look. This, of course, comes from the fact that Kubernetes itself solves the whole app lifecycle, and we wanted to make sure we sat in just the right layer to make our integration as seamless as possible.

Before diving into different architectures, we set a list of requirements for the Kubernetes integration we wanted:

  • retention of the declarative Kubernetes API model
  • scheduling capabilities
  • support for Unikraft app density and scale
  • storage and networking support

Important for the Kubernetes integration was the current architecture of Unikraft. The Unikraft platform can be installed on any machine, and it comes with a runtime and an API for managing microVMs on that machine.

With that context in mind, let’s dive into each of our ideas, chronologically, the way we did it, and I will explain why we ditched each of the ideas until we came to the architecture we have today.

Operator

Of course, the first and most obvious architecture that comes to mind when talking about custom logic in Kubernetes would be an operator.

The operator makes a great integration for a one-to-one mapping between Kubernetes configuration and Unikraft resources. For each Instance / Service / Volume, the operator would provide a CRD with all the configuration knobs for your application.

Pretty simple so far, and it seems like a good fit… until you check the requirements we listed above. If only it were that simple 🙃

A Unikraft operator would mean a new set of CRDs, and then integrating with other tools and operators becomes a problem. If you pick up Strimzi or CNPG, which reconcile their own CRs and create Pods and other native resources, you can’t run them on top of Unikraft because our operator introduced a new set of CRDs.

Another issue with the operator was scheduling. We could register multiple Unikraft boxes to be managed by a single operator, but then all of a sudden the operator is implementing scheduling. Kinda out of place, especially when talking about a Kubernetes cluster that has its own scheduling implemented.

We had to look further than the operator. That said, the operator still has its place: if you want a simple way to manage Unikraft resources without worrying about scheduling, it’s still available and maintained on GitHub. But we knew we could do better… and we did.

A clean one-to-one mapping. But nothing else can reach it.

Problem 1: the ecosystem creates Pods, the operator only takes CRs Ecosystem: everything reconciles into Pods Unikraft operator: everything is a CR Deployment Pod Strimzi Pod CloudNativePG Pod what every Kubernetes tool produces no Pod gets in the only way in Instance CR Service CR Volume CR own API surface, own tooling Problem 2: with several boxes, the operator must pick one Unikraft operator which box? box 1 box 2 box 3 That is scheduling, and the cluster already has a scheduler. The operator would redo it.
Strimzi and CloudNativePG create Pods. A Unikraft operator only understands its own CRs, so their Pods can never run on Unikraft. And once one operator manages several boxes, it has to decide which box gets what. That is scheduling, and the cluster already does it.

CRI implementation

All of the points we disliked about the Unikraft operator stemmed from the fact that the operator reconciled custom resources instead of something native to Kubernetes — Pods!

This was an important step in the design, since it switched how we view Unikraft in the context of Kubernetes. Instead of thinking of it as an API for microVMs, we began to view it as a runtime.

We started looking into how we could implement a Unikraft CRI, or a containerd-compatible runtime. This felt like a much more mature and compatible way of integrating Unikraft into Kubernetes. It allowed us to integrate with other tools and operators because Unikraft instances are now configured via the Pod object.

The CRI seemed like a great fit until we started thinking about all the fun scaling problems it brings.

The Unikraft platform can run one million microVMs on a single machine (thanks to our platform team). What this means in the Kubernetes context is that the Kubelet calling our CRI would need to keep the state of a million Pods.

Even if we talk about tens of thousands of VMs on a single machine, this is still unprecedented for the Kubelet. The current documented limit for a Kubernetes node is 110 Pods, which is far below the density achievable with Unikraft.

We needed a different, more flexible solution to get around the bottlenecks. This led us to Kraftlet.

Correct on paper. But the kubelet must track every Pod.

Stock kubelet + CRI

110 / 110

Pods tracked

node limit reached
max 110 Pods per node

One Unikraft box

1,000,000

one square = 1000 microVMs

A CRI puts Unikraft under the kubelet, and the kubelet then has to track every microVM as a Pod. Its documented ceiling is 110 per node. The box underneath it runs a million.

Kraftlet

The Kubernetes Kubelet is the primary agent that runs on each node in the cluster, managing the lifecycle of Pods on that node. It’s what watches for new Pods scheduled to its node and makes sure they are running.

We knew that integrating into the Pod lifecycle was the correct direction to go in… but on what level?

Implementing the CRI would be technically correct, but it wouldn’t scale, at least not to match the capabilities of Unikraft. The regular Kubelet was obviously the bottleneck.

This is why we built Kraftlet (named after Unikraft + kubelet). Built on top of Virtual Kubelet, Kraftlet is our own implementation of Pod reconciliation, which allows us to manage the whole lifecycle of a Pod and control how other interfaces like CSI and CNI are invoked.

The API server sees a node. The node is Kraftlet.

Kubernetes cluster EKS · GKE · AKS · on-prem Control plane kube-apiserver scheduler · etcd controllers Pods Nodes worker-node-1 kubelet container runtime Kraftlet Virtual Kubelet · registers as a Node Unikraft platform one box · remote microVM API one Pod = one microVM Service Volume
To the API server, Kraftlet is one more node. It takes the Pods scheduled to it and runs each one as a microVM on a remote Unikraft box, while the stock kubelet keeps running containers next to it.

Another important thing is that with Kraftlet, we could decouple the Pod lifecycle from the actual runtime on the machine. Because the Unikraft platform exposes an API for managing microVMs, we don’t have to collocate Kraftlet on the same machine where the microVMs are running.

This means we can now horizontally scale Kraftlets that all manage the same box. With this, we can achieve a much higher density of Pods for the available infra.

Many Kraftlets. Still one box.

Kubernetes cluster EKS · GKE · AKS · on-prem Control plane kube-apiserver scheduler · etcd controllers Pods Nodes worker-node-1 kubelet container runtime kraftlet-0 kraftlet-1 kraftlet-n Unikraft platform one box · remote microVM API one Pod = one microVM Service Volume
Kraftlet is decoupled from the runtime, so several Kraftlets can register as nodes and manage the same Unikraft box in parallel. Pod density scales with Kraftlets, not with machines.

Storage (CSI)

Our custom Kraftlet implementation also allows us to integrate with CSI. Yes, Kraftlet is not collocated with the machine running VMs, but we configured CSI to be exposed remotely, and Kraftlet can invoke CSI to Stage and Publish a volume, and then mount it into a microVM. This means you get the performance and isolation of a microVM with the storage solution of your choice.

Any CSI driver. Mounted into a microVM.

Kubernetes cluster EKS · GKE · AKS · on-prem Control plane kube-apiserver scheduler · etcd controllers CSI Controller provisions volumes Kraftlet Virtual Kubelet · remote CSI client External APIs e.g. AWS EC2 Unikraft Cloud remote microVM API CSI Node e.g. AWS EBS Managed Volume Instance microVM 1 2 3 4 5 6
  1. 1 Watch PVCs : The CSI Controller reconciles PersistentVolumeClaims.
  2. 2 Call external APIs : The controller provisions the disk, e.g. via the AWS EC2 API.
  3. 3 Watch Pods : Kraftlet takes the Pods scheduled to its node.
  4. 4 NodeStageVolume : The remote CSI Node formats and mounts the volume to the staging path.
  5. 5 Create managed volume : Kraftlet turns the staged disk into a Unikraft managed volume.
  6. 6 Create Instance : The microVM boots with the volume attached.
The CSI flow: Kraftlet watches for PVCs, drives the CSI controller and node plugins (e.g. AWS EBS), stages and mounts the managed volume, then attaches it to the microVM instance.

Networking (CNI)

Similarly to CSI, we wanted Kraftlet to support the CNI that the Kubelet does. And CNI works a bit differently. Since there was no service we could invoke from Kraftlet, we built a thin CNI gRPC service that invokes libcni on a remote machine.

Once the CNI configures the interface, Kraftlet just passes it down to the Unikraft platform, and your microVM is now integrated into your network.

This way, Kraftlet runs your Pods as microVMs, with an app density you can’t get from stock Kubernetes, compatible with CSI and CNI.

Compatibility and scale

Kraftlet can work with any Kubernetes distro out there. Whether it’s a managed offering like EKS and GKE or on-prem clusters, Kraftlet registers itself as a node and listens for Pods being scheduled to it. As long as you have a Kubernetes API server, it just works.

As for density, we showcased Kraftlet at KubeCon Europe ‘26 in Amsterdam to demonstrate the scale we can get with Unikraft.

Our demo was a 100k-Pod cluster. In stock Kubernetes, that would be a huge cluster with a couple of hundred nodes. The infrastructure backing it was a single Hetzner box running Unikraft and 20 Kraftlets. That’s ~5k Pods per Kraftlet.

  • 100k — Pods in a single cluster
  • 1 box — Hetzner server + 20 Kraftlets
  • ~10 ms — Boot time, ready to respond

All 100k Pods are ready to respond and boot up in ~10 ms, at a fraction of the cost.

Try it yourself

Installing and trying out Kraftlet on your own cluster is as easy as installing a Helm chart. We’ve documented the steps in our Kubernetes documentation.

Redefining the Kubernetes node

Our Kubernetes story started as a simple integration to fit Unikraft into Kubernetes. It turned into rethinking what a Kubernetes node is, what it’s made of, and along the way we pushed the limits of application density.

By owning the Pod lifecycle through Kraftlet, we kept what developers love about Kubernetes and added microVM-level isolation and performance on top. Your existing tooling, your existing cluster, your existing workflows — just faster and more secure.

If you’re battling node density, running workloads with spiky traffic, or deploying AI agents and want to stay on Kubernetes — check out Kraftlet and Unikraft.

Run microVM Pods on the Kubernetes you already have. 10 ms boots, hardware isolation, and 100k Pods on a single box — one Helm chart away.


Read next: The Mighty (micro)Virtual Machine — Why, for multi-tenant workloads, the VM is — and only ever was — the one isolation primitive that actually isolates.