March 17, 2022

Introduction to WebAssembly on the Cloud

Fermyon

webassembly wasm wagi rust

Introduction to WebAssembly on the Cloud

The first question everyone asks us when we start talking about WebAssembly is “So, does this run in the browser?”

This is a logical question. After all, the name WebAssembly (abbreviated as Wasm) implies that it is a web technology, and the original purpose of WebAssembly was to serve as a runtime inside of web browsers. In fact, all major browsers already support executing WebAssembly inside.

But that’s not really how we at Fermyon are using WebAssembly. Our interest is in running cloud services that can accomplish a few special goals:

  1. Speed. We want very fast startup time. VMs take minutes. Containers take seconds. We want to start up our cloud-side apps in milliseconds… or even microseconds.
  2. Security. We want security by default. More specifically, we want to be able to run code in a cloud service without running a risk that the code can breakout of its sandbox and cause problems for other applications in that cloud service. This is a core feature of both VMs and containers. And we think any compute service needs it.
  3. Cross-platform. One of the things that has frustrated us about containers is that you need to build a version of your container for each operating system and architecture that you want to support. But we’d like a way to compile once, and have it run on Windows, Linux, Mac, or even other OS. And we also want it to run on Intel, ARM, or whatever other architecture.
  4. Language agnostic. We want all of this PLUS we want to allow developers to write their applications in whatever language they prefer.

We started with those four goals, and then went looking for a technology that could satisfy them. And WebAssembly fit the bill. It’s fast, secure, cross-platform, and the list of languages that support compiling to WebAssembly just keeps grown.

All we needed to do was implement a cloud-side runtime that used WebAssembly similarly to the way it is used in the browser. (For more on cloud-side WebAssembly, in an earlier blog post, we compared WebAssembly to Docker Containers.)

So What Is WebAssembly?

We think the most practical way to think about WebAssembly is as binary executable format. When you compile a C program, you compile it to an operating system and architecture (such as Windows on Intel or Linux on ARM). Instead of compiling to an operating system and architecture, we can compile to the Wasm format. In Rust, it looks something like this:

$ cargo build --target wasm32-wasi

Now, instead of having an exe or a Linux elf binary, we have a wasm binary.

Wasm binaries require a runtime to execute them. This is very similar to Java’s JRE/JSDK or .NET’s CLR. (And in some ways, it is similar to scripting languages that require a runtime, like Node.js for JavaScript or the python command for Python).

Our favorite commandline runtime is wasmtime. Here’s how we would write a WebAssembly binary in Rust, compile it to Wasm, and then run it in wasmtime.

Creating and Running Wasm Executable

The process is similar for most languages, but we will use Rust here because it is really easy to show. We can follow a similar process in Go, JavaScript, C, C++, Swift or other languages. (And a great place to start is with Yo-Wasm). We’ll walk through this process on a Mac, but the process is similar for Linux and Windows.

$ cargo new hello-rust
$ cd hello-rust
$ code src/main.rs # Use whatever editor you prefer here

The code should look something like this:

fn main() {
    println!("Hello, world!");
}

That’s it! Now we can compile it to Wasm:

$ cargo build --target wasm32-wasi
   Compiling hello-wasm v0.1.0 (/Users/technosophos/Code/Rust/hello-wasm)
    Finished dev [unoptimized + debuginfo] target(s) in 1.07s

Now we have a binary in the build directory target/wasm32-wasi/debug/ named hello-wasm.wasm. We can run it with Wasmtime:

$ wasmtime target/wasm32-wasi/debug/hello-wasm.wasm
Hello, world!

To The Cloud!

The above was a little code heavy. But the important thing is that we have taken a regular program, compiled it to WebAssembly, and executed it.

Wasmtime is a command line runner, though. And we started the article with the assertion that WebAssembly is a great technology for the cloud. What would it take, therefore, to turn this simple little app into a cloud service?

That’s where the Wagi runtime comes in. With just a simple change to our source code and a couple lines of TOML, we can rebuild our application and deploy it in a web server.

Here’s our new version of the code:

fn main() {
    println!("Content-Type: text/plain");
    println!("");
    println!("Hello, world!");
}

This is still just plain old Rust, and we can compile it and run it in wasmtime:

$ cargo build --target wasm32-wasi
   Compiling hello-wasm v0.1.0 (/Users/technosophos/Code/Rust/hello-wasm)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
$ wasmtime target/wasm32-wasi/debug/hello-wasm.wasm
Content-Type: text/plain

Hello, world!

All our change has done is print the Content-Type: text/plain and an empty line. Why did we do this? Because when we start this app in Wagi, it needs to know how to instruct a client to display.

To run this in the Wagi webserver, we need to tell Wagi how to map our little app to a web address. To do this, we create a simple TOML-formatted file.

Create a modules.toml in the editor of your choice.

$ code modules.toml # Use whatever editor you prefer here

And then we just map our Wasm module to a route. In this case, we’ll make it listen on the path /.

[[module]]
module = "target/wasm32-wasi/debug/hello-wasm.wasm"
route = "/"

The above tells Wagi that when an HTTP request is received for the root path, run our little app. Let’s start Wagi and see this in action:

$ wagi -c ./modules.toml 
No log_dir specified, using temporary directory /tmp/mkbs8vx12zs0gkm6 for logs
Ready: serving on 127.0.0.1:3000 

Now we are running a webserver locally at the URL https://127.0.0.1:3000/. Let’s use the command line web browser curl to see the result:

$ curl http://localhost:3000/       
Hello, world!

That’s it! We just executed our WebAssembly module as a web application.

You might notice, though, that the Content-Type: text/plain part went away. That’s because Wagi used that part of the output to adjust the HTTP headers that are invisible to the user. If you want to see them in curl, you can use the -v flag and see the entire HTTP transaction:

*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.77.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain
< content-length: 14
< date: Fri, 17 Dec 2021 23:39:59 GMT
< 
Hello, world!
* Connection #0 to host localhost left intact

This Is The Beginning

What is WebAssembly? We think it’s best to think of WebAssembly as a secure, fast, and OS/architecture/language-neutral compile target. And while it was originally written for the web browser, we’re really excited about its potential as a cloud compute runtime.

In this article, we looked at the characteristics of WebAssembly that make it compelling for cloud computing. Then we coded up a little program.

With just a few lines of code, we wrote a Rust program that can be run as an HTTP service. We built that code into a WebAssembly binary and then started a Wagi web server running our binary.

Sure, this example was basic. But our goal was to illustrate how straightforward it is to build code into Wasm binaries that can run in the cloud. Just imagine a platform-as-a-service that runs WebAssembly modules. No need to build a Docker container or write a gigantic Kubernetes manifest, or even to select a special buildpack that the cloud can run for you. Just use your local tools, build your app, and deploy it.

Fermyon is building many new tools for cloud computing with WebAssembly. In an earlier post, we talked about our Bartholomew WebAssembly CMS. We have several other tools in the works. Stay tuned!

Appendix: Tools You’ll Need

To code up the example in this article, you will need the following tools:


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started