WasmバイナリをrunwasiとPodman/crun/WasmEdgeから実行してみた

2024.03.04

DockerやKubernetesで利用されているコンテナランタイム containerd からは、shim を通じてruncやAWS Fargateで利用されているFirecrackerなど様々なコンテナ実行方法をサポートします。

数年前にDocker社のプレスリリースで話題になったのように、WebAssembly(Wasm)アプリも実行することができ、Wasm向けshimから runwasi を呼び出すほか、runc 向け shimから Wasm対応した crun を 呼び出す事もできます。

※ 図はIntroducing the Docker+Wasm Technical Preview | Docker から

本記事では、両ケースに対応する containerd をインストールしたあと、runwasi 方式とPodman&crun のそれぞれで実行します。

検証環境

  • Ubuntu 22.04 x86(Amazon EC2を利用) 30GiB以上のストレージを推奨
  • containerd : 1.7.13
  • crun : 1.14.4.0.0.0.10-64ee
  • wasmedge : 0.13.5
  • podman : 3.4.4

事前準備

Ubuntu を用意

今回は Linux 上で Ubuntu 22.04(x86_64)を利用しました。 EBSのストレージサイズを 20GiBで起動したところ、ギリギリ容量不足になったため、30GiB程度は確保しておきましょう。

$ sudo apt update

containerd を用意

次に高レベルランタイムの containerd をインストールします。

GitHub レポジトリ second-state/wasmedge-containers-examples に containerd と Wasm(wasmedge)対応したcrunをインストールするスクリプトがあります。 containerd のバージョンだけ調整して流用します。

スクリプト内では、containerd のランタイムをデフォルトの runc から crun に変更するなどしています。 詳細はシェルスクリプトをご確認ください。

$ wget  https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/containerd/install.sh

# バージョン変更
$ diff -u orig-install.sh install.sh
--- orig-install.sh	2024-03-03 04:35:06.574460659 +0000
+++ install.sh	2024-03-03 04:35:16.262457855 +0000
@@ -20,7 +20,7 @@

 echo -e "Starting installation ..."
 sudo apt update
-export VERSION="1.5.7"
+export VERSION="1.7.13"
 echo -e "Version: $VERSION"
 echo -e "Installing libseccomp2 ..."
 sudo apt install -y libseccomp2

$ bash install.sh
...

# デフォルトランタイムを変更
$ grep crun /etc/containerd/*
/etc/containerd/config.toml:      default_runtime_name = "crun"
/etc/containerd/config.toml:    runtime = "crun"
/etc/containerd/config.toml.rej:+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun]
/etc/containerd/config.toml.rej:+          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun.options]
/etc/containerd/config.toml.rej:+            BinaryName = "crun"


$ ls -1 /usr/local/bin/
containerd
containerd-shim
containerd-shim-runc-v1
containerd-shim-runc-v2
containerd-stress
crictl
critest
crun
ctd-decoder
ctr
wasmedge
wasmedgec

crun のバージョン情報に Wasm 対応している旨も出力されていますね。

$ /usr/local/bin/crun -v
crun version 1.14.4.0.0.0.10-64ee
commit: 64ee22ce09e2879eaf346fd3bd03806a64b4acd6
rundir: /run/user/1000/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +WASM:wasmedge +YAJL

containerd から hello-world コンテナを動かしてみましょう

$ sudo ctr images pull docker.io/library/hello-world:latest
$ sudo ctr run --rm docker.io/library/hello-world:latest hello1

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

成功です。

Rustをインストール

次に Rust をインストールします。

Wasmバイナリの作成や関連プログラムのビルドのために必要です

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ source "$HOME/.cargo/env"

WasmEdge からWasmバイナリを実行

WasmはもともとWebブラウザ上で実行することを想定して開発されました。その後、Wasmをブラウザの外でも実行するようなユースケースが増え、Wasmを様々な環境で汎用的に実行できるように策定されたのがWebAssembly System Interface(WASI)です。

WasmEdge はそのような WASI ランタイムの一つであり、Linux 上でWasmバイナリを実行できます。

Rust の hello-world プログラムをWasmをターゲットにコンパイルし、WasmEdge から実行してみましょう。

$ cargo new hello && cd hello

$ cat src/main.rs
fn main() {
    println!("Hello, world!");
}

$ rustup target add wasm32-wasi

$ cargo build --target wasm32-wasi
   Compiling hello v0.1.0 (/home/ubuntu/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s

$ cp target/wasm32-wasi/debug/hello.wasm .

$ file hello.wasm
hello.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

$ wasmedge hello.wasm
Hello, world!

無事成功しました。

runwasi からWasmバイナリを実行

runwasi は GitHub の containerd/runwasi レポジトリで管理されています。

同レポジトリ内の ./scripts/setup-linux.sh で依存プログラムをインストールし、 build & make でインストールします。

$ export RUNWASI_VERSOIN=0.4.0
$ wget https://github.com/containerd/runwasi/archive/refs/tags/containerd-shim-wasm/v${RUNWASI_VERSOIN}.tar.gz
$ tar zxfv v${RUNWASI_VERSOIN}.tar.gz
$ cd runwasi-containerd-shim-wasm-v${RUNWASI_VERSOIN}/
$ ./scripts/setup-linux.sh
$ make build
$ sudo make install

Wasmランタイムを提供する WasmEdge がテスト用の Wasmイメージを用意しているので活用します。

containerd 実行時にWasm向けランタイムを指定するだけです。

$ sudo ctr images pull docker.io/wasmedge/example-wasi:latest
...

$ sudo ctr run --rm \
  --runtime=io.containerd.wasmedge.v1 \
  docker.io/wasmedge/example-wasi:latest example-wasi1
Random number: -2130467570
Random bytes: [149, 201, 92, 128, 17, 222, 132, 6, 106, 157, 200, 133, 23, 225, 22, 217, 92, 195, 246, 230, 143, 184, 60, 112, 170, 97, 2, 63, 230, 67, 195, 42, 56, 102, 16, 12, 90, 230, 97, 157, 233, 171, 24, 236, 237, 177, 126, 13, 39, 82, 50, 118, 56, 20, 88, 239, 119, 125, 230, 205, 247, 61, 218, 97, 121, 152, 158, 160, 15, 205, 132, 207, 145, 78, 184, 246, 123, 64, 213, 239, 19, 230, 104, 46, 11, 60, 198, 187, 124, 221, 211, 239, 33, 5, 167, 174, 164, 165, 167, 214, 4, 156, 74, 76, 58, 25, 27, 24, 58, 55, 246, 102, 161, 43, 53, 71, 157, 3, 31, 114, 232, 243, 239, 37, 171, 44, 68, 200]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The args are as follows.
/wasi_example_main.wasm
File content is This is in a file

Podman/crun/WasmEdgeからWasmバイナリを実行

最後に、Podman から Wasm対応した crun を呼び出し Wasmバイナリを実行します。

内容的には、次の @utam0k さんの発表スライドと同等と思われます(発表を直接聞いたわけではありません)

Podman with WebAssembly - Speaker Deck

$ sudo apt install podman

$ podman -v
podman version 3.4.4

containerd と同じく Podman も低レベルランタイムにデフォルトでは runc を利用するため、Wasm対応した crun ランタイムで呼び出します。

$ podman --runtime /usr/local/bin/crun info | grep -A 9 ociRuntime
  ociRuntime:
    name: /usr/local/bin/crun
    package: Unknown
    path: /usr/local/bin/crun
    version: |-
      crun version 1.14.4.0.0.0.10-64ee
      commit: 64ee22ce09e2879eaf346fd3bd03806a64b4acd6
      rundir: /run/user/1000/crun
      spec: 1.0.0
      +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +WASM:wasmedge +YAJL

podman から hello-world イメージを呼び出します

$ podman run --rm hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

次に、先程の Rust でコンパイルした "rust.wasm" ファイルを利用したDockerファイルを用意します。

Dockerfile

FROM scratch
COPY hello.wasm /
ENTRYPOINT ["/hello.wasm"]

次に Wasmイメージであるというアノテーションとともにイメージをビルドします。

$ buildah build \
  --annotation "module.wasm.image/variant=compat" \
  -t hello-wasm .
...

$ podman images
REPOSITORY                       TAG         IMAGE ID      CREATED        SIZE
localhost/hello-wasm             latest      7af43b9e2ab4  8 seconds ago  1.74 MB
docker.io/library/hello-world    latest      d2c94e258dcb  10 months ago  26.3 kB
...

crun ランタイムを指定して起動します

$ podman \
  --runtime /usr/local/bin/crun run \
  --rm localhost/hello-wasm
Hello, world!

無事成功です。

コンテナランタイム is 何?

Amazon ECSのようなフルマネージドのコンテナ実行環境を利用し、アプリケーションをコンテナ化することにていると、コンテナランタイムを意識することはあまりありません。

そんな方にぴったりなのが、本日 2024/03/04 に発売される『[改訂新版]イラストでわかるDockerとKubernetes』です。

著者の徳永 航平氏はコンテナランタイムやイメージなどの開発に従事し、Container Runtime Meetupも運営されています。container2wasmの作者としてもおなじみですね。

約3年前に出版された初版に対するアマゾンの111件のレビューが、本書の素晴らしさを物語っています。

普段、触っている技術領域の一つ下のレイヤーにも興味を向けると、より深く対象を理解できるようになるかもしれません。

参考