DEV Community

David McKay
David McKay

Posted on • Edited on • Originally published at rawkode.com

Introduction to WebAssembly on Kubernetes with Krustlet

Welcome! 👋

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

Why?

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

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

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
Enter fullscreen mode Exit fullscreen mode

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 https://github.com/deislabs/krustlet
cd krustlet

# Using Rust's build tool, Cargo, to build the application
cargo build
Enter fullscreen mode Exit fullscreen mode

Bootstrapping

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.

./docs/howto/assets/bootstrap.sh
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

NAME       STATUS   ROLES    AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE               KERNEL-VERSION   CONTAINER-RUNTIME
arch       Ready    agent    3m15s   0.3.0     192.168.39.37   <none>        <unknown>              <unknown>        mvp
minikube   Ready    master   57m     v1.18.3   192.168.39.37   <none>        Buildroot 2019.02.10   4.19.107         docker://19.3.8
Enter fullscreen mode Exit fullscreen mode

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 😁

TL;DR

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 https://gitlab.com/rawkode/wasi-hello-world
Enter fullscreen mode Exit fullscreen mode

DIY

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

cargo new --bin wasi-hello-world
Enter fullscreen mode Exit fullscreen mode

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

// Code from
// https://github.com/deislabs/krustlet/blob/master/docs/intro/tutorial01.md
use std::thread::sleep;
use std::time::Duration;

fn main() {
    loop {
        println!("Hello, from WASI and Krustlet!");
        sleep(Duration::from_secs(5));
    }
}
Enter fullscreen mode Exit fullscreen mode

Building

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
Enter fullscreen mode Exit fullscreen mode

Running & Publishing

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

❯ ls target/wasm32-wasi/debug
... wasi-hello-world.wasm ...
Enter fullscreen mode Exit fullscreen mode

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!
^C
Enter fullscreen mode Exit fullscreen mode

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 https://github.com/engineerd/wasm-to-oci
cd wasm-to-oci
make
sudo cp ./bin/wasm-to-oci /usr/local/bin/wasm-to-oci
Enter fullscreen mode Exit fullscreen mode

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 registry.gitlab.com:rawkode/wasi-hello-world:latest

INFO[0008] Pushed: registry.gitlab.com/rawkode/wasi-hello-world:latest
INFO[0008] Size: 1808571
INFO[0008] Digest: sha256:a9617d29b859994cc4b6f5ea73f7d68096921f7225ed31e645f0dda7b27163ed
Enter fullscreen mode Exit fullscreen mode

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
metadata:
  name: wasi-hello-world
spec:
  containers:
    - name: wasi-hello-world
      image: registry.gitlab.com/rawkode/wasi-hello-world:latest
      imagePullPolicy: IfNotPresent
  tolerations:
    - key: "krustlet/arch"
      operator: "Equal"
      value: "wasm32-wasi"
      effect: "NoExecute"
Enter fullscreen mode Exit fullscreen mode

Apply this YAML file to your Kubernetes cluster.

kubectl apply -f pod.yaml
Enter fullscreen mode Exit fullscreen mode

💥 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
Enter fullscreen mode Exit fullscreen mode

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 🤘

Top comments (0)