はじめに
前編ではSpinの概要とアプリケーションの作成・実行方法について紹介しました。
後編では個人的に気になった部分をもう少し深掘りしていきます。
この記事では執筆時点の最新版であるspin v2.0.1を使用しています。
Wasm外部とのデータの受け渡し
Wasmモジュールはサンドボックスで実行されるためデフォルトで外部とのデータのやりとりができませんが、spin.toml
で明示的に設定を追加することで、環境変数の取得、ファイルの読み書き、外部へのHTTPリクエスト等の機能を使用できます。
これらの機能を利用する際はWASIとSpin SDKを利用します。ビルドされたWasmモジュールはWASIやSpinが定義したAPIでSpinランタイムを介することで外部とのデータの受け渡しが可能になります。
例えば、ハンドラ内で環境変数を利用する場合は、以下のようにspin.toml
のコンポーネント設定でenvironment
を指定します。
[component.hello-http-go]
source = "main.wasm"
allowed_outbound_hosts = []
environment = { FOO = "bar" }
[component.hello-http-go.build]
command = "tinygo build -target=wasi -gc=leaking -no-debug -o main.wasm main.go"
ハンドラをGoで実装する場合、通常のプログラムと同様にos.Getenv()
で取得できます。
...
foo := os.Getenv("FOO")
...
このコードが動作するのは、tinygo build -target=wasi...
でWASI対応のwasmモジュールにコンパイルされるためです。
作成されたハンドラのwasmバイナリを解析するとwasi_snapshot_preview1
のenviron_get
という関数をインポートして使用していることがわかります。ハンドラ側でenviron_get
を呼び出した時にSpinランタイムがspin.toml
で設定された環境変数を返すというわけです。
$ wasm-tools print main.wasm | grep environ_get
(import "wasi_snapshot_preview1" "environ_get" (func $__imported_wasi_snapshot_preview1_environ_get (;4;) (type 6)))
call $__imported_wasi_snapshot_preview1_environ_get
次に外部へのHTTPリクエストの例をみてみます。(参考: Sending Outbound HTTP Requests)
デフォルトでは全ての外部への通信が拒否されるので、spin.toml
でallowed_outbound_hosts
を指定します。
[component.hello-http-go]
source = "main.wasm"
allowed_outbound_hosts = ["random-data-api.fermyon.app"]
[component.hello-http-go.build]
command = "tinygo build -target=wasi -gc=leaking -no-debug -o main.wasm main.go"
ハンドラのHTTPリクエストの実装は以下のようになります。
Go標準のnet/http
ではなく、Spin SDKが提供するhttpモジュールを使用します。
import (
...
spinhttp "github.com/fermyon/spin/sdk/go/v2/http"
)
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
resp, _ := spinhttp.Get("https://random-data-api.fermyon.app/animals/json")
...
ビルドしたwasmモジュールを解析するとwasi-outbound-http
のrequest
という関数をインポートして使用しているのがわかります。
$ wasm-tools print main.wasm | grep outbound_http
(import "wasi-outbound-http" "request" (func $__wasm_import_wasi_outbound_http_request (;4;) (type 13)))
call $__wasm_import_wasi_outbound_http_request
これは名前にwasiと入っていて紛らわしいですが、WASI公式ではなくSpinで独自に定義している関数になります。
// This is a temporary workaround very similar to https://github.com/deislabs/wasi-experimental-http.
// Once asynchronous functions, streams, and the upstream HTTP API are available, this should be removed.
とあるように、現在のWASIではネットワーク関連の機能が不足しているため、Spin側でワークアラウンドとして提供しているものです。
こちらのロードマップによると次のWASI preview 2でHTTP、preview 3で非同期・ストリームが実装されることになっています。これらのAPIを含むWASIがリリースされ、各言語のツールチェーンがそれをサポートするようになれば、このような独自APIは少なくなっていくと予想されます。
こちらを参考にRustで実装すると以下のようにwasi:http/outgoing-handler
が使用されていました。言語ごとのSDKによって対応状況が違うようです。
$ wasm-tools print target/wasm32-wasi/release/send_outbound.wasm | grep outgoing-handler
(import "wasi:http/outgoing-handler@0.2.0-rc-2023-10-18" "handle" (func $_ZN8spin_sdk3wit4wasi4http16outgoing_handler6handle10wit_import17h4bc6be1fb9219efdE (;43;) (type 10)))
ハンドラの実装タイプ
Spinでは各プログラミング言語でハンドラ(関数)を実装しWebAssembly形式にコンパイルして実行しますが、ハンドラの実装方法には以下の2つのパターンがあります。
コンポーネントモデル
Spinはハンドラの実装にWebAssemblyコンポーネントモデルを利用しています。
以下のwitで定義されたインタフェースを利用して、トリガ -> ハンドラ -> outboundな各種アクセスの呼び出しを行っています。
前述の通り、Go SDKではこちらのwitが使われているようです。
WAGI
コンポーネントモデルをサポートしていないプログラミング言語では、CGIライクなWAGIという仕組みでハンドラを実装しています。
Spinランタイムが受けたHTTPリクエストをWasm(WASI)モジュールの環境変数や標準入力として渡し、Wasm(WASI)モジュールの標準出力をHTTPレスポンスとして返す仕組みです。
Spinアプリケーションのパッケージ管理
現状のSpinアプリケーションの配布方法にも触れておきます。
Spinアプリケーションはメタデータ等の各種設定とWebAssemblyバイナリ、画像などの静的アセットで構成されています。これらをパッケージング・配布するための仕組みとして以下の2つをサポートしています。
- Bindle
- 関連オブジェクトを「コレクション」として管理するための"Aggregate Object Storage"
- https://deislabs.io/posts/introducing-bindle/
- OCIレジストリ
BindleはFermyon Platform/Fermyon Cloudで現在利用されていますが、こちらのプロポーザルによると、まだ実験的なプロジェクトで、マネージド・サービスがない、スケーリングで未解決の問題がある、といった運用上の課題があるため、汎用的なアーティファクトにも対応し既に各クラウドプロバイダでも普及しているOCIレジストリを使用する方法をサポートしたようです。
OCIレジストリに対応することで、Docker Hub、GitHub Container Registry(GHCR)といった既存のマネージドサービスを利用できるため、Spinアプリケーションの配布が容易になります。
spin build
した後にspin registry push
でGHCRにpushする例です。 (参考: Spin Apps in Registries)
# イメージ作成
$ spin registry push ghcr.io/takuhiro/hello-http-rust:0.1.0
Pushing app to the Registry.... Pushed with digest sha256:f5d5a2959582bf2d5b59a027c7bce4a34a62ac5c459305ae8cfab9c870bf7cd6
# イメージを指定して起動
$ spin up -f ghcr.io/takuhiro/hello-http-rust:0.1.0
Serving http://127.0.0.1:3000
Available Routes:
hello-http-rust: http://127.0.0.1:3000 (wildcard)
イメージのマニフェストは以下のようになっていました。
{
"config": {
"digest": "sha256:aabedf0b094944b344cecbffcfa0a9172e905a5d156bdf67c4c6cbb6d27d1d92",
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 78
},
"layers": [
{
"digest": "sha256:6ef24b375b68caac830008302d83c787cce2c87afd86d98a7b622524bcd5298a",
"mediaType": "application/vnd.wasm.content.layer.v1+wasm",
"size": 2335413
},
{
"digest": "sha256:5f9ca81fda69965b4b2ee938e54bf7c230663d4327fe3806422f43f42a2d96c0",
"mediaType": "application/vnd.fermyon.spin.application.v1+config",
"size": 592
}
],
"schemaVersion": 2
}
Spinの設定とwasmモジュールが別々のレイヤで保存されているのがわかります。
Bytecode AllianceではWasmパッケージ用のレジストリプロトコルwargを策定中です。このプロジェクトが成熟してきたらSpinでもサポート予定であることが言及されています。
まとめ
後編ではWASI周りなど、Spinの個人的に気になる部分を紹介しました。
WASIのpreview 2、preview 3、wargなどの登場でSpinも更に変化していくと思いますが、サーバサイドでのWasm活用事例として参考になる部分が多いので今後もキャッチアップしていきたいと思います。