Wasm Builders 🧱

Cover image for Capsule, my personal wasm FaaS (part 2)
Philippe Charrière
Philippe Charrière

Posted on • Updated on

Capsule, my personal wasm FaaS (part 2)

Sept 4th, 2022 - update following release 0.1.9

Some days ago, I presented my new side project Capsule (see Capsule, my personal wasm multi-tools knife (part 1)). Quickly, at the beginning, Capsule is a "wasm module launcher" (you can run it as a CLI or as a tiny HTTP server).

I'm developing Capsule thanks to the Wazero project. Initially, the project's goal was to progress with GoLang and WebAssembly (especially with WASI) and to experiment with Wazero. Very quickly, I was able to serve wasm modules through HTTP.
But dealing with HTTP ports was a bit messy (every wasm module is a function, and each module has a different port). Then I wanted to be able to call every function like this: http://localhost:8888/functions/<name_of_the_function> or even like this http://localhost:8888/functions/<name_of_the_function>/<revision_name> (I wanted to serve several versions of the same function at the same time ... like bleu, green revisions πŸ˜‰). So I developed a small reverse proxy thanks to the Gin Web Framework. At the same time, I decided to create a kind of "wasm modules registry" (always with Gin 😍).
At this moment, I needed to deploy remotely (from my laptop) a wasm module to a remote version of my reverse proxy (hosted on the cloud or somewhere else) with a REST API. It was the birth of the Capsule Worker (I even did a CLI to ease the deployment of the wasm module).

Now, I can say, "Oh, it seems I have a little FaaS" based on Wasm, Go, and TinyGo. πŸŽ‰

How to play with "Capsule FaaS".

Setup

There are five components to run "Capsule FaaS":

  • The Capsule launcher
  • The Capsule registry
  • The Capsule reverse-proxy
  • The Capsule worker
  • The Capsule CLI (aka caps, thank you Darek Dwornikowski for the nickname πŸ™‚)

You can download every component on: https://github.com/bots-garden/capsule/releases/tag/0.1.9, untar each of them, copy the executables to /usr/local/bin ... or use a "quick'n dirty" script like this one:

#!/bin/bash
CAPSULE_VERSION="0.1.9"
CAPSULE_OS="linux"
CAPSULE_ARCH="amd64"

# Install the capsule launcher
wget https://github.com/bots-garden/capsule/releases/download/${CAPSULE_VERSION}/capsule-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
sudo tar -zxf capsule-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz --directory /usr/local/bin
rm capsule-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz

# Install the capsule registry
wget https://github.com/bots-garden/capsule/releases/download/${CAPSULE_VERSION}/capsule-registry-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
sudo tar -zxf capsule-registry-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz --directory /usr/local/bin
rm capsule-registry-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz

# Install the capsule reverse-proxy
wget https://github.com/bots-garden/capsule/releases/download/${CAPSULE_VERSION}/capsule-reverse-proxy-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
sudo tar -zxf capsule-reverse-proxy-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz --directory /usr/local/bin
rm capsule-reverse-proxy-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz

# Install the capsule worker
wget https://github.com/bots-garden/capsule/releases/download/${CAPSULE_VERSION}/capsule-worker-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
sudo tar -zxf capsule-worker-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz --directory /usr/local/bin
rm capsule-worker-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz

# Install the capsule CLI
wget https://github.com/bots-garden/capsule/releases/download/${CAPSULE_VERSION}/caps-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
sudo tar -zxf caps-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz --directory /usr/local/bin
rm caps-${CAPSULE_VERSION}-${CAPSULE_OS}-${CAPSULE_ARCH}.tar.gz
Enter fullscreen mode Exit fullscreen mode

you can find the script here: https://github.com/bots-garden/capsule-faas-demo/blob/main/install-capsule-faas.sh

Launch πŸš€ the Capsule FaaS

Start the Capsule Registry

Run the below command:

DOWNLOADED_FILES_PATH="${PWD}/registry/functions"
CAPSULE_REGISTRY_ADMIN_TOKEN="AZERTYUIOP" \
capsule-registry \
   -files="${DOWNLOADED_FILES_PATH}" \
   -httpPort=4999
Enter fullscreen mode Exit fullscreen mode
  • CAPSULE_REGISTRY_ADMIN_TOKEN="AZERTYUIOP" is not mandatory, but if you want to protect the wasm module uploading to the registry, use it.
  • We will see later how to use the registry

Start the Capsule Reverse Proxy

Run the below command:

CAPSULE_REVERSE_PROXY_ADMIN_TOKEN="1234567890" \
capsule-reverse-proxy \
   -backend=memory \
   -httpPort=8888
Enter fullscreen mode Exit fullscreen mode
  • CAPSULE_REVERSE_PROXY_ADMIN_TOKEN="1234567890" is not mandatory, but if you want to protect the API, use it.
  • -backend=memory: right now, everything is handle in memory (persistence will come in a future release).

Start the Capsule Worker

Run the below command:

CAPSULE_REVERSE_PROXY_ADMIN_TOKEN="1234567890" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
capsule-worker \
   -reverseProxy=http://localhost:8888 \
   -backend=memory \
   -capsulePath=capsule \
   -httpPortCounter=10000 \
   -httpPort=9999
Enter fullscreen mode Exit fullscreen mode
  • CAPSULE_REVERSE_PROXY_ADMIN_TOKEN="1234567890" and CAPSULE_WORKER_ADMIN_TOKEN="0987654321" are not mandatory, but if you want to protect the API, use them.

Publish a wasm function to the registry

First, create and build a hello function

hello.go:

package main

// TinyGo wasm module
import (
  hf "github.com/bots-garden/capsule/capsulemodule/hostfunctions"
)

func main() {
  hf.SetHandleHttp(Handle)
}

func Handle(req hf.Request) (resp hf.Response, errResp error) {
  message, _ := hf.GetEnv("MESSAGE")
  token, _ := hf.GetEnv("TOKEN")
  html := `
  <html>
    <head>
      <meta charset="utf-8">
      <title>Wasm is fantastic 😍</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        .container { min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; }
        .title { font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; }
        .subtitle { font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; }
        .links { padding-top: 15px; }
      </style>
    </head>

    <body>
      <section class="container">
        <div>
          <h1 class="title">πŸ‘‹ Hello World 🌍</h1>
          <h2 class="subtitle">Served with πŸ’œ by Capsule πŸ’Š</h2>
          <h2 class="subtitle">` + message + `</h2>
          <h2 class="subtitle">` + token + `</h2>
        </div>
      </section>
    </body>

  </html>
  `

  headers := map[string]string{
    "Content-Type": "text/html; charset=utf-8",
  }

  return hf.Response{Body: html, Headers: headers}, nil
}
Enter fullscreen mode Exit fullscreen mode

Find the entire sample here: https://github.com/bots-garden/capsule-faas-demo/tree/main/src/functions/hello

Build the wasm module:

tinygo build -o hello.wasm -scheduler=none -target wasi ./hello.go
Enter fullscreen mode Exit fullscreen mode

Then, publish the wasm module to the registry

We will use the Capsule CLI: caps

CAPSULE_REGISTRY_ADMIN_TOKEN="AZERTYUIOP" \
caps publish \
-wasmFile=./src/functions/hello/hello.wasm -wasmInfo="this is the hello module" \
-wasmOrg=demo -wasmName=hello -wasmTag=0.0.0 \
-registryUrl=http://localhost:4999
Enter fullscreen mode Exit fullscreen mode

πŸ– the wasm modules are published to registry/functions/demo (the .info file is generated automatically)

./registry
β”œβ”€β”€ functions
β”‚  └── demo
β”‚     β”œβ”€β”€ hello
β”‚     β”‚  └── 0.0.0
β”‚     β”‚     β”œβ”€β”€ hello.info
β”‚     β”‚     └── hello.wasm
β”‚     └── hey
β”‚        └── 0.0.0
β”‚           β”œβ”€β”€ hey.info
β”‚           └── hey.wasm
Enter fullscreen mode Exit fullscreen mode

Deploy the hello wasm function to the worker

We will deploy a first revision (-revision=orange) of the hello function:

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps deploy \
-function=hello \
-revision=orange \
-downloadUrl=http://localhost:4999/demo/hello/0.0.0/hello.wasm \
-envVariables='{"MESSAGE": "Revision 🟠","TOKEN": "😑"}'
Enter fullscreen mode Exit fullscreen mode
  • A function deployment is always related to a revision
  • The revision is created at the same time
  • A function can be "linked" to several revisions
  • A revision is like a version of a running function
  • Use -envVariables to pass environment variables to the module

The output of the command will be:

⏳ [deploying to worker] hello / orange
πŸ™‚ [deployed to worker] hello / orange
🌍 [serving] http://localhost:8888/functions/hello/orange
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ Now you can access the function with http://localhost:8888/functions/hello/orange

hello orange

Deploy again (twice) the hello wasm function to the worker

Now, deploy several times the same revision (orange) of the hello function and change the environment variables:

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps deploy \
-function=hello \
-revision=orange \
-downloadUrl=http://localhost:4999/demo/hello/0.0.0/hello.wasm \
-envVariables='{"MESSAGE": "Revision 🟠","TOKEN": "😑🀬"}'
Enter fullscreen mode Exit fullscreen mode
CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps deploy \
-function=hello \
-revision=orange \
-downloadUrl=http://localhost:4999/demo/hello/0.0.0/hello.wasm \
-envVariables='{"MESSAGE": "Revision 🟠","TOKEN": "😑🀬πŸ₯΅"}'
Enter fullscreen mode Exit fullscreen mode

If you refresh the web page, you can notice that the "angry smileys" are changing:

hello orange

hello orange

We have 3 running instances of the orange revision (and the reverse-proxy is doing a kind of load balancing on these 3 instances).

Deploy 2 new revisions of the hello wasm function to the worker (blue and green)

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps deploy \
-function=hello \
-revision=blue \
-downloadUrl=http://localhost:4999/demo/hello/0.0.0/hello.wasm \
-envVariables='{"MESSAGE": "Revision πŸ”΅","TOKEN": "πŸ‘©β€πŸ”§πŸ§‘β€πŸ”§πŸ‘¨β€πŸ”§"}'
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [deploying to worker] hello / blue
πŸ™‚ [deployed to worker] hello / blue
🌍 [serving] http://localhost:8888/functions/hello/blue
Enter fullscreen mode Exit fullscreen mode
CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps deploy \
-function=hello \
-revision=green \
-downloadUrl=http://localhost:4999/demo/hello/0.0.0/hello.wasm \
-envVariables='{"MESSAGE": "Revision 🟒","TOKEN": "🍏πŸ₯πŸ‰"}'
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [deploying to worker] hello / green
πŸ™‚ [deployed to worker] hello / green
🌍 [serving] http://localhost:8888/functions/hello/green
Enter fullscreen mode Exit fullscreen mode

Open the 2 links in your browser:

blue revision

green revision

So, now, we have 3 revisions of the hello function:

Then we can define which revision is the default revision.

Define a "default" revision

Defining a "default" revision means you can call the default revision without the name of the selected revision, like this:

For example, we set the default revision to the blue revision=

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps set-default \
-function=hello \
-revision=blue
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [setting default revision] hello / blue
πŸ™‚ [the default revision is set]-> hello / blue
🌍 [serving] http://localhost:8888/functions/hello
Enter fullscreen mode Exit fullscreen mode

Now, we can call the blue revision directly with http://localhost:8888/functions/hello

Switch to another revision

To change again the default revision, use the same command with only changing the name of the revision:

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps set-default \
-function=hello \
-revision=green
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [setting default revision] hello / green
πŸ™‚ [the default revision is set]-> hello / green
🌍 [serving] http://localhost:8888/functions/hello
Enter fullscreen mode Exit fullscreen mode

Remove the default revision of a function

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps unset-default \
-function=hello
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [unsetting default revision] hello
πŸ™‚ [the default revision is unset]-> hello
Enter fullscreen mode Exit fullscreen mode

Un-deploy a revision

You can un-deploy the revision of a function (and kill the related process == stop the HTTP server):

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
CAPSULE_WORKER_ADMIN_TOKEN="0987654321" \
caps un-deploy \
-function=hello \
-revision=green
Enter fullscreen mode Exit fullscreen mode

output:

⏳ [un-deploying revision] hello / green
πŸ™‚ [the revision is un-deployed (all processes killed)]-> hello / green
Enter fullscreen mode Exit fullscreen mode

Get information about the worker

To get the list of all the deployed revisions, use the following command:

CAPSULE_WORKER_URL="http://localhost:9999" \
CAPSULE_BACKEND="memory" \
caps worker
Enter fullscreen mode Exit fullscreen mode

output:

{
    "hello": {
        "blue": {
            "isDefaultRevision": "false",
            "wasmModules": {
                "8540": {
                    "envVariables": {
                        "MESSAGE": "Revision πŸ”΅",
                        "TOKEN": "πŸ‘©β€πŸ”§πŸ§‘β€πŸ”§πŸ‘¨β€πŸ”§"
                    },
                    "localUrl": "http://localhost:10004",
                    "remoteUrl": "http://localhost:8888/functions/hello/blue"
                }
            },
            "wasmRegistryUrl": "http://localhost:4999/demo/hello/0.0.0/hello.wasm"
        },
        "orange": {
            "isDefaultRevision": "false",
            "wasmModules": {
                "7518": {
                    "envVariables": {
                        "MESSAGE": "Revision 🟠",
                        "TOKEN": "😑"
                    },
                    "localUrl": "http://localhost:10001",
                    "remoteUrl": "http://localhost:8888/functions/hello/orange"
                },
                "7908": {
                    "envVariables": {
                        "MESSAGE": "Revision 🟠",
                        "TOKEN": "😑🀬"
                    },
                    "localUrl": "http://localhost:10002",
                    "remoteUrl": "http://localhost:8888/functions/hello/orange"
                },
                "8095": {
                    "envVariables": {
                        "MESSAGE": "Revision 🟠",
                        "TOKEN": "😑🀬πŸ₯΅"
                    },
                    "localUrl": "http://localhost:10003",
                    "remoteUrl": "http://localhost:8888/functions/hello/orange"
                }
            },
            "wasmRegistryUrl": "http://localhost:4999/demo/hello/0.0.0/hello.wasm"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That's all for this sunny Sunday 🌀.

One more thing: Capsule is running pretty well on a Pi3A+ (use the arm64 files of the release)

If you want to play with Capsule FaaS without installing anything, you can use this project with Gitpod: https://github.com/bots-garden/capsule-faas-demo

Photo by danilo.alvesd on Unsplash

Top comments (0)