Docker+Wasm で WASM をコンテナとして実行する
Back to Top昨年10月に Docker+Wasm がテクニカルプレビューとして発表されました。WebAssembly ランタイムをターゲットとしてビルドされた WASM バイナリーを OCI 互換の環境で実行できるようにするものです。
Introducing the Docker+Wasm Technical Preview | Docker
ブログから、Docker+Wasm の実行イメージを引用します。
Docker Desktop では OCI ランタイム containerd を使用してコンテナイメージを管理・実行します。コンテナは runc などのさらに低レベルなライタイムにより実行されます。Docker+Wasm はこの runc にあたるレイヤーに WasmEdge を適用して WASM を実行します(図の右下)。Docker Desktop では containerd のサブプロセスである containerd-shim を介して runc の機能を利用します。WasmEdge と containerd を仲介するために containerd-wasm-shim が開発されました。
WasmEdge は WASM をエッジ環境で実行するためのランタイムです。
Docker+Wasm を利用するための設定
#Apple Silicon 用 Docker Desktop 4.16.2 で試しました。
Docker Desktop では containerd によるイメージ管理もまだベータ版であり、Docker+Wasm の機能を利用するには、containerd image store の機能を有効化する必要があります。
Docker Desktop の Settings > Features in development の Beta features で、Use containerd for pulling and storing images のチェックボックスをチェックして、Apply & restart で 反映します。
WASM を docker run で実行する
#公式ドキュメントにあるコマンドでサンプルの WASM アプリを実行してみます。--runtime オプションと --platform オプションで、それぞれ、io.containerd.wasmedge.v1 と wasi/wasm32 を指定しています。
docker run -dp 8080:8080 \
  --name=wasm-example \
  --runtime=io.containerd.wasmedge.v1 \
  --platform=wasi/wasm32 \
  michaelirwin244/wasm-example
  
WASI は Web 標準である WASM をブラウザ外で利用するための標準です。WASI のランタイムについては以下の記事でも取り上げています。
実行すると以下のようにイメージが取得され実行されました。
Unable to find image 'michaelirwin244/wasm-example:latest' locally
2a58923a21cb: Download complete 
130eeaf02640: Download complete 
e049f00c5289: Download complete 
f74ee7cf8049b69ef1279b8eab95e00366a11397bd26988f67ed6cea068e5dae
docker ps で確認するとポート8080で WASM のバイナリがサーバーとして起動しています。
docker ps
  
CONTAINER ID   IMAGE                          COMMAND              CREATED          STATUS          PORTS                    NAMES
f74ee7cf8049   michaelirwin244/wasm-example   "hello_world.wasm"   11 seconds ago   Up 10 seconds   0.0.0.0:8080->8080/tcp   wasm-example
コンテナイメージを確認すると 1.57MB と通常のイメージと比べると非常に小さいです。
docker image ls
  
REPOSITORY                     TAG       IMAGE ID       CREATED         SIZE
michaelirwin244/wasm-example   latest    2a58923a21cb   2 minutes ago   1.57MB
ブラウザで localhost:8080 に接続するとちゃんと動作しています。
このデモで使用されたのは、Docker Hub に push されている以下のイメージです。
michaelirwin244/wasm-example - Docker image | Docker Hub
ソースコードは以下のリポジトリにあります。Rust でシンプルな Web サーバーが実装されています。
GitHub - mikesir87/wasm-example
Dockerfile を見ると、Rust から WASM をビルドする部分と、scratch イメージに ビルド済みの WASM を COPY して最終的なイメージを作る部分からなるマルチステージビルドになっています。
# WASM ビルド
FROM  rust:1.64 AS buildbase
## ビルドステップ省略
RUN /root/.wasmedge/bin/wasmedgec target/wasm32-wasi/release/hello_world.wasm hello_world.wasm
# 最終的なイメージのビルド
FROM scratch
ENTRYPOINT [ "hello_world.wasm" ]
COPY --link --from=build /src/hello_world.wasm /hello_world.wasm
  
scratch イメージに WASM のバイナリファイルが置いてあるだけなので非常に小さいサイズのイメージになっています。
Docker+Wasm ではこの WASM だけのイメージから WASM を直接ロードし WasmEdge で実行します。従来の Docker 環境で (Wasmtime などの)WASM ランタイム入りのコンテナを実行するより、少ないオーバーヘッドで高速に実行できるということなのでしょう。
docker-compose で動かす
#docker-compose で動かす場合は以下のように platform と runtime を指定します。
- docker-compose.yml
 
services:
  app:
    image: michaelirwin244/wasm-example
    platform: wasi/wasm32
    runtime: io.containerd.wasmedge.v1
    ports:
      - 8080:8080
  
docker-compose で起動します。
docker-compose up -d
  
[+] Running 1/1
 ⠿ Container docker-wasm-app-1  Started 
起動されたアプリケーションを見てみます。docker run と同様に起動しています。
docker-compose ps
  
NAME                IMAGE                          COMMAND              SERVICE             CREATED              STATUS              PORTS
docker-wasm-app-1   michaelirwin244/wasm-example   "hello_world.wasm"   app                 About a minute ago   Up 7 seconds        0.0.0.0:8080->8080/tcp
通常のコンテナイメージとの相互運用
#公式ドキュメントでは、WASM と通常のコンテナが混在して実行できるサンプルも紹介されています。
このサンプルは、MySQL にデータを永続化する Web アプリケーションです。以下の3つのコンポーネントから構成されています。
- MariaDB のコンテナイメージ
 - HTML/JS のアセットを配信するための NGINX のコンテナイメージ
 - Rust で書かれ WASI/WASM でビルドされたマイクロサービス
 
マイクロサービス用の Dockerfile はやはり scratch イメージに WASM をコピーしただけのものです。
docker-compose.yml は以下のようになっています。NGINX は ports と volumes を指定。WASM のマイクロサービス(demo-microservice)は、platform と ruintime を指定して Dockerfile をビルドするようになっています。それ以外は通常のアプリと同様です。
services:
  client:
    image: nginx:alpine
    ports:
      - 8090:80
    volumes:
      - ./client:/usr/share/nginx/html
  server:
    image: demo-microservice
    platform: wasi/wasm
    build:
      context: .
    ports:
      - 8080:8080
    environment:
      DATABASE_URL: mysql://root:whalehello@db:3306/mysql
      RUST_BACKTRACE: full
    restart: unless-stopped
    runtime: io.containerd.wasmedge.v1
  db:
    image: mariadb:10.9
    environment:
      MYSQL_ROOT_PASSWORD: whalehello
  
docker-compose up すると Rust のマイクロサービスのビルドとイメージ作成・実行、及び、NGINX / MariaDB のイメージの pull・実行が行われます。
localhost:8090 に接続するとデモアプリ(何かの発注画面)が利用できるようになっています。登録した注文はDBに格納されます。
最後に
#以上、Docker Desktop に統合された WASM 実行環境 Docker+Wasm を動かしてみました。WASM ランタイム入りのイメージを用意することなく直接 Docker が WASM を実行してくれるので、オーバーヘッドもイメージサイズも小さく通常のコンテナとの相互運用も簡単でした。
Kubernetes の場合、WASM のワークロードを直接実行できる Krustlet という OSS が開発されています。これは kubelet に相当する実装で WASM を Pod として実行するソフトウェアです。
このように、コンテナの世界でも WASM が軽量なワークロードとして、既存のコンテナと共に実行されるのが普通になっていくのではないかと思いました。




