This is a POC of a Unikernel designed to run WebAssembly code.
The project has been done as part of SUSE Hackweek 2023. The code has been written over a week as a learning experiment, hence some shortcuts have been taken 😅
The code has some limitations, that are described below.
For more details, checkout this series of blog posts.
The goal of this Hackweek project has been to learn about RustyHermit and figure out how hard it would be to create a Unikernel capable of running WebAssembly.
WASI support has not been a goal of this project. I instead targeted a portion of the SpiderLightning project APIs.
I wanted to be able to show the same WebAssembly module being run both
by the vanilla slight
runtime and by this Unikernel.
- Unikernel: RustyHermit
- WebAssembly runtime: wasmi - this runtime has
been used because it's written in pure Rust and can be built into the unikernel.
Other WebAssembly runtimes are currently assuming the availability of
libc
, hence they cannot be built as a RustyHermit application. - WIT definitions
are taken from the SpiderLightning project.
The WASMI bindings are generated using this fork
of
wit-bindgen
that adds WASMI support.
Note: Currently RustyHermit supports only the x86_64 platform.
The unikernel will run the SpiderLightning http-server-demo
example.
This code makes use of two SpiderLightning interfaces:
The WebAssembly module will start a HTTP server listening on 0.0.0.0:3000
of
the unikernel.
The web server exposes the following routes:
GET
/hello
: this prints back a messageGET
/foo
: this returns the value of themy-container:key
key inside of the K/V storePUT
/bar
: this sets the value of themy-container:key
key inside of the K/V store
Note: the code runs a polished version of the example based on this PR.
The WebAssembly module can be found under the /wasm
directory. The code has
then been compiled targeting the wasm32-unknown-unknown
Rust target.
The POC suffers from the following limitations.
I didn't find an easy way to get the .wasm
file into the running VM.
Right now the code is embedded at compile time into the unikernel by using the
include_bytes
Rust macro.
TLS support via openssl is of course not doable from within the unikernel.
Unfortunately rustls depends on the
ring
crate, which does not compile when
targeting RustyHermit.
Because of that, it's not possible to connect to a TLS terminated Redis instances. Moreover, the http server ran by the unikernel is not doing TLS termination.
The scheduler of RustyHermit seems to have some problems managing the different
threads ran by my application (connection pool towards Redis, workers for the
HTTP server, the main
that handles the WebAssembly engine).
Because of that, the response time of the web server are fluctuating a lot.
The unikernel can be built using the following Makefile target:
make build
The unikernel must be run using QEMU, the uhyve hypervisor cannot be used because it doesn't have network support yet.
The demo application needs to interact with a Redis server. This can be started with the help of docker:
docker run --name some-redis --net host redis
Note: the container will have access to the network stack of the host. This is convenient because it will make the Redis server reachable by the unikernel at the
10.0.2.2
address.
Once Redis is running, the unikernel can be run using the following Makefile target:
make run
Note: this has been tested only on a Linux host.
This will start QEMU using the "user networking (SLIRP)"
stack. This is slower than using a tap
device, but it works out of the box
and doesn't require root privileges.
Port 3000 on the host will be forwarded to port 3000 of the guest. This is the port used by the web server of the unikernel.
Note: The unikernel application has different cli flags. These can be set as kernel flags. This is done inside of the
Makefile
, using QEMU-append
flag.