wasm のプラットフォームに spin と wasmCloud を試してみる
wasi-http によって wasm がホスト言語に頼らずスタンドアロンで web server を立てられるようになった。
最近出た中で wasm のホスティングサービスに, spin と wasmcloud がある。これら2つを比較しながら試す。
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
───────────────┼───────────┼──────────────────────────────────────────────────
699497 ┊ 36.02% ┊ custom section '.debug_str'
447542 ┊ 23.05% ┊ custom section '.debug_info'
276454 ┊ 14.24% ┊ custom section '.debug_line'
191232 ┊ 9.85% ┊ custom section '.debug_ranges'
52979 ┊ 2.73% ┊ custom section 'component-type:platform'
46720 ┊ 2.41% ┊ "function names" subsection
36418 ┊ 1.88% ┊ data[0]
14009 ┊ 0.72% ┊ custom section 'component-type:imports'
7913 ┊ 0.41% ┊ custom section 'component-type:wasi-http-trigger'
6538 ┊ 0.34% ┊ dlmalloc
162651 ┊ 8.38% ┊ ... and 668 more.
1941953 ┊ 100.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
───────────────┼───────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
52979 ┊ 19.15% ┊ custom section 'component-type:platform'
39908 ┊ 14.43% ┊ "function names" subsection
33620 ┊ 12.15% ┊ data[0]
14009 ┊ 5.06% ┊ custom section 'component-type:imports'
7913 ┊ 2.86% ┊ custom section 'component-type:wasi-http-trigger'
6670 ┊ 2.41% ┊ anyhow::fmt::<impl anyhow::error::ErrorImpl>::debug::he4ba69e13e10de28
6207 ┊ 2.24% ┊ dlmalloc
4577 ┊ 1.65% ┊ wasi:http/incoming-handler@0.2.0#handle
3447 ┊ 1.25% ┊ <&T as core::fmt::Display>::fmt::h22c4c1857bc12b26
3359 ┊ 1.21% ┊ <spin_sdk::http::Request as spin_sdk::http::conversions::TryFromIncomingRequest>::try_from_incoming_request::{{closure}}::h3e1fb0a378bb429d
103944 ┊ 37.57% ┊ ... and 553 more.
276633 ┊ 100.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 する方法がある。
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 の設定で対応できそう。入口は広いほうがいい。
Starter プランが無料。100,000 requests/month, sqlite 1GB。
Growthプラン $19.38/month で 1,000,000 requests まで捌ける。egress が 50GB/month 。それ以上は要相談。50GB の sqlite が付いている。
wasmCloud
これも CNCF 系プロダクトで、docker, k8s, edge にデプロイ出来る。
install
wash という CLI ツールをいれる。洗濯みたい。
# 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 はこれ
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 みたいなものだろうか。
プラットフォームを立ち上げて、そこに対してデプロイするみたいなイメージ。
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 できる?
kubectl apply -f wadm.yaml
application/echo created
k8s custom resource として定義されてるっぽい。
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