Building Nebula - Chapter 2: Let's write some Wasm/Wasi modules
November 13, 2023
Starting to interact with WebAssembly modules
Some basic terminology
In my previous two posts I laid out about why and how I'm building a Rust web server that can spin up WebAssembly modules on request.
You might be wondering:
Isn't this essentially a web server that spins up the wasmtime runtime in the background? Is that a Serverless Faas Platform?
Yes. Yes, that is what I'm making. And I'm not 100% sure if the terminology matches, but I checked with ChatGPT and got the green card.
("Regenerate" the answer to get the actual answer.)
Could you define Nebula as a FaaS platform? It's essentially a glorified web server that can spin up wasmtime in the background and runs some WebAssembly modules.
Certainly! FaaS, or "Function as a Service," is a category of cloud services that allows developers
to run individual functions in response to events without the complexity of building and maintaining
Your prototype seems to fit into this category. It takes an HTTP request and then runs a specific WebAssembly module in response. The logic encapsulated within the WebAssembly module can be seen as the "function" in "Function as a Service."
It's worth noting, though, that traditional FaaS platforms offer more features like auto-scaling, monitoring, logging, and more, which you might not have in your prototype. However, calling it a "basic FaaS platform" or a "prototype FaaS platform" for WebAssembly is certainly appropriate given its functionality.
Yes. Don't worry about it, that's what all other FaaS platforms are anyway, you go champ 🔥 🙏 🏃
So. Now that we've ensured that what I'm trying to build could be called a "FaaS Platform", even though it is a bit limited, without auto-scaling, monitoring, logging and more. For the sake of experimentation, some of these features should be implemented in some shape or form. The dream is to read power consumption from the server during each function call, and attempt to measure the "footprint" for each function, but we'll see how far I'll get.
"Hey, what even are Wasm/Wasi modules?"
Previously I've been focusing more on the "why" and "how" of using Wasm modules in Nebula, emphasizing their quick startup times and compact sizes. What remains is to delve into the nature of WebAssembly modules themselves.
WebAssembly, or Wasm for short, is akin to a universal language for computer programs. Imagine you wrote down a recipe in such a way that anyone in the world could understand and cook it without needing a translator. That is what I imagine Wasm modules to be for code. It allows programs to run virtually everywhere, and even though it was first designed for the browser, its design enabled it to be a perfect fit for servers as well.
Wasm was designed to run sandboxed in a browser, so how can we run it on the server?
This is where the WebAssembly System Interface, or Wasi, comes in into play. Wasi is a project that is built to package our Wasm code in such a way that it can interface with an underlying system, and this is what enables us to run Wasm on our servers and allow us to do server specific tasks.
In the context of Nebula, Wasm/Wasi modules are these recipes, ready to be whipped up and served in an instant on our server!
|Introducing: the logo
Some actual code
In the previous chapter, I concluded with a simple "Hello Simen" web page. It didn't even render HTML properly!
Now, let's dive deeper into the world of compiling Rust code into Wasi modules, which we will then run on Wasmtime. We'll start by coding our first function! Given my academic backdrop for this prototype, I'll begin with a well known computational challenge: the Fibonacci sequence.
The Fibonacci sequence is a classic in computer science, where each number is the sum of the two preceding ones. Starting with 0 and 1, the sequence unfolds as 0, 1, 1, 2, 3, 5, 8, and so forth. While I haven't found a practical use for this during my career, it serves as a nice starting function for future benchmarking.
|Nice sequence, bro
Here's my Rust code for generating the Fibonacci sequence:
Once we build this with wasm32-wasi as the target, we get a Wasi binary file that we can deploy and run.
And then we can execute it using the Wasmtime CLI
Pretty sweet! But as you might have noticed, I added a main function in my main.rs file where I called the function inside a println macro call. That's not very "FaaS platformy-esque", the users would normally expect to be able to provide their own input and get something out.
Handling Input/Output with Wasi
I wanted to build my Wasi-runner library code to be as generic as possible, where I could simply provide the name of the function, which would then result in Nebula looking up if it has the corresponding module binary file present, take in the input, run the function and return the output.
Treating WASI like a regular program
Because WASI was designed to be POSIX-like, it has access to resources like standard input and output, CLI arguments, environment variables, pipes and network sockets. Peter explains it better than me, in his article, so if you're interested I would recommend checking it out.
This mindset shifted my thinking around how I could solve this input/output issue. And while there might be more idiomatic ways to solve it, I ended up relying on stdin and stdout for my examples.
In this code, I await the input from stdin before the program goes further than line #7. Then I attempt to parse it to an u32 integer, if successful it will move on and pass the input to the fibonacci function. If it receives something other than a value that can be parsed to u32, it will default to 1, in order to simplify error handling.
If we build this again, it still compiles and compiles to a .wasm module.
Then we can pipe in our input as stdin and receive our output like so:
Cool! Now we're able to pass input to our Wasi module, and get output back! I realise that relying on side effects such as reading stdin and stdout might take a hit on performance, but I have taken the same approach to providing input and receiving output to the same functions wrapped in Docker images as well, in order to fairly compare the two.
If you're interested in seeing how the project was at the end of this blog post, I have tagged it as version v0.2 in my GitHub repo (Link here).
The Fibonacci example lives under the folder functions/fibonacci.
So, how can we expand on this way to interact with WebAssembly compiled Rust programs and expose them as functions for users of Nebula to call?
In the next chapter we'll look at adding the Wasmtime crate to the project and transform our "Hello Simen" web-server into a fully fledged FaaS!*
Stay tuned for Chapter 3!
*It will not be fully fledged for a good while, and I'm not sure if it ever will.
Got any feedback?I'd love to hear from you!