containerd shims make it possible to seamlessly run WebAssembly apps on Kubernetes. This article explains the role of containerd shims and how they work.
Kubernetes and containerd primer
Kubernetes is a high-level application orchestrator that relies on lower-level tools such as containerd and runc to execute applications.
A typical workflow deploying an application to Kubernetes looks like this:
1. Kubernetes schedules work to cluster nodes
2. The kubelet on the node hands the work to containerd
3. containerd asks runc to create the container and start the app
This model defines three clear responsibilities. Kubernetes is responsible for scheduling work to appropriate nodes in the cluster. containerd runs on each node and is responsible for managing lifecycle events such as creating, starting, stopping, and deleting containers. runc is responsible for low-level tasks such as creating namespaces, cgroups, and other constructs that make up a container.
On the jargon front, containerd is a high-level runtime, whereas runc is a low-level runtime.
The architecture is shown in the diagram below.
The purpose of containerd shims
containerd shims are helper processes that sit between containerd and the low-level runtime. They can perform many important tasks, one of which is abstracting the detail of low-level runtimes.
This architecture means you can write a shim to integrate almost any low-level runtime with containerd.
The following diagram shows Kubernetes scheduling OCI containers and WebAssembly apps to a single cluster node running containerd.
On the left, containerd is using runc and a shim to manage two OCI containers. On the right, the same containerd instance is using WebAssembly runtimes and shims to manage two WebAssembly workloads.
This article explains how Kubernetes targets WebAssembly workloads to nodes with the required runtimes and shims.
WebAssembly containerd shims
WebAssembly containerd shims are binary executables that sit between containerd and a Wasm runtime. They have the following three requirements:
– They embed the code required to interface with containerd
– They embed the Wasm runtime code
– They must be named according to the containerd binary naming convention
The following list shows 4 popular WebAssembly containerd shims. Notice how the binary names include the name of the target runtime and version.
– Slight:
containerd-shim-slight-v1
– Spin:
containerd-shim-spin-v1
– WasmEdge:
containerd-shim-wasmedge-v1
– wasmtime:
containerd-shim-wasmtime-v1
The shim code required to interface with containerd is runwasi. It’s an official containerd project and is designed to be used as a Rust library. The following code snippet shows the spin shim importing runwasi.
use containerd_shim_wasm::sandbox::{
error::Error,
instance::{EngineGetter, InstanceConfig},
oci, Instance, ShimCli,
};
The following diagram shows the high-level architecture for WebAssembly containerd shims. Notice how the shim includes the runwasi code and the Wasm runtime code. runwasi handles the interface with containerd, whereas the Wasm runtime code executes the Wasm app.
WebAssembly containerd shims in practice
In the future, most Kubernetes distros will automate the process of installing Wasm shims. However, it’s useful to understand the configuration involved.
Installing a WebAssembly containerd shim is a two-step process:
1. Copy the shim binary to the node
2. Register the shim with containerd
The shim binary must be named appropriately and installed in a directory visible to containerd. This is usually the /bin directory but can be any PATH visible to containerd. A standard installation of the wasmtime shim would be:
/bin/containerd-shim-wasmtime-v1
[plugins.cri.containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins.cri.containerd.runtimes.wasmtime]
runtime_type = "io.containerd.wasmtime.v1"
[plugins.cri.containerd.runtimes.spin]
runtime_type = "io.containerd.spin.v1"
With the shim installed and registered, the node is ready to execute appropriate Wasm apps.
containerd spawns a shim process for each Wasm app it starts. Each shim process is a long-running daemon process that executes for as long as the Wasm app executes.
The shim executes the Wasm app by invoking the embedded Wasm runtime code which creates the Wasm host and executes the Wasm app.
The following snippet is from a Kubernetes node running three Wasm apps. Notice that there are three shim processes.
$ ps | grep wasm
PID USER COMMAND
2722 0 {containerd-shim}.../bin/containerd-shim-wasmtime-v1 ...
2962 0 {containerd-shim}.../bin/containerd-shim-wasmtime-v1 ...
3367 0 {containerd-shim}.../bin/containerd-shim-wasmtime-v1 ...
The shim process also interfaces with containerd which includes listening for instructions and reporting back on status and exit codes.
Summary
Kubernetes is a high-level application orchestrator that relies on lower-level tools to execute the apps.
containerd is the most widely-used high-level Kubernetes runtime. It implements a shim architecture that lets you mix-and-match low-level runtimes, including WebAssembly runtimes.
WebAssembly containerd shims embed the runwasi code that interfaces with containerd. They also embed the Wasm runtime code that creates the Wasm host and executes Wasm apps.
This architecture makes it possible to seamlessly execute WebAssembly apps on Kubernetes.
– This article explains everything you need to know about integrating WebAssembly with Kubernetes.
– This article walks you through the hands-on process of deploying a WebAssembly app to Kubernetes.
Massive thanks to Joe Zhou for fact-checking my early research. Any mistakes in this final article are mine.