WasmEdge でサーバープログラム

この記事では WebAssembly の Runtime である WasmEdge について記載します。

## 前書き

私は以前から定期的に WebAssembly(以下 WASM) でできることを確認しているのですが、 その中でも WASI(WebAssembly System Interface) の [WASI Sockets](https://github.com/WebAssembly/wasi-sockets) の実装に興味があり調べています。

単純に Echo Server のようなサーバープログラムを書きたいという理由なのですが、そのためにはソケットが必要です。上記のプロポーザルは、[WebAssembly W3C Process](https://github.com/WebAssembly/meetings/blob/main/process/phases.md) で定められた 5 つのフェーズのうち、最初の段回である `Phase 1 Feature Proposal` の状態です。今後も仕様に変更や改善が入る状態で、まだまだ実装されるまでの道のりが長そうです。WASI 対応の WASM をビルドできる Rust でも、v1.66 の段階では一部の実装のみとなっています。

https://github.com/rust-lang/rust/blob/1.66.0/library/std/src/sys/wasi/net.rs

ただ、少しずつですが実装もされています。  
[feat: implement TcpStream shutdown for wasm32-wasi by haraldh · Pull Request #104811 · rust-lang/rust](https://github.com/rust-lang/rust/pull/104811)

こうした状況ですので、2022 年の段階では WASI Sockets の検証はまだ早そうです。
ただし、言語ではなく Runtime で Socket を実装し、サーバープログラミングを実現しているものもあります。CNCF だと Sandbox プロジェクトに [WasmEdge](https://wasmedge.org/) と [wasmCloud](https://wasmcloud.com/) があります。最近は WASM の Runtime がいろいろ作成されているため、他にもありそうですが今回は気軽に試せる WasmEdge を利用し、サーバープログラムが起動できるか確認していきます。


## WasmEdge

前書きに書いた通り、WasmEdge は WASI Sockets を実装・提供しています。その他にも WASI Proposals である [Crypto](https://github.com/WebAssembly/wasi-crypto) や [Machine Learning (wasi-nn)](https://github.com/WebAssembly/wasi-nn)、WebAssembly Proposals の [Threads](https://github.com/webassembly/threads) なども提供されています。色々なプロポーザルを試したい方には良い Runtime です。

- [Supported WASM And WASI Proposals - WasmEdge Runtime](https://wasmedge.org/book/en/features/proposals.html)

他にも、[キーバリューストレージとの連携](https://github.com/second-state/wasmedge-storage)（サンプルでは RocksDB が使用）や [Tensorflow](https://github.com/second-state/wasmedge-tensorflow) との連携など実用的なアプリケーションで必要となる機能も提供されています。


ドキュメントにはユースケースごとの利用例も記載されていますので気になる方は、公式のドキュメントを確認してみてください。
[WasmEdge Use Cases - WasmEdge Runtime](https://wasmedge.org/book/en/use_cases.html)

では、実際にビルドして起動できるか確認していきます。

## 開発マシーン上の wasmedge コマンドで起動する

まずはローカル環境の macOS で、Rust を使ったサンプルプロジェクトの Echo Server を起動してみます。
Rust と WasmEdge は公式のドキュメントに沿ってインストールとセットアップをします。

- [Rust のインストールとセットアップ](https://www.rust-lang.org/tools/install)
- [WasmEdge のインストールとセットアップ](https://wasmedge.org/book/en/quick_start/install.html)

セットアップが完了すると `rustup` コマンドが利用できるので、以下のコマンドで WASM をビルドできるように設定を追加します。

```sh
$ rustup target add wasm32-wasi
```

次にサンプルプロジェクトをクローンし、ビルドします。

```sh
$  git clone git@github.com:second-state/wasmedge_wasi_socket.git
Cloning into 'wasmedge_wasi_socket'...
remote: Enumerating objects: 12139, done.
remote: Counting objects: 100% (2323/2323), done.
remote: Compressing objects: 100% (508/508), done.
remote: Total 12139 (delta 1776), reused 2176 (delta 1680), pack-reused 9816
Receiving objects: 100% (12139/12139), 6.42 MiB | 4.79 MiB/s, done.
Resolving deltas: 100% (11332/11332), done.

$ cd wasmedge_wasi_socket/examples/http_server

$ cargo build --target wasm32-wasi --release
    ...
    Finished release [optimized] target(s) in 6.57s
```

ビルドに成功すると `http_server.wasm` というファイルが作成されるので、このファイルを `wasmedge` コマンドで起動します。

```sh
$ ls -al target/wasm32-wasi/release/http_server.wasm
.rwxr-xr-x 2.3M watawuwu 20 Dec 16:15 target/wasm32-wasi/release/http_server.wasm

# 以下のように実行したいところですが、v0.11.2 では macOS の実装がされておらず、リクエストすると次のようなメッセージが出力されてしまいます。
#   Error: Os { code: 52, kind: Unsupported, message: "Function not implemented" }
$ wasmedge target/wasm32-wasi/release/http_server.wasm

# 次のバージョンでは実装されているようなので、それまでは docker で確認します。
$ docker run -it --platform=linux/amd64 --rm -v $PWD:/app -w /app -p 1234:1234 wasmedge/slim:0.11.2 wasmedge target/wasm32-wasi/release/http_server.wasm
new connection at 1234
```

curl で 1234 ポートにリクエストすると期待通りリクエストした内容がレスポンスされました :tada:

```sh
$ curl -d "hello world" -X POST http://127.0.0.1:1234
echo: hello world
```

## Docker+Wasm で起動する

では次に Docker の Runtime を `wasmedge` に変更して起動します。先ほども macOS では対応していないため一時的に Docker で起動していましたが、[containerd image store](https://docs.docker.com/desktop/containerd/) 機能を有効にしている場合、Runtime は containerd + runc が利用されており、コンテナ上で `wasmedge` コマンドが実行されていました。

```sh
# 先ほどの実行方法
docker run -it --platform=linux/amd64 --rm -v $PWD:/app -w /app -p 1234:1234 wasmedge/slim:0.11.2 wasmedge target/wasm32-wasi/release/http_server.wasm
```

一方、[Docker Desktop v4.15](https://www.docker.com/blog/docker-desktop-4-15-improved-usability-and-performance/) から Beta として利用できる [Docker+Wasm](https://docs.docker.com/desktop/wasm/) は `containerd-shim + runc` の代わりに `containerd-wasm-shim + wasmedge` が利用されます。[詳細は公式ブログ](https://www.docker.com/blog/docker-wasm-technical-preview/)に図が記載されているので気になるかたはチェックしてください。
これにより WASM 用にビルドされたイメージを docker で起動できます。

では実際に WASM 用のイメージをビルドし起動していきます。  
この機能を試すには macOS の場合、Docker Desktop for Mac を v4.15 以上にアップデートし、[containerd image store](https://docs.docker.com/desktop/containerd/) 機能を有効にしてください。

```sh
# WASM 用の Dockerfile
# WASM の場合 FROM 部分は不要だと思いますので、将来的には専用の構文ができる気がします
$ cat <<EOF > Dockerfile
FROM scratch
COPY target/wasm32-wasi/release/http_server.wasm /http_server.wasm
ENTRYPOINT [ "http_server.wasm" ]
EOF

# buildx でビルド
$ docker buildx build --platform wasi/wasm32 -t wasm-echo-server:0.1.0 .
[+] Building 0.4s (5/5) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 151B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load build context
 => => transferring context: 2.26MB
 => [1/1] COPY target/wasm32-wasi/release/http_server.wasm /http_server.wasm
 => exporting to image
 => => exporting layers
 => => exporting manifest sha256:0121b30c21900c0410d7a3eb7ae28295d204cd9654713a1f8bf2d7ceb453a1bf
 => => exporting config sha256:65758e05228c974c51704f0e33ced2913e7ab2a29189a91b382b639708750c02
 => => naming to docker.io/watawuwu/wasm-echo-server:0.1.0
 => => unpacking to docker.io/watawuwu/wasm-echo-server:0.1.0
```

作成されたコンテナイメージは Architecture が wasm32、 OS が wasi となります。

```sh
$ docker inspect wasm-echo-server:0.1.0  | grep -A 3 "Architecture"
        "Architecture": "wasm32",
        "Os": "wasi",
        "Size": 558176,
        "VirtualSize": 558176,
```

コンテナイメージを起動するには `runtime` に `io.containerd.wasmedge.v1`、`platform` に `wasi/wasm32` を指定して起動します。

```sh
$ docker run -i -p 1234:1234 \
  --runtime=io.containerd.wasmedge.v1 \
  --platform=wasi/wasm32 \
  wasm-echo-server:0.1.0
new connection at 1234
```

Runtime を変えたバージョンでも疎通が確認できました :tada:

```sh
$ curl -d "runc to wasmedge" -X POST http://127.0.0.1:1234
echo: runc to wasmedge
```

他にも WasmEdge の公式ドキュメントでは [Kubernetes(kind)](https://wasmedge.org/book/en/use_cases/kubernetes/kubernetes/kind.html) での起動方法も記載されています。
今後は Kubernetes でも RuntimeClass リソースなどを活用して一部のコンテナを WASM に置き換えるなどの試みができると楽しそうです。

## 最後に

前書きの通りまだ提案段階にある WASI Sockets です。この機能が実装されるとサーバープログラミングが実現でき WASM でできることがぐっと広がります。今回試した WasmEdge のように Runtime 側で先行実装された内容がプロポーザルにフィードバックされ好循環になると GA までの期間も短くなりそうなので、他の Runtime にも試験機能として実装して欲しいですね。
もちろん、実用的なアプリケーションを書くためには Socket だけでなく、他のプロポーザル（Threadなど） や Runtime、ライブラリなど様々な対応が必要となり時間がかかりそうです。

来年も色々な動きがあると思うので引き続き、WASI Sockets の実装や関連 OSS をトラッキングしていきたいと思います！