WebAssembly promises increased flexibility, efficiency, and speed – and a path to the dream of “write once, run anywhere.” The ecosystem has matured in leaps and bounds over the last couple of years, and teams are seeing real benefits in production – take Adobe’s use of WebAssembly on Kubernetes, which delivers more scheduling flexibility via finer-grained, lighter-weight workloads.
Still, in the CNCF’s 2022 WebAssembly microsurvey, over 45% of respondents cited a lack of tooling as their greatest barrier to using WebAssembly (aka Wasm). The landscape is undoubtedly a work-in-progress, and while there are many cases where excellent tooling exists (or has emerged since that survey), teams don’t always have a good picture of their options.
I’ll be talking about shrinking Kubernetes workloads in a Lightning Talk at KubeCon NA 2023, with approaches ranging from the tried-and-true to the emerging. But here I want to zoom in on WebAssembly. If we’re going to deploy Wasm workloads on Kubernetes, developers need a quick and easy way to spin up a Wasm-on-Kubernetes environment for learning, development, and testing.
In this tutorial, we’ll walk through the setup of such an environment from beginning to end, creating a single-node local development cluster to run WebAssembly workloads. After examining the architecture, we’ll take our new environment for a spin.
Open source tooling for Wasm on Kubernetes
In this walkthrough, we’ll be focusing on three open source tools:
- k0s
- k0s plugin for wasmtime
- Spin
The open source, CNCF-certified k0s distribution provides a lightweight Kubernetes cluster well-suited to serve as a single-node development cluster. In addition to being easy to install, version 1.27 and later can use a “wasm-enabler” plugin to easily and dynamically configure containerd to integrate with the wasmtime WebAssembly runtime. This reduced friction can help developers explore the possibilities of Wasm on Kubernetes.
Install k0s
k0s is quick to install. Check the docs for specific system requirements, but installation should be straightforward in an up-to-date Ubuntu Server environment (the sort you can spin up quickly with multipass or distrobox). Just make sure you’ve got at least 2 vCPUs, 4 GB memory, and a good amount of disk space-15 GB should be more than enough for our purposes here.
First, download the k0s installer script:
$ curl -sSLf https://get.k0s.sh | sudo sh
Once the k0s binary is installed in /usr/bin/k0s, you will be able to run k0s to install k0s as a system service, with the cluster configured in a single-node implementation.
$ sudo k0s install controller –single
Start the service with:
$ sudo k0s start
After a moment, k0s will be ready to use. The service begins automatically after restarting the machine.
Apply the wasm-enabler plugin
To enable k0s’ wasm-enabler plugin, use this manifest. (Find the most up-to-date version on the plugin’s GitHub page.)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: wasm-enabler
namespace: kube-system
spec:
selector:
matchLabels:
k0s-app: wasm-enabler
template:
metadata:
labels:
k0s-app: wasm-enabler
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
– matchExpressions:
– key: plugin.k0sproject.io/wasm-enabled
operator: DoesNotExist
initContainers:
– name: wasm-enabler
image: quay.io/k0sproject/k0s-wasm-plugin:main
env:
– name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
securityContext:
privileged: true
volumeMounts:
– name: bin
mountPath: /var/lib/k0s/bin
– name: imports
mountPath: /etc/k0s/containerd.d/
containers:
– name: dummy
image: registry.k8s.io/pause:3.6
volumes:
– name: bin
hostPath:
path: /var/lib/k0s/bin
type: Directory
– name: imports
hostPath:
path: /etc/k0s/containerd.d/
type: Directory
—
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: wasmtime-spin
handler: spin
You can name this manifest wasm-enabler.yaml (or whatever you wish) and deploy it with kubectl apply. Note that if you’re using the kubectl bundled with k0s, you’ll run it as follows:
$ sudo k0s kubectl apply -f wasm-enabler.yaml
Check on the wasm-enabler init pods by getting the status of pods in the kube-system namespace:
$ sudo k0s kubectl get pods -n kube-system
If you see wasm-enabler init pods getting hung up, run the following to label our host node as suitable for running Wasm workloads:
$ kubectl label nodes “YOUR_NODE_NAME_HERE” plugin.k0sproject.io/wasm-enabled=true –overwrite
You can test the Wasm integration with a sample workload. To do so, create a manifest file (you can call it wasm-test.yaml) with the contents below:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wasm-spin
spec:
replicas: 1
selector:
matchLabels:
app: wasm-spin
template:
metadata:
labels:
app: wasm-spin
spec:
runtimeClassName: wasmtime-spin
containers:
– name: spin-hello
image: ghcr.io/deislabs/containerd-wasm-shims/examples/spin-rust-hello:v0.5.1
command: [“/”]
resources: # limit the resources to 128Mi of memory and 100m of CPU
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
—
apiVersion: v1
kind: Service
metadata:
name: wasm-spin
spec:
type: NodePort
ports:
– protocol: TCP
port: 80
targetPort: 80
selector:
app: wasm-spin
Note the RuntimeClassName in the Deployment spec. This tells the cluster which runtime to use for this workload-in this case, wasmtime-spin.
Deploy with kubectl apply:
$ sudo k0s kubectl apply -f wasm-test.yaml
Check your services to find the port for the wasm-spin sample service:
$ sudo k0s kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9m57s
wasm-spin NodePort 10.109.224.190 <none> 80:30817/TCP 5m6s
In my case, the port is 30817. Now I can run a curl request against the NodePort exposed service:
$ curl localhost:30817/hello
Hello world from Spin!
Create a Wasm workload and deploy to Kubernetes
You’ve seen references to Spin a couple times now. This open source framework from Fermyon will help us create useful HTTP applications with WebAssembly-apps that can run both locally and on our Kubernetes cluster.
Installing Spin is usually a quick matter of running the install script-follow the instructions for your OS to get started.
Now we’re ready to create a simple WebAssembly workload and deploy it to Kubernetes. In this case, we’ll use the included base template for an HTTP handler in Go. (We’re using TinyGo, but this exercise won’t require TinyGo installed on our system.)
Begin a new Spin project with spin new and choose the http-go template. Choose a name for your app and set the HTTP base and HTTP path as below, simply pressing enter to accept the default values.
Pick a template to start your application with: http-go (HTTP request handler using (Tiny)Go)
Enter a name for your new application: gogo-wasm
Description: Go Wasm HTTP handler
HTTP base: /
HTTP path: /…
Spin will create a new templated project with the standard contents for a Go project-main.go, go.mod, and go.sum-along with a spin.toml file that contains Spin metadata.
The main.js file will look like this:
package main
import (
“fmt”
“net/http”
spinhttp “github.com/fermyon/spin/sdk/go/http”
)
func init() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “text/plain”)
fmt.Fprintln(w, “Go go Wasm!”)
})
}
func main() {}
Feel free to change the response body to something like “Go go Wasm!” as I’ve done here.
We’ve created our simple HTTP application in Go. Before we proceed, make sure your spin.toml file looks exactly as below (except for the name and author):
spin_version = “1”
authors = [“Eric <[email protected]>”]
description = “Go Wasm HTTP handler”
name = “gogo-wasm”
trigger = { type = “http”, base = “/” }
version = “0.1.0”
[[component]]
id = “gogo-wasm”
source = “main.wasm”
environment = { VERSION = “v0.1.0” }
[component.trigger]
route = “/…”
[component.build]
command = “tinygo build -wasm-abi=generic -target=wasi -gc=leaking -no-debug -o main.wasm main.go”
Now it’s time to build to Wasm and deploy to Kubernetes.
Build and publish
We’ll deploy our application via a minimal scratch container. (Very minimal-the compressed file size for our image will be around 84 KB.)
For our build, we need to make sure we’re using buildx. Working locally, the easiest way is to use Docker Desktop. Make sure it’s up-to-date and configured to enable Wasm images in the Beta settings menu.
In your project directory, create a Dockerfile that looks like this:
FROM –platform=${BUILDPLATFORM} tinygo/tinygo:0.25.0 AS build
WORKDIR /opt/build
COPY . .
RUN tinygo build -wasm-abi=generic -target=wasi -gc=leaking -no-debug -o main.wasm main.go
FROM scratch
COPY –from=build /opt/build/main.wasm .
COPY –from=build /opt/build/spin.toml .
When we build with this Dockerfile, we’ll first build the wasm in a TinyGo-based build container, and then copy the main.wasm output and the spin.toml file to a new scratch container.
To build, run this command from your project directory, using your own namespace and project name:
$ docker buildx build –provenance=false –platform=wasi/wasm -t ericgregory/gogo-wasm .
In this case, I’ll publish to Docker Hub:
% docker tag ericgregory/gogo-wasm ericgregory/gogo-wasm:0.1.1
% docker push ericgregory/gogo-wasm:0.1.1
Deploy to k0s development cluster
Now we can deploy our Wasm workload to our k0s cluster. We’ll use a manifest very similar to the one we used for a sample deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gogo-wasm
spec:
replicas: 1
selector:
matchLabels:
app: gogo-wasm
template:
metadata:
labels:
app: gogo-wasm
spec:
runtimeClassName: wasmtime-spin
containers:
– name: gogo-wasm
image: ericgregory/gogo-wasm:0.1.1
imagePullPolicy: Always
command: [“/”]
—
apiVersion: v1
kind: Service
metadata:
name: gogo-wasm
spec:
type: ClusterIP
ports:
– protocol: TCP
port: 80
targetPort: 80
selector:
app: gogo-wasm
Since we’re deploying a ClusterIP service this time, we’ll run a port-forward to access it locally:
$ kubectl port-forward svc/go-spin 80:80
In another terminal tab:
$ curl localhost:80/
Go go Wasm!
And that’s it. With these tools, you can start writing meaningful WebAssembly applications that run on Kubernetes using open source tooling – and take advantage of WebAssembly’s portability and efficiency. If you’d like to refer to the code in this guide, you can find it on GitHub. I hope you’ve found this useful!
++
Join us at KubeCon + CloudNativeCon North America this November 6 – 9 in Chicago for more on Kubernetes and the cloud native ecosystem.
##
ABOUT THE AUTHOR
Eric Gregory, senior technical writer, Mirantis
Eric Gregory is a senior technical writer at Mirantis who likes to share experiences with Kubernetes, containers, open source, and DevOps. His past experience includes teaching, as well as writing and curating content.





