🤖

wasm のプラットフォームに spin と wasmCloud を試してみる

2024/08/11に公開

wasi-http によって wasm がホスト言語に頼らずスタンドアロンで web server を立てられるようになった。

最近出た中で wasm のホスティングサービスに, spin と wasmcloud がある。これら2つを比較しながら試す。


spin

https://www.fermyon.com/spin

fermyon が開発している wasm serverless service.

spin 自体が提供する spin cloud にホスティングするのと、 SpinKube で k8s にホスティングするパターンがある。

install

$ curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
$ cp spin ~/bin
$ spin new
$ cd spin-rust
$ spin build
$ spin up
# open http://localhost:3000/

生成されたコードを見る

use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

/// A simple Spin HTTP component.
#[http_component]
fn handle_spin_rust(req: Request) -> anyhow::Result<impl IntoResponse> {
    println!("Handling request to {:?}", req.header("spin-full-url"));
    Ok(Response::builder()
        .status(200)
        .header("content-type", "text/plain")
        .body("Hello, Fermyon")
        .build())
}

Deploy

spin cloud にデプロイしてみる。。

$ spin login するとログインを促されるので、GitHub 連携して one-time code を入力して完了。

$ spin cloud deploy すると、 https://cloud.fermyon.com/ にデプロイステータスが表示される。

今回は https://spin-rust-tjttf312.fermyon.app/ にデプロイされた。

一行だけ変更して spin build && spin cloud deploy

33秒で反映された。wasm にしてはあんまり速くない気がする。ビルドサイズを見てみる。

$ ls -al target/wasm32-wasi/release/spin_rust.wasm  
.rwxr-xr-x 1.9M kotaro.chikuba 10 Aug 17:32 target/wasm32-wasi/release/spin_rust.wasm

optimize されてないのかな。 twiggy で中身を見てみる。

# cargo install twiggy
$ twiggy top target/wasm32-wasi/release/spin_rust.wasm -n 10
 Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼──────────────────────────────────────────────────
        69949736.02% ┊ custom section '.debug_str'
        44754223.05% ┊ custom section '.debug_info'
        27645414.24% ┊ custom section '.debug_line'
        1912329.85% ┊ custom section '.debug_ranges'
         529792.73% ┊ custom section 'component-type:platform'
         467202.41% ┊ "function names" subsection
         364181.88% ┊ data[0]
         140090.72% ┊ custom section 'component-type:imports'
          79130.41% ┊ custom section 'component-type:wasi-http-trigger'
          65380.34% ┊ dlmalloc
        1626518.38% ┊ ... and 668 more.
       1941953100.00% ┊ Σ [678 Total Rows]

debug info がメインなので、 release ビルドをさせればいい。

Cargo.toml に追加

[profile.release]
lto = true
opt-level = "z"

これで 798k

wasm-snip でデバッグ情報を取り除く

# cargo install wasm-snip
$ wasm-snip target/wasm32-wasi/release/spin_rust.wasm -o target/wasm32-wasi/release/spin_rust.wasm

277kb

この状態で twiggy で中身を見てみる。

$ twiggy top target/wasm32-wasi/release/spin_rust.wasm -n 10
 Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
         5297919.15% ┊ custom section 'component-type:platform'
         3990814.43% ┊ "function names" subsection
         3362012.15% ┊ data[0]
         140095.06% ┊ custom section 'component-type:imports'
          79132.86% ┊ custom section 'component-type:wasi-http-trigger'
          66702.41% ┊ anyhow::fmt::<impl anyhow::error::ErrorImpl>::debug::he4ba69e13e10de28
          62072.24% ┊ dlmalloc
          45771.65% ┊ wasi:http/incoming-handler@0.2.0#handle
          34471.25% ┊ <&T as core::fmt::Display>::fmt::h22c4c1857bc12b26
          33591.21% ┊ <spin_sdk::http::Request as spin_sdk::http::conversions::TryFromIncomingRequest>::try_from_incoming_request::{{closure}}::h3e1fb0a378bb429d
        10394437.57% ┊ ... and 553 more.
        276633100.00% ┊ Σ [563 Total Rows]

プラットフォームや wasi インターフェース関連なので、雑に小さくできるのはここまで。

この状態でデプロイが高速化するか見てみる。

$ spin cloud deploy
Uploading spin-rust version 0.1.0 to Fermyon Cloud...
Deploying...
Waiting for application to become ready............ ready

View application:   https://spin-rust-tjttf312.fermyon.app/
Manage application: https://cloud.fermyon.com/app/spin-rust

33 秒。ここは変わらないっぽい。おそらくスケール速度が変わるはずだが、無料枠でベンチマークしたくないので。

デバッグのしやすさと相談してこの辺の情報を削ることになるはず。

spin on k8s

k8s を用意するのは面倒なので紹介に留めるが、k8s に spin をホストして、そこに対して spin を deploy する方法がある。

https://developer.fermyon.com/spin/v2/kubernetes

https://www.spinkube.dev/

https://www.spinkube.dev/docs/topics/packaging/

Quikstart を覗いた感じだと、 spin cloud 自体を k8s にデプロイして、そこに対して wasm をデプロイするように見える。

$ k3d cluster create wasm-cluster \
  --image ghcr.io/spinkube/containerd-shim-spin/k3d:v0.15.1 \
  --port "8081:80@loadbalancer" \
  --agents 2
# Package and Distribute the hello-spin app
$ spin registry push --build ttl.sh/hello-spin:24h
$ spin kube deploy --from ttl.sh/hello-spin:24h
spinapp.core.spinoperator.dev/hello-spin created

自分は wasm に軽量さ、手軽さを求めてるのであえて k8s を使おうとは思わないのだが、逆に k8s を使ってる環境で実験的に wasm ecosystem を試したい場合に使える。セキュリティ要件で VPC 組む必要があっても、 k8s の設定で対応できそう。入口は広いほうがいい。

https://www.fermyon.com/pricing

Starter プランが無料。100,000 requests/month, sqlite 1GB。

Growthプラン $19.38/month で 1,000,000 requests まで捌ける。egress が 50GB/month 。それ以上は要相談。50GB の sqlite が付いている。


wasmCloud

https://wasmcloud.com/

これも CNCF 系プロダクトで、docker, k8s, edge にデプロイ出来る。

install

wash という CLI ツールをいれる。洗濯みたい。

https://wasmcloud.com/docs/installation

# mac
$ brew install wasmcloud/wasmcloud/wash
$ wash up
🛁 wash up completed successfully, already running
🕸  NATS is running in the background at http://127.0.0.1:4222
📜 Logs for the host are being written to /Users/kotaro.chikuba/.wash/downloads/wasmcloud.log

nats はこれ

https://nats.io/

NATS is a simple, secure and high performance open source data layer for cloud native applications, IoT messaging, and microservices architectures.
We feel that it should be the backbone of your communication between services. It doesn't matter what language, protocol, or platform you are using; NATS is the best way to connect your services.

簡略版 k8s みたいなものだろうか。
プラットフォームを立ち上げて、そこに対してデプロイするみたいなイメージ。

https://wasmcloud.com/docs/ecosystem/wadm/

wasmCloud Application Deployment Manager (wadm)は、宣言型アプリケーションのデプロイメントを管理し、アプリケーションの現在の状態と希望する状態を調整します。
宣言的デプロイメントパターンでは、開発者はアプリケーションのコンポーネント、構成、スケーリングプロパティを、バージョン管理、共有、編集、その他真実のソースとして使用できる静的構成ファイルを使用して定義します。wasmCloud では、これらのアプリケーション マニフェストはオープン アプリケーション モデル (OAM) に準拠し、YAML または JSON で記述できます。デプロイが宣言されると、wadm はその宣言を実現するための低レベルコマンドを発行します。

nats-server + wadm で k8s + deployment manfiest みたいな話に見える。

$ wash new component hello --template-name hello-world-rust
$ cd hello
$ wash build
$ wash app deploy wadm.yaml

# プロセスを確認
$ wash app list
  Name               Latest Version               Deployed Version             Deploy Status   Description                                                                                                  
  rust-hello-world   01J4Y17MGBGT73PS74H9WYA81Z   01J4Y17MGBGT73PS74H9WYA81Z        Deployed   HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)  
$ curl localhost:8080
Hello from Rust

動いた。

wadm.yaml はどうなっているんだろうか

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: rust-hello-world
  annotations:
    description: 'HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
    wasmcloud.dev/authors: wasmCloud team
    wasmcloud.dev/source-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/http-hello-world/wadm.yaml
    wasmcloud.dev/readme-md-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/rust/components/http-hello-world/README.md
    wasmcloud.dev/homepage: https://github.com/wasmCloud/wasmCloud/tree/main/examples/rust/components/http-hello-world
    wasmcloud.dev/categories: |
      http,http-server,rust,hello-world,example
spec:
  components:
    - name: http-component
      type: component
      properties:
        image: file://./build/http_hello_world_s.wasm
        # To use the a precompiled version of this component, use the line below instead:
        # image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
      traits:
        # Govern the spread/scheduling of the component
        - type: spreadscaler
          properties:
            instances: 1

    # Add a capability provider that enables HTTP access
    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.22.0
      traits:
        # Establish a unidirectional link from this http server provider (the "source")
        # to the `http-component` component (the "target") so the component can handle incoming HTTP requests,
        #
        # The source (this provider) is configured such that the HTTP server listens on 127.0.0.1:8080
        - type: link
          properties:
            target: http-component
            namespace: wasi
            package: http
            interfaces: [incoming-handler]
            source_config:
              - name: default-http
                properties:
                  address: 127.0.0.1:8080

パッと見 k8s manifest に見える。そのまま k8s に apply できる?

https://wasmcloud.com/docs/kubernetes

kubectl apply -f wadm.yaml
application/echo created

k8s custom resource として定義されてるっぽい。

https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/

Rust の lib.rs をみてみる。

wit_bindgen::generate!({
    generate_all
});

use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;

struct HttpServer;

impl Guest for HttpServer {
    fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
        let response = OutgoingResponse::new(Fields::new());
        response.set_status_code(200).unwrap();
        let response_body = response.body().unwrap();
        ResponseOutparam::set(response_out, Ok(response));
        response_body
            .write()
            .unwrap()
            .blocking_write_and_flush(b"Hello from Rust!\n")
            .unwrap();
        OutgoingBody::finish(response_body, None).expect("failed to finish response body");
    }
}

export!(HttpServer);

spin は derive で wasmcloud は impl Guest for HttpServer みたいな違いだが、本質的な違いはなさそう。

ところで次のコードは moonbit の http-wasi で全く同じものを見た。

        response_body
            .write()
            .unwrap()
            .blocking_write_and_flush(b"Hello from Rust!\n")
            .unwrap();

http-wasi を使うと同じようなインターフェースになるようだ。
spin はおそらく専用のラッパーで隠蔽してるのに対し、 wasmCloud は wit が剥き出しになっている。

wit-deps を使っている。 wit/deps.toml をみる。

http = "https://github.com/WebAssembly/wasi-http/archive/v0.2.0.tar.gz"
keyvalue = "https://github.com/WebAssembly/wasi-keyvalue/archive/main.tar.gz"
logging = "https://github.com/WebAssembly/wasi-logging/archive/main.tar.gz"

wit-deps update で wit/deps 以下に import している。
これらのコードは次のマクロでコードとして展開されている。

wit_bindgen::generate!({
    generate_all
});

ビルドサイズは 1.7M。最適化するにしても同じになるのでスキップ。

なんとなくの感想だが、基盤が安定しない今は wit を下手にラップするより剥き出しのほうが安心できる気がしていて、その点 spin より wasmcloud のが筋が良さそうに見える。

デプロイ方法を調べたが wasmcloud 自体は https://cosmonic.com/ に由来するようだが、これ自体は一般ユーザーへリリースされていない?
現状、 wasmCloud を動かすには、 k8s cluster を用意する必要がある。


まとめ

  • spin はSDKでラップされてていて独自プラットフォーム感強め。ホスティングサービス付き。ユースケースの情報が少なく、信頼性は不明。
  • wasmCloud はCNCFでホストされており、標準準拠感が強い。動かすには自前の k8s cluster を用意する必要あり。

Discussion