Introduction to WebAssembly on Kubernetes with Krustlet

Published on

June 2, 2020

Reading time

9 minutes

Word Count

1542 words

Technologies Logo Krustlet Logo WebAssembly Logo Kubernetes

This article will aim to introduce you to something wild and wonderful - running WebAssembly binaries on Kubernetes, with Krustlet.


WebAssembly is bytecode that can be executed in your browser, regardless of operating system or architecture; this has opened up a, potentially, whole new era of web programming; one that doesn't need transpiled to JavaScript.

Thus far, a few languages have trialed and tested support for compiling their code to WASM for usage within the browser. You can learn about some of these efforts by checking out these links.

There are more, but I think that's enough to pique your interest.

ANY language, in the browser ... that's a pretty interesting idea, right? 😉

One thing we don't often talk about when discussing the browser ... it's pretty darn secure. Running JavaScript in your browser can't actually do much damage to your host system. This is because the JavaScript engines, such as v8 or SpiderMonkey, run the JavaScript code in a sandbox; much like modern Linux container systems, like containerd.

So what if we want to leverage WASM, in our many languages, and benefit from the sandbox out the browser ... but without the browser?

Enter, WASI and wasmtime .

WebAssembly System Interface (WASI)

WASI provides an interface for running WASM code as a process on your operating system, rather than browsers; which allows us to interact with all the things the browser doesn't: files and filesystems, Berkeley sockets, clocks, and random numbers.

WASI does this in a fashion that still gives us similiar security boundaries that we get within the browser, but through a capabilities model defined by the runtime. WASI is not a runtime, but the interface that runtimes must adhere too. This is handled by a capability-based security model.

How do capabilities work? Lets use an example. Most applications open a file by some reference, which is usually a file path on the filesystem. Any application that has that reference (file path) can interact with that file, provided the Ambient Authority allows.

What does that mean? It means that if user rawkode executes a binary, and rawkode has permissions to read a file; then that binary can read that file.

That's not ideal.

WASI uses a Capability model , rather than ambient authority. When you execute a WASI binary, it can ONLY access the files that you have given that binary the permission to.

What does that mean? Think about container images when executed, they can only access the files within the container image, or explictly bind mounted in. WASI is the same. WASI binaries can only access files if the binary is given permission to access them, usually by providing a directory root.


wasmtime is an implemention of WASI. You can think of wasmtime much like node . In the same way that node allowed us to execute JavaScript outside of the browser, wasmtime does that for WASM.


Krustlet is a Kubelet implementation for Kubernetes that supports running WASM bytecode, with the wasmtime runtime, instead of container images with a container runtime.

Getting Started

Getting a Kubernetes cluster

For today's tutorial, we're going to use minikube . minikube gives us a full Kubernetes cluster, in a VM, on our local machine. With minikube installed, create a cluster with the following command.

				minikube start

Building Krustlet

As this is rather experimental / early stages, you'll need to compile the Krustlet from source. Depending on your machine, this can take anywhere from 3 minutes to 10 minutes; so you make want to go make a coffee or something ⏰

				# Clone the Krustlet source code to a local directory and enter it
git clone
cd krustlet

# Using Rust's build tool, Cargo, to build the application
cargo build



Now that we have the binaries needed to run our Krustlet, we need to perform some initial bootstrapping.

Bootstrapping is important, because Kubernetes restricts access to the control plane. We need to provide enough configuration for our Krustlet to register itself with the control plane, so it can become a "Node" within the cluster.

You can create this bootstrap configuration with the following command. PLEASE ensure your KUBECONFIG context is pointing to your development cluster, and not something in production.


You'll see some output, like so:

				secret/bootstrap-token-x4jygy created
Switched to context "minikube".
Context "minikube" renamed to "tls-bootstrap-token-user@kubernetes".
User "tls-bootstrap-token-user" set.
Context "tls-bootstrap-token-user@kubernetes" modified.
Context "tls-bootstrap-token-user@kubernetes" modified.

What does this all mean? 🤓

First, this bootstrap script creates a secret inside of our cluster; using our default KUBECONFIG . This will be setup for you when you spin up minikube .

This secret is very similiar to a Kubelets bootstrap secret. It's all very 😴

				auth-extra-groups: # system:bootstrappers:kubeadm:default-node-token
expiration: MjAyMC0wNy0wMlQxMzozOTo0OVo= # 2020-07-02T13:39:49Z (1 hour from bootstrap)
token-id: eDRqeWd5 # x4jygy
token-secret: OWR1enFtamdvM3BlaXQ3YQ== # 9duzqmjgo3peit7a
usage-bootstrap-authentication: dHJ1ZQ== # true
usage-bootstrap-signing: dHJ1ZQ== # true

Next, the bootstrap script grabs your existing KUBECONFIG , copies it, crates a new user within your cluster, modifies the context to use that new user, and stores it at ~/.krustlet/config/bootstrap.conf . This bootstrap.conf can now be used to provide authentication for our Krustlet to the control plane.

Running the Krustlet

				KUBECONFIG=~/.krustlet/config/bootstrap.conf krustlet-wasi --node-ip $(minikube ip) --port 3000 --bootstrap-file ~/.krustlet/config/bootstrap.conf

The first time this runs, it will generate the required TLS certificates. You'll need to approve the CSR in another terminal tab / window.

				kubectl certificate approve $(uname -n)-tls

Lets confirm everything looks good, we should see 2 "nodes" now.

arch Ready agent 3m15s 0.3.0 <none> <unknown> <unknown> mvp
minikube Ready master 57m v1.18.3 <none> Buildroot 2019.02.10 4.19.107 docker://19.3.84

Congratulations, you've got a Krustlet running on minikube

Building a WASI Application

Next, we're going to throw together a quick "Hello World" style application. Unfortunately, WASI hasn't defined what networking / Berkeley sockets will look like yet within WASI-land; so we can't run a web server or anything more interesting (yet).

All in good time though 😁


If you don't want to follow the DIY steps below, you can clone my repository and jump straight to the Building section.

				git clone


First, create a Rust project. This command generates a skeleton Rust project.

				cargo new --bin wasi-hello-world

You'll want to update with the following code. This code prints a message, and sleeps for 5 seconds; it then loops this process indefinitely.

				// Code from
use std::thread::sleep;
use std::time::Duration;

fn main() {
    println!("Hello, from WASI and Krustlet!");


We need to enable the WASI target, which we'll do with rustup . We also need to build our application, targetting WASI as a runtime.

				# Enable wasm32-wasi as a compilation targeht
rustup target add wasm32-wasi

# Build WASI bytecode
cargo build --target wasm32-wasi

Running & Publishing

When we build a WASI compatible binary, what we get is a wasm file.

				ls target/wasm32-wasi/debug

In order to run WASI bytecode, we use a WASI runtime; like wasmtime .

				wasmtime ./target/wasm32-wasi/debug/wasi-hello-world.wasm
Hello, from WASI and Krustlet!

Oooh, shiny! 🌠

However, that's not what we want to do. We want to run this on Kubernetes using our Krustlet.

In-order to do so, we need to "publish" our image to "somewhere".

Fortunately, there's wasm-to-oci which allows publishing wasm binaries to OCI compatible registries.

Note: Docker Hub and Quay do not support these artifacts at the time of writing, but GitLab, Google Cloud, and Azure Cloud do.

Sadly, we do need to build this ourselves too; fortunately, it's I've prepared the commands 😃

				git clone
cd wasm-to-oci

sudo cp ./bin/wasm-to-oci /usr/local/bin/wasm-to-oci

Now, lets go back to our wasi-hello-world directory and push an image with our WASI binary.

				# You can use this image if you want, it's public
wasm-to-oci push ./target/wasm32-wasi/debug/wasi-hello-world.wasm

INFO[0008] Pushed:
INFO[0008] Size: 1808571
INFO[0008] Digest: sha256:a9617d29b859994cc4b6f5ea73f7d68096921f7225ed31e645f0dda7b27163ed

The Last Kilometer

OK. We're almost there. Thus far, we've:

  • ☑ Created a Kubernetes cluster with minikube
  • ☑ Cloned, compiled, and ran a Krustlet from source
  • ☑ Created a Rust application and compiled it to WASI
  • ☑ Converted our WASI binary to an OCI compatible image and pushed it to the GitLab Container Registry
  • ❌ Run our Rust WASI targetting binary on Kubernetes

OK, so now we need to schedule our WASI workload on the Krustlet. Like ALL Kubernetes resources, we need YAML for this.

Save the following to pod.yaml .

				apiVersion: v1
kind: Pod
  name: wasi-hello-world
    - name: wasi-hello-world
      imagePullPolicy: IfNotPresent
    - key: "krustlet/arch"
      operator: "Equal"
      value: "wasm32-wasi"
      effect: "NoExecute"


Apply this YAML file to your Kubernetes cluster.

				kubectl apply -f pod.yaml

💥 DONE 💥

We've now deployed a WASI binary, via Krustlet, to our Kubernetes cluster.

Confirm the Awesome

To confirm your WASI binary is running, you can use the Kubernetes tooling to check a few things.

First, is the pod scheduled and running and do we have log output? Run the following commands.

				kubectl get pods
kubectl logs -f wasi-hello-world

Awesome! I hope this helps you on your path to understanding, getting excited, and adopting WASM, WASI, and Krustlet.

If you have any questions, grab me on Twitter .

Until next time 🤘