Docker+Wasmを試してみた(Docker Engineのビルドから)

2022/11/08に公開

はじめに

DockerでWasmを実行できるDocker+Wasmがテクノロジープレビューとして発表されました。
https://www.docker.com/blog/docker-wasm-technical-preview/
公式ドキュメントにはDocker Desktopのバイナリしか公開されていませんでしたが、GitHubリポジトリにWasm対応のDocker Engineをインストールする方法がありました。
https://github.com/chris-crone/wasm-day-na-22/tree/8e61f3e5e981c1e9a86687214ee1d45b2630bbb0/server

個人アカウントのリポジトリですが、Docker社の人がCloud Native Wasm Day North America向けにつくったリポジトリのように思われます。

本記事は、上記の方法でWasm対応のDocker Engineをインストールして試した結果のまとめになります。

環境

  • ホストOS:Ubuntu 22.04.1

作業ログ

以下を参考にインストールを進めます。
https://github.com/chris-crone/wasm-day-na-22/tree/8e61f3e5e981c1e9a86687214ee1d45b2630bbb0/server

Docker+Wasmのインストール

前提条件

以下がインストールされていること

  • Docker
  • Docker Compose

本記事では上記ソフトウェアのインストールは割愛します。

nakkoh@dockerwasm:~$ docker -v
Docker version 20.10.21, build baeda1f
nakkoh@dockerwasm:~$ docker compose version
Docker Compose version v2.12.2

wasmEdgeのインストール

以下の図は、Docker+Wasmのコンポーネントを表しています。

https://www.docker.com/wp-content/uploads/2022/10/docker-containerd-wasm-diagram.png.webp

図右下の緑の箱がwasmを実行するためのコンポーネントで、wasmEdgeとwasmEdge用のcontainerd shimが必要です。

nakkoh@dockerwasm:~$ curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all -p /usr/local

...

var? wasmedge-tensorflow-lite
0.11.2 0.11.2
Installation of wasmedge-tensorflow-lite-0.11.2 successfull
WasmEdge binaries accessible

containerd-wasm-shimのインストール

ちなみにですが、containerd-wasm-shimはkrustletの開発元であるDeis Labsが開発しているようです。 shimをWasmEdgeの開発元であるSecond Stateがフォークしたもののようです。

https://deislabs.io/posts/helpful-webassembly-resources/
https://github.com/second-state/runwasi

nakkoh@dockerwasm:~$ wget https://github.com/second-state/runwasi/releases/download/v0.3.2/containerd-shim-wasmedge-v1-v0.3.2-linux-amd64.tar.gz

...

nakkoh@dockerwasm:~$ tar -zxvf containerd-shim-wasmedge-v1-v0.3.2-linux-amd64.tar.gz 
containerd-shim-wasmedge-v1
nakkoh@dockerwasm:~$ sudo mv containerd-shim-wasmedge-v1 /usr/local/bin/

dockerdのビルド & インストール

dockerdのビルド

nakkoh@dockerwasm:~$ git clone https://github.com/rumpl/moby.git && cd moby && git checkout wasmedge && make binary
Cloning into 'moby'...
remote: Enumerating objects: 345026, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 345026 (delta 0), reused 3 (delta 0), pack-reused 345019
Receiving objects: 100% (345026/345026), 184.11 MiB | 5.35 MiB/s, done.
Resolving deltas: 100% (229111/229111), done.
Branch 'wasmedge' set up to track remote branch 'wasmedge' from 'origin'.
Switched to a new branch 'wasmedge'
-bash: make: command not found

makeがなくて失敗。
makeをインストールしてから、再度ビルドを実行。

nakkoh@dockerwasm:~/moby$ sudo apt install make

...

nakkoh@dockerwasm:~/moby$ make binary
docker build  --build-arg=GO_VERSION  -f "Dockerfile" --output=bundles/ --target=binary --build-arg VERSION --build-arg DOCKER_GITCOMMIT --build-arg PRODUCT --build-arg PLATFORM --build-arg DEFAULT_PRODUCT_LICENSE --build-arg PACKAGER_NAME .

...

 => exporting to client                                                                                                                                 1.1s 
 => => copying files 201.47MB

Docker+Wasmの要件として、Containerd Image Store (Beta)を有効にする必要があります。
イメージとファイルシステム管理に、containerdのsnapshotterを使うオプションのようです。

  • /etc/docker/daemon.json
{
  "features": {
    "containerd-snapshotter": true
  }
}

Dockerデーモンをバックグラウンドで起動

nakkoh@dockerwasm:~/moby$ nohup sudo -b sh -c "./bundles/binary-daemon/dockerd -D -H unix:///tmp/docker.sock --data-root /tmp/root --pidfile /tmp/docker.pid"
nohup: ignoring input and appending output to 'nohup.out'

インストール済のdockerデーモンも起動しているため、dockerデーモンが2つ動作していることになります。

サンプルアプリの実行

Dockerコンテキストの作成と変更

通常のDockerデーモンとWasm対応版のDockerデーモンを一緒に起動しているため、Docker CLIでWasm対応版の方を操作するには新たにdockerコンテキストを作成する必要があります。

nakkoh@dockerwasm:~$ docker context create wasm --docker "host=unix:///tmp/docker.sock"
wasm
Successfully created context "wasm"
nakkoh@dockerwasm:~$ docker context ls
NAME        DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default *   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
wasm                                                  unix:///tmp/docker.sock

コンテキストの変更

nakkoh@dockerwasm:~$ docker context use wasm
wasm

Wasmアプリ(サーバープログラムでない)の実行

指定した文字列を返すプログラムを実行する。

nakkoh@dockerwasm:~$ docker run --runtime=io.containerd.wasmedge.v1 rumpl/wasmtest echo 'hello from wasm'
Unable to find image 'rumpl/wasmtest:latest' locally
7cbb9105cdaf: Download complete 
dce2b2760aba: Download complete 
004a1e2d62d7: Download complete 
WARNING: The requested image's platform (wasi/wasm) does not match the detected host platform (linux/amd64/v2) and no specific platform was requested
hello from wasm
exiting

Wasmを実行できた!

また、wasmモジュールの一覧もコンテナイメージと同様の方法で確認できた。

nakkoh@dockerwasm:~$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
rumpl/wasmtest   latest    7cbb9105cdaf   49 minutes ago   505kB

Wasmアプリ(echoサーバー)の実行

今度は、以下を参考にサーバープログラムを実行する
https://docs.docker.com/desktop/wasm/#usage-examples

nakkoh@dockerwasm:~$ docker run -dp 8080:8080 \
  --name=wasm-example \
  --runtime=io.containerd.wasmedge.v1 \
  --platform=wasi/wasm32 \
  michaelirwin244/wasm-example
Unable to find image 'michaelirwin244/wasm-example:latest' locally
2a58923a21cb: Download complete 
130eeaf02640: Download complete 
e049f00c5289: Download complete 
8debd860a04d808ea2b8ede5ed262a36501101208285b4963ecd32b48cc0a6e7
nakkoh@dockerwasm:~$ docker ps
CONTAINER ID   IMAGE                          COMMAND              CREATED         STATUS         PORTS                                       NAMES
8debd860a04d   michaelirwin244/wasm-example   "hello_world.wasm"   7 seconds ago   Up 6 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   wasm-example

httpでアクセスしてみる。

nakkoh@dockerwasm:~$ curl localhost:8080
Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to younakkoh@dockerwasm:~$
nakkoh@dockerwasm:~$ curl -X POST -d "hello!" localhost:8080/echo
hello!

wasmでサーバープログラムを実行することができた。

Docker ComposeでWasmアプリを実行

以下を参考に実施する。
https://docs.docker.com/desktop/wasm/#running-a-wasm-application-with-docker-compose

以下のcomposeファイルを使用する。

services:
  app:
    image: michaelirwin244/wasm-example
    platform: wasi/wasm32
    runtime: io.containerd.wasmedge.v1
    ports:
      - 8080:8080

アプリ自体は上で実行したechoサーバーと同じ。
先程実行したwasmアプリを削除する。

nakkoh@dockerwasm:~$ docker rm -f 8debd860a04d
8debd860a04d
nakkoh@dockerwasm:~$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

docker composeでアプリを起動

nakkoh@dockerwasm:~$ docker compose -f echo-server/docker-compose.yaml up -d
[+] Running 1/1
 ⠿ Container echo-server-app-1  Started                                                                                                                 0.2s
nakkoh@dockerwasm:~$ docker compose -f echo-server/docker-compose.yaml ps
NAME                COMMAND              SERVICE             STATUS              PORTS
echo-server-app-1   "hello_world.wasm"   app                 running             0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
nakkoh@dockerwasm:~$ curl localhost:8080
Hello world from Rust running with Wasm! Send POST data to /echo to have it echoed back to younakkoh@dockerwasm:~$

multi-serviceアプリの実行

Wasmとコンテナのワークロードが混在したアプリにも対応している。

gitリポジトリをcloneする。

nakkoh@dockerwasm:~$ git clone https://github.com/second-state/microservice-rust-mysql.git

...

nakkoh@dockerwasm:~$ cd microservice-rust-mysql/

以下のcomposeファイルを使用する。

services:
  client:
    image: nginx:alpine
    ports:
      - 8090:80
    volumes:
      - ./client:/usr/share/nginx/html
  server:
    image: demo-microservice
    build:
      context: .
      platforms:
        - wasi/wasm32
    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

serverがwasmで動き、nginxとmariadbがコンテナで動作するアプリのようである。
serverはビルドから実施される。

先程のアプリと同じ8080番ポートを使うので、事前にアプリを削除する必要がある。

nakkoh@dockerwasm:~/microservice-rust-mysql$ docker compose -f ~/echo-server/docker-compose.yaml down
[+] Running 2/2
 ⠿ Container echo-server-app-1  Removed                                                                                                                 0.1s
 ⠿ Network echo-server_default  Removed                                                                                                                 0.0s

composeファイルからアプリを起動

nakkoh@dockerwasm:~/microservice-rust-mysql$ docker compose up -d

...

[+] Running 3/3
 ⠿ Container microservice-rust-mysql-db-1      Started                                                                                                  0.9s
 ⠿ Container microservice-rust-mysql-client-1  Started                                                                                                  0.9s
 ⠿ Container microservice-rust-mysql-server-1  Started                                                                                                  0.6s
nakkoh@dockerwasm:~/microservice-rust-mysql$ docker compose ps
NAME                               COMMAND                  SERVICE             STATUS              PORTS
microservice-rust-mysql-client-1   "/docker-entrypoint.…"   client              running             0.0.0.0:8090->80/tcp, :::8090->80/tcp
microservice-rust-mysql-db-1       "docker-entrypoint.s…"   db                  running             3306/tcp
microservice-rust-mysql-server-1   "order_demo_service.…"   server              running             0.0.0.0:8080->8080/tcp, :::8080->8080/tcp

ビルドが走るため時間がかかったが、アプリが起動したようである。

イメージを確認

nakkoh@dockerwasm:~/microservice-rust-mysql$ docker images
REPOSITORY                     TAG       IMAGE ID       CREATED          SIZE
demo-microservice              latest    6b8ad69b0139   9 minutes ago    3.06MB
mariadb                        10.9      bb39098029f4   18 minutes ago   124MB
nginx                          alpine    2452715dd322   18 minutes ago   10.2MB
michaelirwin244/wasm-example   latest    2a58923a21cb   4 hours ago      1.57MB
rumpl/wasmtest                 latest    7cbb9105cdaf   5 hours ago      505kB

ブラウザからhttp://localhost:8090にアクセスする。

Add an orderに値を入力し、orderを追加する。

追加したorderが表示されるようになった。

Wasmモジュールのビルドとプッシュ

Rustのhello-worldをビルドしてみる。

nakkoh@dockerwasm:~/hello$ tree .
.
├── Dockerfile
└── src
    └── hello.rs

1 directory, 2 files
  • src/hello.rs
fn main() {
    println!("Hello World!");
}
  • Dockerfile
FROM --platform=$BUILDPLATFORM rust:1.65
COPY src ./src
RUN rustup target add wasm32-wasi
RUN rustc ./src/hello.rs --target wasm32-wasi

FROM scratch
COPY --from=0 ./hello.wasm /hello.wasm
ENTRYPOINT [ "hello.wasm" ]

ビルドする。

nakkoh@dockerwasm:~/hello$ docker buildx build --platform wasi/wasm32 -t nakkoh/hello .
[+] Building 39.8s (11/11) FINISHED                                                                                                                          
 => [internal] load build definition from Dockerfile                                                                                                    0.0s
 => => transferring dockerfile: 256B                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                       0.0s
 => => transferring context: 2B                                                                                                                         0.0s
 => [internal] load metadata for docker.io/library/rust:1.65                                                                                            3.1s
 => [auth] library/rust:pull token for registry-1.docker.io                                                                                             0.0s
 => [internal] load build context                                                                                                                       0.0s
 => => transferring context: 110B                                                                                                                       0.0s
 => [stage-0 1/4] FROM docker.io/library/rust:1.65@sha256:b0f2a9e48df82f009fda8ae777119e7983104a1b4dc47026653b6cdaf447d14b                             30.7s
 => => resolve docker.io/library/rust:1.65@sha256:b0f2a9e48df82f009fda8ae777119e7983104a1b4dc47026653b6cdaf447d14b                                      0.0s
 => => sha256:a027b53f293bcf73812c9d074bc8e0da83c4752c6d7d8b0891d60245b804fdcb 157.29MB / 158.46MB                                                     36.6s
 => => extracting sha256:a027b53f293bcf73812c9d074bc8e0da83c4752c6d7d8b0891d60245b804fdcb                                                               4.2s
 => [stage-0 2/4] COPY src ./src                                                                                                                        0.2s
 => [stage-0 3/4] RUN rustup target add wasm32-wasi                                                                                                     5.1s
 => [stage-0 4/4] RUN rustc ./src/hello.rs --target wasm32-wasi                                                                                         0.2s
 => [stage-1 1/1] COPY --from=0 ./hello.wasm /hello.wasm                                                                                                0.0s 
 => exporting to image                                                                                                                                  0.1s
 => => exporting layers                                                                                                                                 0.1s
 => => exporting manifest sha256:2b384a1565fa1c81cf9d25717c04ca630ad13185434ac855026c9fa0474253f7                                                       0.0s
 => => exporting config sha256:345d594325b1677723258788f820d1741463468c845ac62b11215dfaea482241                                                         0.0s
 => => naming to docker.io/nakkoh/hello:latest                                                                                                          0.0s
 => => unpacking to docker.io/nakkoh/hello:latest                                                                                                       0.0s
nakkoh@dockerwasm:~/hello$ docker images
REPOSITORY                     TAG       IMAGE ID       CREATED          SIZE
demo-microservice              latest    6b8ad69b0139   3 hours ago      3.06MB
mariadb                        10.9      bb39098029f4   4 hours ago      124MB
nginx                          alpine    2452715dd322   4 hours ago      10.2MB
michaelirwin244/wasm-example   latest    2a58923a21cb   7 hours ago      1.57MB
nakkoh/hello                   latest    2b384a1565fa   19 seconds ago   502kB
rumpl/wasmtest                 latest    7cbb9105cdaf   8 hours ago      505kB

ビルドしたwasmモジュールを実行

nakkoh@dockerwasm:~/hello$ docker run --runtime=io.containerd.wasmedge.v1 nakkoh/hello
WARNING: The requested image's platform (wasi/wasm32) does not match the detected host platform (linux/amd64/v2) and no specific platform was requested
Hello World!

Docker Hubにプッシュ

nakkoh@dockerwasm:~/hello$ 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: nakkoh
Password: 

...

Login Succeeded
nakkoh@dockerwasm:~/hello$ docker push nakkoh/hello
Using default tag: latest
345d594325b1: Pushed 
880ce0c71f28: Pushed 
2b384a1565fa: Pushed 
latest: digest: sha256:2b384a1565fa1c81cf9d25717c04ca630ad13185434ac855026c9fa0474253f7, size: 5266

所感

コンテナと同じ操作で、wasmの実行、ビルド、プッシュできるので、気軽にwasmを使うことができます。
コンテナとwasmのワークロードが混在するアプリもつくることができるので、ユースケースに合わせてコンテナとwasmを選択するといった開発もできます。
Dockerがwasm対応したことで今後、よりwasmが注目されるようになると思うので、引き続き動向を追っていきたいなと思います。

Discussion