DockerでWASMを動かそう #docker #webassembly #wasm #wasi
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
WebAssembly (WASM)を使うと、ウェブブラウザ上でJavaScript以外の言語を動作させることができます。さらに WASI (WebAssembly System Interface) という仕組みを使うと、WASM をブラウザ外で動かすことができます。WASIは「コンテナの次」となるポータブルでセキュアな仕組みとして注目を集めています。本稿では Docker Desktop を使わずに、DockerでWASMを動かしてみます。
注意: 本稿の内容は実験であり、本番環境では利用できません。また、開発中のソフトウェアを多数利用しているため、今後動作が変更される可能性があります。
Dockerの準備
DockerでWASMを動かすには、containerdイメージストアへの統合 が必要です。ここでは
- Rocky Linux 9 をインストールした仮想マシン
- Docker 24.0.0-beta.2 のインストール
- Stargz Snapshotter のインストールと設定
が完了しているものとします。
WasmEdgeの準備
WasmEdge とは、WASI アプリケーションを実行できるスタンドアローンランタイムの一種です。過去記事「RubyでWebAssemblyを試してみよう」で紹介した Wasmtime や Wasmer の仲間です。
本稿で数あるスタンドアローンランタイムからWasmEdgeを選択したのは、Docker Desktop に内蔵 されているからです。
では WasmEdge のインストールを行いましょう。
$ curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash Using Python: /usr/bin/python3 cat: /etc/lsb-release: No such file or directory Exception on process, rc= 1 output= b'' ['cat /etc/lsb-release | grep RELEASE'] /bin/sh: line 1: lsb_release: command not found Compatible with current configuration Running Uninstaller /usr/bin/which: no wasmedge in (/home/vagrant/.local/bin:/home/vagrant/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin) WARNING - Uninstaller did not find previous installation WARNING - SHELL variable not found. Using bash as SHELL shell configuration updated Downloading WasmEdge |============================================================|100.02 %Downloaded Installing WasmEdge WasmEdge Successfully installed Run: source /home/vagrant/.bashrc $
どうやらホームディレクトリ以下にインストールされるようです。
$ find .wasmedge/ .wasmedge/ .wasmedge/plugin .wasmedge/plugin/libwasmedgePluginWasmEdgeProcess.so .wasmedge/env .wasmedge/include .wasmedge/include/wasmedge .wasmedge/include/wasmedge/int128.h .wasmedge/include/wasmedge/enum_types.h .wasmedge/include/wasmedge/enum.inc .wasmedge/include/wasmedge/enum_errcode.h .wasmedge/include/wasmedge/enum_configure.h .wasmedge/include/wasmedge/version.h .wasmedge/include/wasmedge/wasmedge.h .wasmedge/lib .wasmedge/lib/libwasmedge.so.0.0.1 .wasmedge/lib/libwasmedge.so.0 .wasmedge/lib/libwasmedge.so .wasmedge/bin .wasmedge/bin/wasmedge .wasmedge/bin/wasmedgec $
過去記事「RubyでWebAssemblyを試してみよう」で作った hello.wasm を WasmEdge で動かしてみましょう。
$ source /home/vagrant/.bashrc $ wasmedge hello.wasm /src/hello.rb Hello, world! $
Wasmtime や Wasmer 同様に動作しました。
runwasiの準備
runwasiとは、containerd から WASI アプリケーションを実行するための仕組みです。containerd から WASI アプリケーションを動かせるということは、containerd を利用している Docker や Kubernetes で WASI アプリケーションを動かせるということです。
runwasi は wasmtime と WasmEdge に対応していますが、本稿では前述の通り WasmEdge を使います。
WasmEdgeのインストール
先程ホームディレクトリにインストールした WasmEdge ライブラリをシステムディレクトリに配置します。
$ sudo cp -a .wasmedge/lib/libwasmedge.so* /usr/local/lib/ $ sudo -E sh -c 'echo "/usr/local/lib" > /etc/ld.so.conf.d/libwasmedge.conf' $ sudo ldconfig $
runwasiのビルド
runwasi Gitレポジトリをクローンし、 containerd-shim-wasm/v0.1.2 タグをチェックアウトします。
$ git clone https://github.com/containerd/runwasi $ cd runwasi $ git checkout containerd-shim-wasm/v0.1.2 $
ここでビルド…といきたいのですが、RockyLinux 9 収録の Cargo が 1.62.1 であり、1.64.0 の機能を使っている runwasi はビルドできません。
$ cargo test -- --nocapture error: failed to load manifest for workspace member `/home/vagrant/runwasi/crates/containerd-shim-wasm` Caused by: failed to parse manifest at `/home/vagrant/runwasi/crates/containerd-shim-wasm/Cargo.toml` Caused by: feature `workspace-inheritance` is required The package requires the Cargo feature called `workspace-inheritance`, but that feature is not stabilized in this version of Cargo (1.62.1). Consider trying a newer version of Cargo (this may require the nightly release). See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#workspace-inheritance for more information about the status of this feature. $ rpm -q cargo cargo-1.62.1-1.el9.x86_64 $
そこで、Dockerを使ってビルドします。
$ docker image build -t runwasi . [+] Building 640.7s (23/23) FINISHED => [internal] load .dockerignore 0.2s => => transferring context: 181B 0.0s => [internal] load build definition from Dockerfile 0.2s => => transferring dockerfile: 2.94kB 0.0s => resolve image config for docker.io/docker/dockerfile:1 3.0s => docker-image://docker.io/docker/dockerfile:1@sha256:39b85bbfa7536a5fe 3.0s => => resolve docker.io/docker/dockerfile:1@sha256:39b85bbfa7536a5feceb7 0.2s => => sha256:39b85bbfa7536a5feceb7372a0817649ecb2724562a 8.40kB / 8.40kB 0.0s => => sha256:966d40f9ba8366e74c2fa353fc0bc7bbc167d2a0f3ad242 482B / 482B 0.0s => => sha256:dbdd11720762ad504260c66161c964e59eba06b95a7 2.90kB / 2.90kB 0.0s => => sha256:a47ff7046597eea0123ea02817165350e3680f750 11.55MB / 11.55MB 1.4s => => extracting sha256:a47ff7046597eea0123ea02817165350e3680f75000dc5d6 0.6s => [internal] load metadata for docker.io/library/rust:1.64 3.9s => [internal] load metadata for docker.io/tonistiigi/xx:1.1.0 4.3s => [internal] load build context 0.7s => => transferring context: 317.55kB 0.0s => [base 1/3] FROM docker.io/library/rust:1.64@sha256:53ded1c919ea0dc23 72.8s => => resolve docker.io/library/rust:1.64@sha256:53ded1c919ea0dc23be959e 0.8s => => sha256:53ded1c919ea0dc23be959e28037238d8c321cd88fbac04 988B / 988B 0.0s => => sha256:44ba8b8d8a2993694926cc847e1cce27937550c2e9e 1.59kB / 1.59kB 0.0s => => sha256:dd3f19acb68106e1b124cade911fc4c41ee098533ba 6.42kB / 6.42kB 0.0s => => sha256:de4a4c6caea8801bb0b7377e10220a914da403bc93f 5.16MB / 5.16MB 1.1s => => sha256:17c9e6141fdb3387e5a1c07d4f9b6a05ac1498e9 55.05MB / 55.05MB 13.9s => => sha256:4edced8587e6c18412817019074f5e04a8ede4e2f 10.88MB / 10.88MB 5.9s => => sha256:a7969cffbf46e6a91291fd76b19ecbe93c03ea4d 54.59MB / 54.59MB 12.7s => => sha256:74fbfde6af91271fb88f0a1716224dcce5c0eb 196.87MB / 196.87MB 43.2s => => sha256:9253f8c367c00e02371372b42faef03795cace 148.95MB / 148.95MB 42.3s => => extracting sha256:17c9e6141fdb3387e5a1c07d4f9b6a05ac1498e96029fa3e 4.3s => => extracting sha256:de4a4c6caea8801bb0b7377e10220a914da403bc93fa7966 0.5s => => extracting sha256:4edced8587e6c18412817019074f5e04a8ede4e2fc89d06a 0.5s => => extracting sha256:a7969cffbf46e6a91291fd76b19ecbe93c03ea4ded0d1404 4.9s => => extracting sha256:74fbfde6af91271fb88f0a1716224dcce5c0ebead360994 14.4s => => extracting sha256:9253f8c367c00e02371372b42faef03795cace79aed82d8 11.4s => [xx 1/1] FROM docker.io/tonistiigi/xx:1.1.0@sha256:38b94c9b7d64f3154 17.3s => => resolve docker.io/tonistiigi/xx:1.1.0@sha256:38b94c9b7d64f31544c9f 0.9s => => sha256:38b94c9b7d64f31544c9fd7a979fa856ab50b2b41cf 2.65kB / 2.65kB 0.0s => => sha256:44baf01e2b60ed9abdd47f1c8e6cf8916b70a48c93016dd 525B / 525B 0.0s => => sha256:156f8400f6eb0df86c13844ba62bf5c21fafda08f0c2f50 940B / 940B 0.0s => => sha256:9b3b2510e20ad4453410e380667d4f482b9dc5f8 14.84kB / 14.84kB 15.3s => => extracting sha256:9b3b2510e20ad4453410e380667d4f482b9dc5f82e3023eb 0.1s => [base 2/3] COPY --from=xx / / 14.1s => [base 3/3] RUN apt-get update -y && apt-get install --no-install-rec 28.5s => [build 1/10] RUN xx-apt-get install -y gcc g++ libc++6-dev zlib1g 209.5s => [build 2/10] RUN rustup target add $(xx-info march)-unknown-$(xx-inf 1.7s => [build 3/10] RUN < [build 4/10] WORKDIR /build/src 1.1s => [build 5/10] COPY --link crates ./crates 0.9s => [build 6/10] COPY --link Cargo.toml ./ 0.9s => [build 7/10] COPY --link Cargo.lock ./ 0.9s => [build 8/10] RUN --mount=type=cache,target=/usr/local/cargo/git/db 280.0s => [build 9/10] COPY scripts ./scripts 1.0s => [build 10/10] RUN --mount=type=cache,target=/usr/local/cargo/git/db 2.8s => [release 1/1] COPY --link --from=build /build/bin/* / 1.3s => exporting to image 1.3s => => exporting layers 1.2s => => writing image sha256:d7eb6526bf031c3d8e2940fb5f5a991381b6f3cb2ec2c 0.0s => => naming to docker.io/library/runwasi 0.0s $
Dockerイメージを分解し、中から必要ファイルを取り出します。
$ docker image save runwasi | tar xf - $ tar xvf 9ac116108171d9081f8d023d32618fdbbd2ff39307c35d209641960424a9f3b8/layer.tar containerd-shim-wasmedge-v1 containerd-shim-wasmedged-v1 containerd-shim-wasmtime-v1 containerd-shim-wasmtimed-v1 containerd-wasmedged containerd-wasmtimed wasi-demo-app $
この containerd-* が必要なファイルです。
runwasiのインストール
先程イメージから取り出した containerd-* を /usr/bin ディレクトリに配置します。
$ sudo cp containerd-* /usr/bin $ ls -l /usr/bin/containerd* -rwxr-xr-x. 1 root root 52181200 Mar 31 16:27 /usr/bin/containerd -rwxr-xr-x. 1 root root 7348224 Mar 31 16:27 /usr/bin/containerd-shim -rwxr-xr-x. 1 root root 9461760 Mar 31 16:27 /usr/bin/containerd-shim-runc-v1 -rwxr-xr-x. 1 root root 9482240 Mar 31 16:27 /usr/bin/containerd-shim-runc-v2 -rwxr-xr-x. 1 root root 6809232 Apr 20 03:38 /usr/bin/containerd-shim-wasmedged-v1 -rwxr-xr-x. 1 root root 9048000 Apr 20 03:38 /usr/bin/containerd-shim-wasmedge-v1 -rwxr-xr-x. 1 root root 6804984 Apr 20 03:38 /usr/bin/containerd-shim-wasmtimed-v1 -rwxr-xr-x. 1 root root 16672792 Apr 20 03:38 /usr/bin/containerd-shim-wasmtime-v1 -rwxr-xr-x. 1 root root 9449304 Apr 20 03:38 /usr/bin/containerd-wasmedged -rwxr-xr-x. 1 root root 17075216 Apr 20 03:38 /usr/bin/containerd-wasmtimed $
念のため、正しくWasmEdgeのライブラリを読み込んでいるか確認しておきましょう。
$ ldd /usr/bin/containerd-shim-wasmedge-v1 linux-vdso.so.1 (0x00007ffc9db07000) libwasmedge.so.0 => /usr/local/lib/libwasmedge.so.0 (0x00007fe1e1f9e000) libc.so.6 => /lib64/libc.so.6 (0x00007fe1e1d95000) /lib64/ld-linux-x86-64.so.2 (0x00007fe1e4fc7000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fe1e1d7a000) librt.so.1 => /lib64/librt.so.1 (0x00007fe1e1d75000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fe1e1d70000) libm.so.6 => /lib64/libm.so.6 (0x00007fe1e1c95000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fe1e1c8e000) libz.so.1 => /lib64/libz.so.1 (0x00007fe1e1c74000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fe1e1a4d000) $
問題ないようです。これでrunwasiの準備ができました。
DockerでWASMを動かす
では Running a Wasm application with docker run のサンプル WASM アプリケーションを実行してみましょう。
$ docker run -dp 8080:8080 \ --name=wasm-example \ --runtime=io.containerd.wasmedge.v1 \ --platform=wasi/wasm32 \ michaelirwin244/wasm-example 80e271150b189e77e06a91b526bffabb670d56290db4f715da611a537ed1b53c $
起動しました。curl コマンドで localhost:8080 を開いてみます。
$ curl localhost:8080 Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to you $
応答がありました! さらに指示に従ってみましょう。
$ curl -d 'Hello, world!' localhost:8080/echo Hello, world! $
ポストした Hello, world! をエコーバックしてきました。
見事、DockerでWASMを動かすことに成功しました。もし挙動がおかしい場合は docker や containerd を再起動してみたり、WasmEdgeのライブラリパスが正しいかなど確認してみてください。
WASMのDockerイメージの作成
では、Dockerイメージも作ってしまいましょう。過去記事「RubyでWebAssemblyを試してみよう」で作った hello.wasm を対象とします。新しくディレクトリを作り、その中に hello.wasm と次のDockerfileを配置します。
FROM scratch COPY hello.wasm /hello.wasm ENTRYPOINT [ "hello.wasm", "/src/hello.rb" ]
ではビルドしてみましょう。この際 --platform wasi/wasm32 というオプションをつける必要があります。
$ docker buildx build --platform wasi/wasm32 -t daihiguchi/hellowasm . [+] Building 5.9s (5/5) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 182B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build context 0.9s => => transferring context: 55.94MB 0.9s => [1/1] COPY hello.wasm /hello.wasm 0.3s => ERROR exporting to image 4.5s => => exporting layers 4.5s => => exporting manifest sha256:d5fc8e593906ee2180288adbdfd2ccbd427451e3 0.0s => => exporting config sha256:cb519d8b3e025a185677f07bd32acdbdb9e91c693f 0.0s => => exporting attestation manifest sha256:0c7ec67ffec4977be93c27bb2c97 0.0s => => exporting manifest list sha256:6f6933e0f8dc102e51c1c7036060a20d325 0.0s => => naming to docker.io/daihiguchi/hellowasm:latest 0.0s => => unpacking to docker.io/daihiguchi/hellowasm:latest 0.0s ------ > exporting to image: ------ ERROR: failed to solve: no match for platform in manifest sha256:6f6933e0f8dc102e51c1c7036060a20d325efce8c69dce85628aecae5ce8ebd5: not found $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE daihiguchi/hellowasm latest 6f6933e0f8dc 25 seconds ago 18MB $
なぜかエラーになってしまっていますが、イメージは作成できているというおかしな状態になっています。
それでも起動してみますが、
$ docker container run --rm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 daihiguchi/hellowasm Unable to find image 'daihiguchi/hellowasm:latest' locally docker: Error response from daemon: failed to resolve reference "docker.io/daihiguchi/hellowasm:latest": docker.io/daihiguchi/hellowasm:latest: not found. See 'docker run --help'. $
失敗してしまいました。Dockerとcontainerdのイメージストアを統合したはずですが、うまく動いていないのでしょうか?
ここでDocker Hubに一旦プッシュしてみます。
$ docker login Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. Username: daihiguchi Password: WARNING! Your password will be stored unencrypted in /home/vagrant/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded $ docker image push daihiguchi/hellowasm Using default tag: latest 0c7ec67ffec4: Pushed 6f6933e0f8dc: Pushed cb519d8b3e02: Pushed 69e8907cb928: Pushed 96c9c35c2bbe: Pushed 24e93cf4a79a: Pushed d5fc8e593906: Pushed $
プッシュに成功しました。Docker HubのウェブUIでも確認できています。
再度実行してみましょう。
$ docker container run --rm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 daihiguchi/hellowasm Unable to find image 'daihiguchi/hellowasm:latest' locally 6f6933e0f8dc: Exists d5fc8e593906: Exists cb519d8b3e02: Exists 69e8907cb928: Exists Hello, world!
今度は起動に成功しました! ただ、コンテナが終了せずに強制的に kill することになってしまいましたが…とにかく目的は達成です。
まとめ
本稿ではさまざまなソフトウェアを組み合わせてDocker Desktopとほぼ同等の環境を作成し、Docker上でWASMを動かしてみました。
まだまだベータ版・開発中のソフトウェアや機能ばかりで構築するのも一苦労ですが、成熟が進めんで手軽になればWASMが動いているDocker環境というのも増えていくと思います。クリエーションラインでは引き続き WebAssmbly と Docker について調査していきたいと思います。