Zeet Labs: Deploying Rust-based WebAssembly backends with Suborbital Atmo

Introduction

We recently dove into a post that took readers into creating serverless Rust functions using WebAssembly. We wanted to follow up on that post by introducing you all to a new platform being developed by a startup called Suborbital that is allowing developers to create powerful WASM services in a declarative manner.

In this blog post, we’re going to take a deep dive into what Suborbital brings to the table as far as WebAssembly and how you can leverage that in your environment today.

What is Suborbital?

Suborbital is a cloud-native startup that seeks to leverage the power of WebAssembly to provide a compute platform for serverless developers that is faster and more secure than current compute offerings.

By leveraging the power of Wasm, Suborbital allows teams to build powerful API’s while leveraging third party code in a safe and secure manner. By leveraging a brandable IDE that can be embedded in just about any cloud-provider, engineering teams are able to build flexible and secure Wasm based API’s.

What is Atmo?

Under the hood, Suborbital runs on top of Atmo, their server application environment that leverages WebAssembly to allow developers to build and scale powerful web applications. Atmo used a declarative framework called the Directive format that makes it incredibly easy to create a scalable application without all the boilerplate code.

Now that we’ve talked a bit about what Atmo is, let’s dive into some of the underlying concepts that define its use.

Concepts

Runnables

When developers create an application with Atmo, they have the ability to segment the logic of that application into individual functions known as Runnables. By leveraging WebAssembly, Runnables have the ability to be written in any of the supported languages.

At startup, Atmo loads a Bundle of your applications Runnables and uses your application Directive (discussed next) to set up and execute your application.

The Directive

Your applications Directive is a declarative file that allows you to describe your application's business logic. By describing your application declaratively, you can avoid all of the boilerplate code that normally comes with building a web service such as binding to ports, setting up TLS, constructing a router, etc.

Here's an example Directive:

identifier: com.suborbital.guide
appVersion: v0.0.1
atmoVersion: v0.4.2

handlers:
  - type: request
    resource: /hello
    method: POST
    steps:
      - group:
        - fn: modify-url
        - fn: helloworld-rs
          onErr:
            any: continue

      - fn: fetch

  - type: request
    resource: /set/:key
    method: POST
    steps:
      - fn: cache-set

  - type: request
    resource: /get/:key
    method: GET
    steps:
      - fn: cache-get

This directive encapsulates all of the logic for your application. It describes three endpoints and the logic needed to handle them. Each handler describes a set of steps that composes a series of Runnables to handle the request.

The Runnable API

The Runnables that you developer for your Atmo application get compiled to WebAssembly, and are run in a controlled sandbox. The set of capabilities that Atmo grants to the sandbox is called the Runnable API and they are used to build your application's logic.

The Runnable API is provided via a library for each of the supported languages, and simply needs to be imported to turn your module into a Runnable. subo will configure all of this on your behalf.

The foremost basic part of the Runnable API is the Runnable interface (also known as a Rust trait or Swift protocol). Every Runnable you write will provide an instance of an object that conforms to this interface. It is very simple, and only requires one method. for example, in Rust you can run:

pub trait Runnable {
    fn run(&self, input: Vec<u8>) -> Result<Vec<u8>, RunErr>;
}

You can find more info about the Runnable API here.

Getting Started with Atmo

Installing the Subo CLI tool

We’re now ready to get started with our first Atmo project, but first we need to install the subo CLI tool. Subo is the command-line helper for working with the Suborbital Development Platform. It is used to build Wasm Runnables, generate new projects and config files, and more over time.

macOS (Homebrew)

If you're on Mac (M1 or Intel), the easiest way to install is via brew:

brew tap suborbital/subo
brew install subo

Install via pre-built binary

If you are a Linux user and prefer to install via one of the pre-built binaries, you can download one of the latest tarballs found on the releases page.

Subo does not have official support for Windows.

Install from source (requires go)

If you use Linux or otherwise prefer to build from source, simply clone this repository or download a source code release archive and run:

make subo

This will install subo into your GOPATH ($HOME/go/bin/subo by default) which you may need to add to your shell's $PATH variable.

Verify subo was installed

To verify your installation, let’s run:

subo --help

Creating a Atmo Project

Now that we have subo installed, we’re ready to create our first Atmo project. To do so, let’s run:

subo create project important-api

This project will create a Directive.yaml file which we talked a bit about earlier. It will also create an example Runnable called helloworld written in Rust. Again, The Directive file defines route handlers and connects Runnables to them.

In the Directive file, you'll see a handler set up for you that serves the POST /hello route using the helloworld Runnable:

# the Directive is a complete description of your application, including all of its business logic.
# appVersion should be updated for each new deployment of your app.
# atmoVersion declares which version of Atmo is used for the `subo dev` command.

identifier: com.suborbital.important-api
appVersion: v0.1.0
atmoVersion: v0.2.3


handlers:
  - type: request
    resource: /hello
    method: POST
    steps:
      - fn: helloworld

Building a Bundle

To build our bundle, we’re going to want to cd into the important-api directory and run:

subo build .

This will automatically compile each of your Runnables in a Docker container and bundle them together in runnables.wasm.zip to be used in Atmo. On completion, your output should look about like this:

⏩ START: building runnables in .
ℹ️  🐳 using Docker toolchain
⏩ START: building runnable: helloworld (rust)
    Updating crates.io index
[...]
✅ DONE: bundle was created -> runnables.wasm.zip @ v0.1.0

Deploying Atmo with Zeet

Atmo is distributed as a Docker image titled as suborbital/atmo. This will allow us to easily deploy our project by leveraging Zeet’s powerful abstraction features. When you run the subo create command, Subo is going to create a Dockerfile that will COPY the ./runnables.wasm.zip and add Atmo as an ENTRYPOINT. It should look a lot like this:

FROM suborbital/atmo:v0.4.2

COPY ./runnables.wasm.zip .

ENTRYPOINT [ "atmo" ]

Deploying Atmo to Zeet

Now that we’ve created a Dockerfile, it’s time to hop over to Zeet and deploy our project. First, we’ll want to log into Zeet and select Github as our project source:

Then we can move onto actually selecting the repository we’re going to use that contains the Dockerfile:

Then, after selecting our project settings, we’re ready to deploy our service into production!

Conclusion

The ability to write declarative web services with WebAssembly is going to become a powerful tool in allowing developers to quickly and securely build scalable infrastructure. While WebAssembly may have had its start in the browser, it’s quickly beginning to show that it has a wider range of capabilities than just a replacement for javascript. We’ll see where things go, but it’s exciting to watch this space progress!

Johnny Dallas

Johnny Dallas