A WebAssembly-based container runtime for the Apache OpenWhisk serverless platform.
The cold start problem is well-known in serverless platforms. The first invocation of a function requires provisioning all the resources necessary for its execution. Typically that means starting a docker container, i.e. a node.js container to then inject some JavaScript code for execution. This process can take hundreds of milliseconds and is thus unacceptable for latency-critical tasks. This project is an evaluation of using WebAssembly instead of Docker containers to implement serverless functions. The simple idea is that WebAssembly is a lighter-weight sandboxing mechanism so the cold start latency should be much smaller. It builds on top of Apache OpenWhisk as the serverless platform. There are three repositories relevant to this:
- This repository, which implements the WebAssembly-based container runtime.
- The OpenWhisk fork that implements the runtimes support on the OpenWhisk side
- The diploma thesis repository, which contains the thesis describing the motivation, background, design and evaluation of this idea.
The project is split into multiple crates, which are:
ow-commoncontains common types such as theWasmRuntimetrait or types that represent OpenWhisk payloads.ow-executorimplements the actual container runtime and the OpenWhisk runtime protocol.ow-wamrimplements theWasmRuntimetrait for the WebAssembly Micro Runtime.ow-wasmerimplements theWasmRuntimetrait for Wasmer.ow-wasmtimeimplements theWasmRuntimetrait for Wasmtime.ow-wasm-actioncontains abstractions for building WebAssembly serverless functions ("actions" in jOpenWhisk terminology) and has a number of example actions.ow-wasmtime-precompilerimplements Ahead-of-Time compilation forwasmtime(which is part of the runtime as of version0.26), making this crate obsolete onceow-wasmtimeis migrated to that version.ow-evaluationcontains some tests used for evaluating the performance of the system, such as concurrency tests and cold start tests.
As a small tutorial, let's build the wasmtime executor and run one of the examples.
- To build the executor with wasmtime run the following command from the root of this repository:
cargo build --manifest-path ./ow-executor/Cargo.toml --release --features wasmtime_rt- Next we build the
addexample for thewasm32-wasitarget with:
cargo build --manifest-path ./ow-wasm-action/Cargo.toml --release --example add --target wasm32-wasi --no-default-features --features wasm
# Optional step to optimize the compiled Wasm if `wasm-opt` is installed
# On Ubuntu it can be installed with `sudo apt install binaryen`
wasm-opt -O4 -o ./target/wasm32-wasi/release/examples/add.wasm ./target/wasm32-wasi/release/examples/add.wasm- Precompile the example for efficient execution with wasmtime:
cargo run --manifest-path ./ow-wasmtime-precompiler/Cargo.toml --release --bin wasmtime ./target/wasm32-wasi/release/examples/add.wasm-
Install wsk-cli from https://github.com/apache/openwhisk-cli/releases/tag/1.2.0
-
Clone the openwhisk repo, checkout the appropriate branch and run OpenWhisk in a separate terminal:
git clone git@github.com:PhilippGackstatter/openwhisk.git
git checkout invoker-wasm
./gradlew core:standalone:bootRunThis will print something like the following:
[ WARN ] Configure wsk via below command to connect to this server as [guest]
wsk property set --apihost 'http://172.17.0.1:3233' --auth '23bc46b1-71f6-4ed5-8c54-816aa4f8c502:123zO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP'
Execute this command.
- Run the executor in a separate terminal. OpenWhisk will forward execution requests for Wasm to it:
./target/release/executor- Upload the example zip to OpenWhisk:
wsk action create --kind wasm:0.1 add ./target/wasm32-wasi/release/examples/add-wasmtime.zip- Run the action. We need to provide the correct parameters for this action, which are defined in our action source file
ow-wasm-action/examples/add.rs. Let's see what the result of2+2is:
wsk action invoke --blocking add --param param1 2 --param param2 2which prints:
{
"activationId": "b67ca8877a494fcdbca8877a494fcd73",
"annotations": [
{
"key": "path",
"value": "guest/add"
},
{
"key": "waitTime",
"value": 23
},
{
"key": "kind",
"value": "wasm:0.1"
},
{
"key": "timeout",
"value": false
},
{
"key": "limits",
"value": {
"concurrency": 1,
"logs": 10,
"memory": 256,
"timeout": 60000
}
},
{
"key": "initTime",
"value": 3
}
],
"duration": 5,
"end": 1655918974484,
"logs": [],
"name": "add",
"namespace": "guest",
"publish": false,
"response": {
"result": {
"result": {
"result": 4
},
"status": "success",
"status_code": 0,
"success": true
},
"size": 73,
"status": "success",
"success": true
},
"start": 1655918974479,
"subject": "guest",
"version": "0.0.1"
}And we get 4 as a result. And that's it, we've run a serverless function with WebAssembly! Try running one of the other examples in ow-wasm-action/examples/, the appropriate compilation commands and features can be found in the justfile.
We can also use just to make building a bit easier. The recipes are defined in the justfile. If you don't want to install this tool, you can copy the commands from there into your terminal.
The just commands generally run operations for all three runtimes, but if not all tools for all Wasm runtimes are installed, some of the steps can be skipped.
To build the executor with the wasmtime runtime, run just build-amd64 wasmtime_rt. Other possible values for the runtime argument are wasmer_rt and wamr_rt to use wasmer or wamr respectively.
To build example actions use just build-wasm-examples. This requires binaryen's wasm-opt to be present in your PATH, which optimizes the produced wasm. For convenience you can also skip this part. To precompile the examples use just precompile, which requires wasmer and wamrc to be installed or in your PATH. Of course only one of the precompilation steps is necessary to run a given module for a certain runtime, so you can skip some of these steps as well.