The following post is a bit theoretical and is worth reading if you’re delving in the concepts of WebAssembly. The author explains why he built an unikernel (think of an application that is bundled with the kernel itself) for WebAssembly, what components were used, but does not show any code along the way about how to do it (although sharing a link to a GitHub repository where the project lives).
https://flavio.castelli.me/2023/02/07/building-a-unikernel-that-runs-webassembly---part-1
Hackweek 22 took place last week. During this week all the SUSE employees are free to hack on whatever they want. This one of the perks of working at SUSE 😎.
This time my personal project has been about building a unikernel that runs WebAssembly.
I wanted this blog post to contain all the details about this journey. However I realized this would have been too much for a single post. I hence decided to split everything into smaller chunks. I’ll update this section to keep track of all the posts.
In the meantime, you can find the code of the POC here.
Why
There are multiple reasons why I did that, but I don’t want to repeat what I wrote inside of the project description. Learning and fun goals aside, I think there’s actually a good reason to mix unikernels and WebAssembly.
From the application developer POV, porting/writing an application to the unikernel is not an easy task. The application and all its dependencies have to support the target unikernel. Some patching might be required inside of the whole application stack to make it work.
From the unikernel maintainers POV, they have to invest quite some energies to ensure any kind of application can run in a seamless way on top of their platform. They don’t know which kind of system primitives the user applications will leverage, this makes everything harder.
On the other hand, when targeting a WebAssembly platform (think of Spin or Spiderlightning), the application has a clear set of capabilities that have to be provided by the WebAssembly runtime.
If you look at the Spiderlightning scenario, an application might be requiring Key/Value store capabilities at runtime. However, how these capabilities are implemented on the host side is not relevant to the application. That means the same .wasm
module can be run by a runtime that implements the K/V store using Redis or using Azure Cosmos DB. That would be totally transparent to the end user application.
You might see where I’m going with all that…
If we write a unikernel application that runs WebAssembly modules and supports a set of Spiderlightning APIs, then the same Spiderlightning application could be run both on top of the regular slight
runtime and of this unikernel.
All of that without any additional work from the application developer. The Wasm module wouldn’t even realize that. The complexity would fall only on the unikernel developer who, whoever, would have a clear set of functionalities to implement (as opposed to “let’s try to make any kind of application work”).
How
Sometimes ago I stumbled over the RustyHermit project, this is a unikernel written in Rust. I decided to use it as the foundation to write my unikernel application.
Building a RustyHermit application is pretty straightforward. Their documentation, even though is a bit scattered, is good and their examples help a lot.
The cool thing is that RustyHermit is part of Rust nightly, which makes the whole developer experience great. It feels like writing a regular Rust application.
Obviously you cannot expect all kind of Rust crates to just work with RustyHermit. You will see how that influenced the development of the POC.
The next sections go over some of the major challenges I faced during the last week. I’ll share more details inside of the upcoming blog posts (see the disclaimer section at the top of the page).
The WebAssembly runtime
Unfortunately Wasmtime, my favorite WebAssembly runtime, does not build on top of RustyHermit. Many of its dependencies expect libc
or other low level libraries to be around. The same applies to wasmer.
I thought about using something like WebAssembly Micro Runtime (WAMR), but I preferred to stick with something written in Rust and have the “full RustyHermit experience”.
After some searching I found wasmi a pure Rust WebAssembly runtime. This works fine on top of RustyHermit, plus its design is inspired by the one of Wasmtime, which allowed me to reuse a lot of my previous knowledge.
WebAssembly Component Model
Spiderlightning leverages the WebAssembly Component Model proposal to offer capabilities to the WebAssembly guests and to allow the host to consume capabilities offered by the WebAssembly guest.
The communication between the host and the guest happens using types defined with the Wasm Interface Type.
To give some concrete examples, the demo I’m going to run leverages the WebAssembly Component Model in these ways:
- The guest asks the host to start a HTTP server. When doing that, the guest informs the host about the HTTP routes that have to be registered, plus the names of its internal handlers (the functions that have to be executed). This is done by using the
http-server
types. In this case it’s the guest that leverages capabilities offered by the host. - The host handles the incoming HTTP requests using the routing information provided by the guest. The http handlers mentioned before are functions exposes by the WebAssembly guest. The server is now consuming capabilities offered by the guest. The communication is done using the
http-handler
types. - Some of the http handlers defined by the guest are also interacting with a Key/Value store. Also in this case the guest is leveraging a set of capabilities offered by the host. These are defined using the
keyvalue
types.
As you can see there are many WIT types involved. For each one of them we need code both inside of the guest (a SDK basically) and on the host (the code that implements the guest SDK). This code can be scaffolded by a cli tool called wit-bindgen
, which generates host/guest code starting from a .wit
file.
In this case I only had to implement the host side of these interfaces inside of the unikernel.
The code generated by wit-bindgen
is doing low level operations using the WebAssembly runtime. The code to be scaffolded depends on the programming language and on the WebAssembly runtime used on the host side.
Obviously the wasmi
WebAssembly runtime was not supported by wit-bindgen
, hence I had to extend wit-bindgen
to handle it. The code can be found inside of this fork, under the wasmi
branch.
With all of that in place, I scaffolded the host side of the Key/Value capability and I made a simple implementation of the host traits. The host code was just emitting some debug information. I was then able run the vanilla keyvalue-demo from the Spiderlightning project. 🥳
Summary
You made to the bottom of this long post, kudos! I think you deserve a prize for that, so here we go…
This is a recording of the unikernel application running the Spiderlightning http-server demo.