↔️

Dockerfileで対象プラットフォームによって処理分岐させる

2023/05/08に公開

概要

マルチプラットフォーム対応のdockerイメージをビルドしていると、最終的にコンテナが動作するホストのCPUアーキテクチャによって使用させる資源をDockerfile内で分岐させたい場合があります。
今回はターゲットのプラットフォームがlinux/amd64linux/arm64だとして、検証してみた際の備忘となります(Intel macとApple silicon macのそれぞれで動作させたかった背景です)。

ポイント

  • Dockerfile内でTARGETPLATFORM変数を使う

やってみた

環境

  • M2 mac
  • docker 20.10.24
  • docker buildx v0.10.4

自動定義されるARG変数の確認

https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope

Buildxによるビルドにおいて、上記ページで記載されているTARGETPLATFORMなどのARG変数は自動で設定されるようです。
Dockerfileで使用したい場合には、ARG TARGETPLATFORMと宣言をしておけば利用ができるとのこと。

以下のようなダミーのechoをするDockerfileとコマンドで試してみます。

Dockerfile
FROM debian:buster

ARG TARGETPLATFORM \
    TARGETARCH \
    BUILDPLATFORM \
    BUILDARCH

RUN echo "TARGETPLATFORM=${TARGETPLATFORM} TARGETARCH=${TARGETARCH}" && \
    echo "BUILDPLATFORM=${BUILDPLATFORM} BUILDARCH=${BUILDARCH}"

コマンド
$ docker buildx build --platform linux/amd64 .

結果は以下のようになりました。
--platformで指定したターゲットはamd64で、ビルドしているホストはM2 macでarm64なので、ちゃんとそれらしい値がARG変数に渡されていますね!

[+] Building 14.5s (6/6) FINISHED                                                                                                                                                 
 => [internal] load build definition from Dockerfile                                                                                                                         0.0s
 => => transferring dockerfile: 576B                                                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                                              0.0s
 => [internal] load metadata for docker.io/library/debian:buster                                                                                                             3.9s
 => [1/2] FROM docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                      10.3s
 => => resolve docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                       0.0s
 => => sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974 50.45MB / 50.45MB                                                                             9.4s
 => => sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6 984B / 984B                                                                                   0.0s
 => => sha256:ebc58102f66492508f6d0f0c5164978afbe27f4a69bd3431ed8bb015c82a8b80 529B / 529B                                                                                   0.0s
 => => sha256:dd20fc9536df9a9498531c65f65a6334b2dd8c199476491a4f2be95c148d9cc1 1.46kB / 1.46kB                                                                               0.0s
 => => extracting sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974                                                                                    0.7s
 => [2/2] RUN echo "TARGETPLATFORM=linux/amd64 TARGETARCH=amd64" &&     echo "BUILDPLATFORM=linux/arm64 BUILDARCH=arm64"                                                     0.2s
 => exporting to image                                                                                                                                                       0.0s
 => => exporting layers                                                                                                                                                      0.0s
 => => writing image sha256:cd5066c6e1c03c8d225070c8884a1c7d1a49b93c13e25a83fbd45ddf0f127976

では--platform linux/amd64,linux/arm64で複数指定した場合はどうなるでしょうか。
(BUILDPLATFORMとBUILDARCHは以降省きます)

結果としては以下のようにそれぞれのアーキテクチャが表示されました!

$ docker buildx create --use
nostalgic_wing
$ docker buildx build --platform linux/amd64,linux/arm64 .
[+] Building 18.4s (9/9) FINISHED                                                                                                                                                 
 => [internal] booting buildkit                                                                                                                                              3.4s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                                           2.7s
 => => creating container buildx_buildkit_nostalgic_wing0                                                                                                                    0.7s
 => [internal] load .dockerignore                                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                                              0.0s
 => [internal] load build definition from Dockerfile                                                                                                                         0.1s
 => => transferring dockerfile: 506B                                                                                                                                         0.0s
 => [linux/arm64 internal] load metadata for docker.io/library/debian:buster                                                                                                 4.4s
 => [linux/amd64 internal] load metadata for docker.io/library/debian:buster                                                                                                 4.4s
 => [linux/amd64 1/2] FROM docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                          10.2s
 => => resolve docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                       0.0s
 => => sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974 50.45MB / 50.45MB                                                                             8.6s
 => => extracting sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974                                                                                    1.6s
 => [linux/arm64 1/2] FROM docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                           9.4s
 => => resolve docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                       0.0s
 => => sha256:b7c5fe8e0ac53c77142bf16686fc01d0d2b1fb2da7be5414cdf2f224ec485592 49.24MB / 49.24MB                                                                             7.8s
 => => extracting sha256:b7c5fe8e0ac53c77142bf16686fc01d0d2b1fb2da7be5414cdf2f224ec485592                                                                                    1.6s
 => [linux/arm64 2/2] RUN echo "TARGETPLATFORM=linux/arm64 TARGETARCH=arm64"                                                                                                 0.2s
 => [linux/amd64 2/2] RUN echo "TARGETPLATFORM=linux/amd64 TARGETARCH=amd64"                                                                                                 0.1s 
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load

変数の値による分岐

前項で確認した変数を使えば、プラットフォームごとに適したバイナリのインストールなどがDockerfileで行えそうです。
ただ、インストールしたいパッケージ名のアーキテクチャ箇所がそのままTARGETPLATFORMの値になっているとかであればwget linux-${TARGETPLATFORM}.tar.gzなどとシンプルに記載できますが、x86_64とかaarch64とかになっていると変換が必要になります。

Dockerfileで分岐構文などは提供されていないため、私はシェル変数にcase文の結果を代入する方法で対応してみました。  
例えば以下のように、TARGETPLATFORMの値をcaseで分岐させた結果をPLATFORMというシェル変数に代入して使用する感じです。

Dockerfile
FROM debian:buster

ARG TARGETPLATFORM

RUN PLATFORM=$( \
      case ${TARGETPLATFORM} in \
        linux/amd64 ) echo "x86_64";; \
        linux/arm64 ) echo "aarch_64";; \
      esac \
    ) && \
    # PLATFORMの値を使用したファイルを作成
    touch ${PLATFORM} && \
    # 作成したファイルがあるか確認(存在しなければ$?=2となりビルドエラーとなる)
    [ -f ${PLATFORM} ]

ビルド結果(正常終了)
$ docker buildx build --platform linux/amd64,linux/arm64 .
[+] Building 19.1s (8/8) FINISHED                                                                                                                                                 
 => [internal] load .dockerignore                                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                                              0.0s
 => [internal] load build definition from Dockerfile                                                                                                                         0.0s
 => => transferring dockerfile: 761B                                                                                                                                         0.0s
 => [linux/arm64 internal] load metadata for docker.io/library/debian:buster                                                                                                 3.2s
 => [linux/amd64 internal] load metadata for docker.io/library/debian:buster                                                                                                 3.7s
 => [linux/arm64 1/2] FROM docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                          15.3s
 => => resolve docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                       0.0s
 => => sha256:b7c5fe8e0ac53c77142bf16686fc01d0d2b1fb2da7be5414cdf2f224ec485592 49.24MB / 49.24MB                                                                            14.2s
 => => extracting sha256:b7c5fe8e0ac53c77142bf16686fc01d0d2b1fb2da7be5414cdf2f224ec485592                                                                                    1.1s
 => [linux/amd64 1/2] FROM docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                          15.1s
 => => resolve docker.io/library/debian:buster@sha256:cca6bcced970f7634197ff1821aabb452024eb437958ab98bfc146ece96969c6                                                       0.0s
 => => sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974 50.45MB / 50.45MB                                                                            14.0s
 => => extracting sha256:a94073ab46f8d565f5938cc345d32f7adda10a2585e39555079da9d4ee595974                                                                                    1.1s
 => [linux/amd64 2/2] RUN PLATFORM=$(       case linux/amd64 in         linux/amd64 ) echo "x86_64";;         linux/arm64 ) echo "aarch_64";;       esac     ) &&     touch  0.2s
 => [linux/arm64 2/2] RUN PLATFORM=$(       case linux/arm64 in         linux/amd64 ) echo "x86_64";;         linux/arm64 ) echo "aarch_64";;       esac     ) &&     touch  0.0s
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load

(もっと良い方法があるよ!とかでしたら是非教えていただきたいです🙇)

それではこのcase分岐を使って、protoc(v3.15.8)のバイナリをダウンロードしてみます。
以下のようにDockerfileを作成し、ビルドしてdockerhubにpushします(ビルド結果は記載省略)。

Dockerfile
FROM debian:buster

ARG TARGETPLATFORM

RUN apt update && apt install -y unzip wget
RUN PLATFORM=$( \
      case ${TARGETPLATFORM} in \
        linux/amd64 ) echo "x86_64";; \
        linux/arm64 ) echo "aarch_64";; \
      esac \
    ) && \
    wget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.8/protoc-3.15.8-linux-${PLATFORM}.zip && \
    unzip protoc-3.15.8-linux-${PLATFORM}.zip -d protoc3 && \
    mv protoc3/bin/* /usr/local/bin/ && \
    mv protoc3/include/* /usr/local/include/

コマンド
$ docker buildx build --platform linux/amd64,linux/arm64 -t ytdrep/protoc-platform-test:latest --push .

pushしたイメージを、linux/arm64とlinux/amd64それぞれで起動してみます。
適切にprotocが動作すればokです。

linux/arm64
$ docker run -it --rm ytdrep/protoc-platform-test:latest bash

root@7e6d7d96a739:/# uname -m
aarch64
root@7e6d7d96a739:/# protoc --version
libprotoc 3.15.8
linux/amd64
$ docker run -it --rm --platform linux/amd64 ytdrep/protoc-platform-test:latest bash

root@722928a0be8e:/# uname -m
x86_64
root@722928a0be8e:/# protoc --version
libprotoc 3.15.8

問題なさそうですね!

あとがき

GitHub Actionsでbuildx使用してマルチプラットフォームビルドした場合も、同じようなDockerfileの使い方ができるか確認して追記したいと思います。

Discussion