Skip to main content

x64 な Windows で ARM アーキテクチャのコンテナイメージをビルドする

Docker は マルチ CPU アーキテクチャ をサポートしているので、docker image pull すると、イメージ側が対応していれば、同じイメージの同じタグを指定しても、環境(OS や CPU アーキテクチャ)に応じて適切なイメージが勝手に取得されます。

で、今回、マルチ CPU アーキテクチャに対応したイメージを、実際に x64 な Windows 環境だけで意外とサクッとビルドできたので、そのお話。

マルチ CPU アーキテクチャ

例えば alpine:latestpull して inspect すると、手元の Windows 環境(x64)では次のように amd64 が降ってくるのに対して、

> docker pull alpine:latest
> docker inspect alpine:latest
[
    {
        "Id": "sha256:f70734b6a266dcb5f44c383274821207885b549b75c8e119404917a61335981a",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54"
        ],
        ...
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 5612304,
        "VirtualSize": 5612304,
        ...
    }
]

同じことを Raspberry Pi にインストールした Ubuntu(arm64)で実行すると、CPU が ARM アーキテクチャなので正しく arm64 が降ってきます。

$ docker pull alpine:latest
$ docker inspect alpine:latest
[
    {
        "Id": "sha256:c20d2a9ab6869161e3ea6d8cb52d00be9adac2cc733d3fbc3955b9268bfd7fc5",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54"
        ],
        ...
        "Architecture": "arm64",
        "Os": "linux",
        "Size": 5357453,
        "VirtualSize": 5357453,
        ...
    }
]

Docker Hub で見ると、ひとつのタグに対してアーキテクチャごとに別々のイメージが紐づけられている様子がわかります。

今回は、つまり、これを自分で作りたいね、ということです。

困ったこと

Docker Hub にあるイメージのうち、公式に展開されているものは、もともとこのような仕組みで(または別のタグ、別のイメージとして)複数の CPU アーキテクチャに対応してくれているものが多いのですが、野良イメージだとそうでない場合があります。

で、今回、実際に amd64 環境で便利に使っていたコンテナを arm64 で動かそうとしたら動かせなかった…… という事があり、そんなわけで自前でビルドすることにしました。

そのイメージの Dockerfile は公開されていたので、Raspberry Pi 上で docker build . すれば使えはするのですが、ビルドのためだけにいちいちそのアーキテクチャのプラットフォームを用意するのも長期的にみると相当イケてないでしょうということで、21 世紀だしエミュレートしてどうにかできるでしょ、みたいな。

方法

さっきのリンク先Buildx というのが書いてあり、それでできるみたいです。現時点では Experimental な機能 なので、覚悟して使いましょう。

Docker Desktop for Windows を使う場合、設定画面の Command Line から、Enable experimental features を有効にする必要があります。

これで docker buildx コマンドが使えるようになるので、あとは さっきのリンク先 の手順通りです。

最初はデフォルトのビルダ default が指定されていますが、これだとマルチ CPU の機能は対応しておらず、multiple platforms feature is currently not supported for docker driver と怒られます。

> docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker
  default default         running linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

> docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t kurokobo/demo:latest .
multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")

ので、BuildKit ベースの新しいビルダを作って、それを使うように設定します。ls したときの * がアクティブなやつです。

> docker buildx create --name multipfbuilder --use
multipfbuilder

> docker buildx ls
NAME/NODE         DRIVER/ENDPOINT                STATUS   PLATFORMS
multipfbuilder *  docker-container
  multipfbuilder0 npipe:////./pipe/docker_engine inactive
default           docker
  default         default                        running  linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

あとは Dockerfile のあるディレクトリを指定してビルドするだけです。--push を指定して、できたイメージは直接リポジトリにつっこみます。初回実行時は BuildKit のコンテナイメージの取得が最初に入ります。

> docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t kurokobo/demo:latest --push .
[+] Building 59.5s (13/13) FINISHED
 => [internal] booting buildkit                                                                                   16.1s
 => => pulling image moby/buildkit:buildx-stable-1                                                                14.5s
 => => creating container buildx_buildkit_multipfbuilder0                                                          1.6s
...
 => [linux/amd64 internal] load metadata for docker.io/library/alpine:3.9                                         13.1s
 => [linux/arm/v7 internal] load metadata for docker.io/library/alpine:3.9                                        10.2s
 => [linux/arm64 internal] load metadata for docker.io/library/alpine:3.9                                         13.1s
 => [linux/amd64 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899e  9.8s
...
 => [linux/arm64 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899  11.8s
...
 => [linux/arm/v7 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899  9.5s
...
 => exporting to image                                                                                            14.4s
 => => exporting layers                                                                                            0.4s
 => => exporting manifest sha256:2ce6c278a94742c9510b15369b72bf62681741e52074c0b11c4c1da8706b1417                  0.0s
 => => exporting config sha256:9913143b883435a3a206777147d7c9aaac67a50e4538b79283eb1dca6a3ef633                    0.0s
 => => exporting manifest sha256:5935a9dde92945f9e4f601cc548918de5076856f7bc06b2137bf0767c5d19978                  0.0s
 => => exporting config sha256:93d0264837f8f039cd002e842511276aaf9ff3355dea4f7171ce6a97b4c0264f                    0.0s
 => => exporting manifest sha256:e023fb745782f5d2eeda3a8dd80fc93ca0f7051a78ecd03a062d8595473ed7b1                  0.0s
 => => exporting config sha256:f5f2bad0db136c6f06bf2d4244814660cf229e66bf9dd18eeb47e958c6033cd6                    0.0s
 => => exporting manifest list sha256:9a058a80760a142ff335305a438dfbb0eb640d910a01e9ee8a319ec1faa00a85             0.0s
 => => pushing layers                                                                                              6.8s
 => => pushing manifest for docker.io/kurokobo/demo:latest                                                         7.0s

--push を指定しないと、docker images にも表示されませんでした。BuildKit 内に キャッシュができるだけのようです。

ビルドしてそのまま使えるようにするには --load を指定すればよさそう…… なのですが、現状、--platform に複数の引数を指定している場合は --load はコケる みたいです。実際、コケました。

BuildKit 自体の機能を正しく使ってキャッシュをどうのこうのとかきちんと何かすればもうすこしいろいろできるのかもですが、まだよくわからんです。

というわけでできました。

Windows と Raspberry Pi で実際に pull すると、別のイメージが降ってくるようになりました。かんぺきです。

なお、できたあとも、手元の Docker ホストには、BuildKit のコンテナ(とそれ用のイメージ)が起動したままで残るようです。使わなくなったら殺そう。

> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
moby/buildkit       buildx-stable-1     f2a88cb62c92        2 weeks ago         82.8MB

> docker ps -a
CONTAINER ID        IMAGE                           COMMAND             CREATED             STATUS              PORTS               NAMES
9b06292f089c        moby/buildkit:buildx-stable-1   "buildkitd"         2 minutes ago       Up 2 minutes                            buildx_buildkit_multipfbuilder0

応用

GitHub Actions などと組み合わせれば、Dockerfile をコミットしたらこれでビルドして Docker Hub へ…… などもできますね。