fbpx

DockerでWASMを動かそう #docker #webassembly #wasm #wasi

WebAssembly (WASM)を使うと、ウェブブラウザ上でJavaScript以外の言語を動作させることができます。さらに WASI (WebAssembly System Interface) という仕組みを使うと、WASM をブラウザ外で動かすことができます。WASIは「コンテナの次」となるポータブルでセキュアな仕組みとして注目を集めています。本稿では Docker Desktop を使わずに、DockerでWASMを動かしてみます。

注意: 本稿の内容は実験であり、本番環境では利用できません。また、開発中のソフトウェアを多数利用しているため、今後動作が変更される可能性があります。

Dockerの準備

DockerでWASMを動かすには、containerdイメージストアへの統合 が必要です。ここでは

が完了しているものとします。

WasmEdgeの準備

WasmEdge とは、WASI アプリケーションを実行できるスタンドアローンランタイムの一種です。過去記事「RubyでWebAssemblyを試してみよう」で紹介した WasmtimeWasmer の仲間です。
本稿で数あるスタンドアローンランタイムから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 について調査していきたいと思います。

Author

Chef・Docker・Mirantis製品などの技術要素に加えて、会議の進め方・文章の書き方などの業務改善にも取り組んでいます。「Chef活用ガイド」共著のほか、Debian Official Developerもやっています。

Daisuke Higuchiの記事一覧

新規CTA