PHP 開発環境の Docker イメージとして公開している shin1x1/php-dev イメージの arm64
対応を行いました。従来の amd64
も必要なので、マルチアーキテクチャビルドでイメージを生成するようにしています。
shin1x1/php-dev については下記を参照で。 blog.shin1x1.com
Docker Buildx による multi-arch ビルド
Docker Buildx は Buildkit でビルド機能を拡張する Docker CLI プラグインです。Buildx にはマルチアーキテクチャビルド機能があるので、これを利用します。
実行の流れを掴むために M1 Mac の Docker Desktop で Buildx を利用してビルドしてみます。Docker Desktop 4.3.1 には Buildx が同梱されており、デフォルトで buildx コマンドが有効となっていました。
$ docker buildx version github.com/docker/buildx v0.7.1 05846896d149da05f3d6fd1e7770da187b52a247
マルチアーキテクチャビルドを実行するために新規ビルダーを作成します。下記では multi-arch
という名前のビルダーを作成し、有効にしています。
$ docker buildx create --use --name multi-arch multi-arch $ docker buildx ls NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS multi-arch * docker-container multi-arch0 unix:///var/run/docker.sock inactive desktop-linux docker desktop-linux desktop-linux running linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 default docker default default running linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
multi-arch
ドライバが inactive になっているので起動します。
$ docker buildx inspect --builder multi-arch --bootstrap [+] Building 3.2s (1/1) FINISHED => [internal] booting buildkit 3.2s => => pulling image moby/buildkit:buildx-stable-1 2.8s => => creating container buildx_buildkit_multi-arch0 0.4s Name: multi-arch Driver: docker-container Nodes: Name: multi-arch0 Endpoint: unix:///var/run/docker.sock Status: running Platforms: linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
ビルドするイメージの Dockerfile を作成します。ここでは、alpine
イメージにファイル /hello
を保存しただけのものです。これで準備ができました。
FROM alpine RUN echo 'Hello' > /hello
ビルドするには、docker buildx build
コマンドを利用します。--platform
オプションでターゲットアーキテクチャを指定します。ここでは、linux/amd64
と linux/arm64
を指定しています。--push
オプションを付けることでビルドしたイメージをそのまま Docker Hub にプッシュします。
$ docker buildx build --platform linux/amd64,linux/arm64 -t shin1x1/test:latest --push . (snip) => => pushing layers 8.5s => => pushing manifest for docker.io/shin1x1/test:latest@sha256:32400fa23ce020b150c88c107e3995de7ace477f51c6fae2ea6c51b6329f0e4a 3.9s => [auth] shin1x1/test:pull,push token for registry-1.docker.io 0.0s => [auth] shin1x1/test:pull,push token for registry-1.docker.io
実行完了後に Docker Hub にアクセスすると、イメージタグに linux/amd64
とlinux/arm64
が表示されており、両アーキテクチャのイメージがプッシュされていることが分かります。
Buildx を利用することで簡単に両アーキテクチャに対応したイメージをビルド、プッシュできます。この操作を GitHub Actions で自動実行します。
GitHub Actions で multi-arch ビルド & プッシュ
GitHub Actions では、Docker Buildx でマルチアーキテクチャビルドするためのアクションとして docker/build-push-action
が Docker から公開されています。こちらにセットアップを含めた利用例があるので、これを参照にアクションを記述しました。
記述したアクションが下記です。上から順に、QEMU、Docker Buildx、Docker Hub ログインとセットアップを進めています。その後、docker/build-push-action
を利用して Docker イメージのビルド、プッシュを行います。platforms
キーではターゲットのアーキテクチャとして linux/amd64
とlinux/arm64
を指定し、tags
キーでイメージとタグ名を指定しています。
このアクションがトリガーされると Docker イメージがビルドされ、Docker Hub にプッシュされます。
https://github.com/shin1x1/docker-php-dev/blob/master/.github/workflows/build-and-push.yml
jobs: build-and-push: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Login to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - run: echo ${{github.ref_name}} - name: Build and push uses: docker/build-push-action@v2 with: context: . file: Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: shin1x1/php-dev:${{github.ref_name}}
実際に公開されているのが下記です。amd64
とarm64
のイメージが公開されています。
https://hub.docker.com/r/shin1x1/php-dev/tags
ビルドが遅い
GitHub Actions でマルチアーキテクチャビルドを実行してみると、完了するまで 30 分から 40 分(!) かかりました。ビルド時間は下記のようになっており、linux/arm64
のビルドでは約 40 分かかっています。
これは M1 Pro Mac でも同様で、同じ Dockerfile をマルチアーキテクチャビルドしてみると、amd64 イメージについては 900s(15m) ほどかかりました。
今回のイメージでは PHP 拡張のビルド処理が入っており、こうした処理をホストとは異なるアーキテクチャのイメージ内で実行すると、QEMU によるオーバーヘッドが発生します。これは仕組み上致し方ないですが、マルチアーキテクチャビルドの場合、ビルド時間については認識しておく必要があります。
ビルド速度改善アイデア
このビルド速度を改善するアイデアとして、2つの方法が Docker Blog で紹介されていました。
案 1 は、複数ホストによるビルドです。
Buildx では、QEMU を利用した単一ホストによる多アーキテクチャビルドだけでなく、複数のホストによるビルドもサポートしています。これを利用すれば、linux/amd64
イメージは x86_64 環境、linux/arm64
イメージは arm64 環境と専用の環境でビルドできます。CircleCI には arm64 の VM もあるようなので、どうにかできないかなと考えたりもします。
案 2 は、クロスコンパイルでターゲットアーキテクチャのバイナリを生成する方法です。
下記では、ホストアーキテクチャイメージ内で Go アプリケーションをビルドして、ターゲットアーキテクチャのバイナリを生成しています。つまり、x86_64 環境であれば build
ステージは常に amd64
イメージで実行して、go build
コマンドでターゲットアーキテクチャを指定することで目的のバイナリを生成します。最後に生成したバイナリをターゲットアーキテクチャのイメージにコピーするという流れです。
FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS build WORKDIR /src COPY . . ARG TARGETOS TARGETARCH RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/myapp . FROM alpine COPY --from=build /out/myapp /bin
shin1x1/php-dev ではこの方法は難しそうですが、Go や Rust のようにクロスコンパイルが容易なアプリケーションであれば単一のホストで実現でき、効果も大きいので良さそうです。
GitHub Actions 並列実行
上記では 1 ビルドが遅いと書きましたが、GitHub Actions では並列に実行できるので、複数イメージをビルドする場合でもトータルの時間は 1 ビルド時間 + α 程度で完了します。shin1x1/php-dev では、Git タグを push すると GitHub Action が実行されてビルド、プッシュを行うようにしています。28 タグを一気に更新したところ、28 並列で実行され、トータルでは 1h 弱で全てのイメージを Docker Hub にプッシュできました。28 並列が一気に起動して動くのは爽快でした :)
この対応の前に利用していた Docker Hub の autobuild では、1 イメージのビルドは 1-3m 程度で終わる(amd64 のみ)のですが、直列にビルドが実行されるので、トータルの時間ではこちらの方が遅かったかもしれません。
これだけの並列にアクションを一度に動かして、それが無料*3というのはすごいですね。ありがたい。
さいごに
M1 Mac の登場により、現時点ではチーム内で利用するアーキテクチャが混在する場面が想定されます。また、いずれ開発環境は arm64 に統一されても、本番環境が x86_64 のままということも十分にありえるでしょう。今後、複数アーキテクチャの Docker イメージが必要な場面が増えてきそうです。
現状でも、Buildx を使うと手軽にビルドできるのですが、ビルド速度が遅いのがネックなので、GitHub Actions や CircleCI などの CI サービスで上手い具合に対応してもらえると嬉しいですね。
*1:https://github.com/shin1x1/docker-php-dev/runs/4495163876?check_suite_focus=true#step:7:2688
*2:https://github.com/shin1x1/docker-php-dev/runs/4495163876?check_suite_focus=true#step:7:4215
*3:"GitHub Actions usage is free for both public repositories and self-hosted runners.": https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#about-billing-for-github-actions