Simplism, the straightforward way for a Wasm FaaS on Clever Cloud

Preamble

Before delving into the heart of the matter, I need to answer four questions quickly:

  • What is a Function as a Service (FaaS) platform?

  • What is Clever Cloud?

  • What is WebAssembly?

  • And finally, what is "Simplism"?

And my answers would be:

  • A FaaS (Function as a Service) platform is a service that enables the deployment and execution of functions without having to worry about the underlying infrastructure.

  • Clever Cloud, a French company, provides its users with a Platform as a Service (PaaS), which is very user-friendly. You can think of it somewhat like a French version of Heroku.

  • WebAssembly is a low-level, portable bytecode that can achieve near-native performance on the web. You can create WebAssembly programs using various languages, including Rust, Go, Zig, and others. The significant advantages of WebAssembly include efficiency, speed, security, and, of course, its multilingual nature.

  • Simplism is a highly lightweight application server that can serve WebAssembly plugins via HTTP as microservices (or even nanoservices) in a speedy and straightforward manner. Simplism is cloud provider-agnostic, allowing you to deploy it anywhere, from a simple Raspberry Pi Zero to Kubernetes.

But let's return to the subject that interests us.

First, let's talk a little about Simplism.

The Simplism functions (or plugins) contain a handler that will be called for each HTTP request. These functions are created in WebAssembly and then served using the Simplism application server. It is important to note that a function corresponds to a Simplism process (an instance of Simplism running). This ensures fine-grained control over the execution of a set of functions. Simply put, it is easy to "kill" a single function without stopping the rest of the system.

Here is an example of a plug-in (or function) in Rust, executable by Simplism:

#![no_main]

use extism_pdk::*;
use serde::{Serialize, Deserialize};


// return a hashmap of array of strings
fn headers_map() ->     std::collections::HashMap<String, Vec<String>> {
    let mut headers = std::collections::HashMap::new();
    headers.insert("Content-Type".to_string(), vec!["application/json".to_string()]);
    headers
}

#[derive(Serialize)]
struct ResponseData {
    pub body: String,
    pub header: std::collections::HashMap<String, Vec<String>>,
    pub code: i32,
}

#[derive(Serialize, Deserialize)]
struct RequestData {
    pub body: String,
    pub header: std::collections::HashMap<String, Vec<String>>,
    pub method: String,
    pub uri: String,
}

#[plugin_fn]
pub fn handle(input: String) -> FnResult<Json<ResponseData>> {

    let req: RequestData = serde_json::from_str(&input).unwrap();

    let message: String = "👋 Hello ".to_string() + &req.body;

    let resp = ResponseData { 
        body: message , 
        code: 200, 
        header: headers_map()
    };

    Ok(Json(resp))
}

Once the function is compiled, you just need to execute it as follows:

simplism listen \
./target/wasm32-wasi/release/say_hello.wasm handle \
--http-port 8080 \
--log-level info

Then, you call it like any other web service:

curl http://localhost:8080 \
-H 'content-type: application/json; charset=utf-8' \
-d 'Bob Morane'

# you should get:  
👋 Hello Bob Morane

If you want to test without installing anything, you can use this project with Gitpod: https://gitpod.io/#https://github.com/simplism-registry/say-hello

It is also possible to run a remote wasm plugin in this way:

simplism listen say_hello.wasm handle \
--http-port 8080 \
--log-level info \
--wasm-url https://github.com/simplism-registry/say-hello/releases/download/v0.0.0/say_hello.wasm

And if you don't want to install Simplism, you can use Docker (the image weighs less than 9 Mb):

docker run \
-p 8080:8080 \
-v $(pwd):/app \
--rm k33g/simplism:0.0.7 \
/simplism listen ./app/say_hello.wasm handle \
--http-port 8080 \
--log-level info \
--wasm-url https://github.com/simplism-registry/say-hello/releases/download/v0.0.0/say_hello.wasm

Simplism can spawn Simplism!

Simplism brings additional features: a Simplism process can serve as a service discovery and a launcher for other Simplism processes. Suppose you combine these two functionalities (along with the system for executing a plugin remotely). In that case, you get a small and simple (yet powerful) Function-as-a-service (FaaS) system that allows serving functions written in different languages.

Let's download a Simplism plugin and start a process in "spawn" + "discovery" mode:

simplism listen simplism-faas.wasm handle \
--http-port 8080 \
--log-level info \
--service-discovery true \
--admin-discovery-token people-are-strange \
--information "simplism faas 💜" \
--spawn-mode true \
--admin-spawn-token michael-burnham-rocks \
--wasm-url https://github.com/simplism-registry/simplism-faas/releases/download/v0.0.0/simplism-faas.wasm

# output:
🌍 downloading https://github.com/simplism-registry/simplism-faas/releases/download/v0.0.0/simplism-faas.wasm ...
🤖 this service is a service discovery
🔎 discovery mode activated: /discovery  ( 8080 )
🚀 this service can spawn other services
🌍 http server is listening on: 8080

Now, if you want to deploy functions, use the Simplism API as follows by using the /span endpoint:

ORGANISATION="simplism-registry"
PROJECT="say-hello"
WASM_FILE="say_hello.wasm"
VERSION="0.0.0"

curl -X POST \
http://localhost:8080/spawn \
-H 'admin-spawn-token:michael-burnham-rocks' \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
    "wasm-file":"${WASM_FILE}", 
    "wasm-url":"https://github.com/${ORGANISATION}/${PROJECT}/releases/download/v${VERSION}/${WASM_FILE}",
    "wasm-function":"handle", 
    "http-port":"9091", 
    "discovery-endpoint":"http://localhost:8080/discovery", 
    "admin-discovery-token":"people-are-strange",
    "admin-spawn-token":"michael-burnham-rocks",
    "information": "✋ I'm the say_hello service",
    "service-name": "say-hello"
}
EOF

PROJECT="hello"
WASM_FILE="hello.wasm"
VERSION="0.0.0"

curl -X POST \
http://localhost:8080/spawn \
-H 'admin-spawn-token:michael-burnham-rocks' \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
    "wasm-file":"${WASM_FILE}", 
    "wasm-url":"https://github.com/${ORGANISATION}/${PROJECT}/releases/download/v${VERSION}/${WASM_FILE}",
    "wasm-function":"handle", 
    "http-port":"9092", 
    "discovery-endpoint":"http://localhost:8080/discovery", 
    "admin-discovery-token":"people-are-strange",
    "admin-spawn-token":"michael-burnham-rocks",
    "information": "✋ I'm the hello service",
    "service-name": "hello"
}
EOF

You can get the list of the deployed services with this curl command:

curl http://localhost:8080/discovery \
-H 'admin-discovery-token:people-are-strange'

You will get this result:

{
    "6741":{
        "pid":6741,
        "functionName":"handle",
        "filePath":"hello.wasm",
        "recordTime":"2023-12-17T09:52:59.931598349Z",
        "startTime":"2023-12-17T09:52:49.298770722Z",
        "stopTime":"0001-01-01T00:00:00Z",
        "httpPort":"9092",
        "information":"✋ I'm the hello service",
        "serviceName":
        "hello",
        "host":""
    },
    "6853":{
        "pid":6853,
        "functionName":"handle",
        "filePath":"say_hello.wasm",
        "recordTime":"2023-12-17T09:53:00.45907357Z",
        "startTime":"2023-12-17T09:53:00.036534918Z",
        "stopTime":"0001-01-01T00:00:00Z",
        "httpPort":"9091",
        "information":"✋ I'm the say_hello service",
        "serviceName":"say-hello","host":""
    }
}

And now you can run each service using their name directly:

curl http://localhost:8080/service/hello \
-d 'Bob Morane'
# output: 🤗 Hello Bob Morane

curl http://localhost:8080/service/say-hello \
-d 'Bob Morane'
# output: 👋 Hello Bob Morane

You see, it's relatively effortless. There are other features, and I urge you to consult the project and its examples of use.

Simplism project: https://github.com/bots-garden/simplism

Now, it's time to take my experimentation to the end and deploy my small FaaS on Clever Cloud.

Let's deploy a FaaS in a minute (or two)!🚀

First, I created a "simplism-faas" repository on GitHub, which only contains a Dockerfile:

FROM k33g/simplism:0.0.7

ENV ADMIN_DISCOVERY_TOKEN=${ADMIN_DISCOVERY_TOKEN}
ENV ADMIN_SPAWN_TOKEN=${ADMIN_SPAWN_TOKEN}
EXPOSE 8080
CMD [ "/simplism",                  \
      "listen",                     \
      "simplism-faas.wasm",         \
      "handle",                     \
      "--http-port",                \
      "8080",                       \
      "--log-level",                \
      "info",                       \
      "--service-discovery",        \
      "true",                       \
      "--information",              \
      "simplism faas 💜",           \
      "--spawn-mode",               \
      "true",                       \
      "--wasm-url",                 \
      "https://github.com/simplism-registry/simplism-faas/releases/download/v0.0.0/simplism-faas.wasm" \
    ]

Then I went to create a Docker-type application at Clever Cloud, linked to my GitHub project, to be able to deploy a Simplism service (in "spawn + discovery" mode):

I selected a region and clicked on the "CREATE" button:

I didn't need any add-ons:

I defined two environment variables (I use tokens to protect the APIs) and then clicked on the "NEXT" button:

And the deployment started...

Deployment was swift (remember, the Simplism image is tiny):

Let's check if everything is OK:

curl https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/
# output: 👋 Hello! I'm Simplism FaaS 😉

So it’s time to deploy our functions again, but this time at Clever Cloud:

ORGANISATION="simplism-registry"
PROJECT="say-hello"
WASM_FILE="say_hello.wasm"
VERSION="0.0.0"

curl -X POST \
https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/spawn \
-H 'admin-spawn-token:michael-burnham-rocks' \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
    "wasm-file":"${WASM_FILE}", 
    "wasm-url":"https://github.com/${ORGANISATION}/${PROJECT}/releases/download/v${VERSION}/${WASM_FILE}",
    "wasm-function":"handle", 
    "http-port":"9091", 
    "discovery-endpoint":"http://localhost:8080/discovery", 
    "admin-discovery-token":"people-are-strange",
    "admin-spawn-token":"michael-burnham-rocks",
    "information": "✋ I'm the say_hello service",
    "service-name": "say-hello"
}
EOF

PROJECT="hello"
WASM_FILE="hello.wasm"
VERSION="0.0.0"

curl -X POST \
https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/spawn \
-H 'admin-spawn-token:michael-burnham-rocks' \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
    "wasm-file":"${WASM_FILE}", 
    "wasm-url":"https://github.com/${ORGANISATION}/${PROJECT}/releases/download/v${VERSION}/${WASM_FILE}",
    "wasm-function":"handle", 
    "http-port":"9092", 
    "discovery-endpoint":"http://localhost:8080/discovery", 
    "admin-discovery-token":"people-are-strange",
    "admin-spawn-token":"michael-burnham-rocks",
    "information": "✋ I'm the hello service",
    "service-name": "hello"
}
EOF

If you look at the logs of your application, you can see that the functions have been deployed:

Of course, you can get the list of the services like this:

curl https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/discovery \
-H 'admin-discovery-token:people-are-strange'

Now we can call the functions:

curl https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/service/hello \
-d 'Bob Morane'
# output: 🤗 Hello Bob Morane

curl https://app-4edc7318-a662-4372-ba99-251630231706.cleverapps.io/service/say-hello \
-d 'Bob Morane'
# output: 👋 Hello Bob Morane

So, you've seen that Simplism allows you to build function systems or 'nano-services' efficiently and quickly. The significant advantage of Simplism is that you can develop functions in different languages and make all of this available to your users very quickly. I encourage you to read the documentation and follow the project to discover its features (for example, Simplism exposes an HTTP API for storing and reading data in a key-value database).

Simplism has been developed in GoLang thanks to the Extism and Wazero projects.

The samples of functions are available here: https://github.com/simplism-registry

Give it a try to Simplism, and I hope you will enjoy it.

3
Subscribe to my newsletter

Read articles from Philippe Charrière directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Philippe Charrière
Philippe Charrière