Closed6

Dockerでマルチプラットフォームビルド

kun432kun432

公式ドキュメントに従ってやってみる

https://docs.docker.com/build/building/multi-platform/

まずはローカルのMac(M2)で。Docker Desktop for Macを使用している。

作成した記憶がないのだが、ビルダーは作成済みだった。

docker buildx ls --no-trunc
出力
NAME/NODE           DRIVER/ENDPOINT     STATUS    BUILDKIT   PLATFORMS
desktop-linux*      docker
 \_ desktop-linux    \_ desktop-linux   running   v0.17.3    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386

QEMUエミュレーションを使ったマルチプラットフォームビルド

作業ディレクトリ作成

mkdir multi-platform && cd multi-platform

以下のDockerfileをビルドする。

Dockerfile
FROM alpine
RUN uname -m > /arch

linux/amd64linux/arm64向けにビルド

docker build --platform linux/amd64,linux/arm64 -t multi-platform .

コンテナを起動してみる

docker run --rm multi-platform cat /arch
出力
aarch64

エミュレーションになってしまうが、AMD64想定で起動してみる

docker run --rm --platform linux/amd64 multi-platform cat /arch
出力
x86_64

DockerHubにpushしてみる

docker tag multi-platform:latest kun432/multi-platform:latest
docker push kun432/multi-platform:latest

マルチプラットフォームに対応したイメージになっているのがわかる

QEMUエミュレーションについては、これぐらいだと時間の違いがわからないので、上の「ぽよめも」様の記事をベースに「miyajan」様が作成されたDockerfileのサンプルを使用させていただく。

Dockerfile
FROM golang:1.22.3 AS builder

ARG VERSION=latest

RUN go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@${VERSION}

FROM gcr.io/distroless/static

COPY --from=builder /go/bin/cowsay /usr/bin/cowsay

ENTRYPOINT ["/usr/bin/cowsay"]
docker build --platform linux/amd64,linux/arm64 -t cowsay .

確かにgo installしている箇所の時間が違う。Mac上でビルドしているのでlinux/arm64のほうが早くlinux/amd64のほうが時間がかかっている。ここでQEMUのエミュレーションが行われているということになる。

出力
 => [linux/arm64 builder 2/2] RUN go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@latest                        7.3s
 => [linux/amd64 builder 2/2] RUN go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@latest                       16.7s
kun432kun432

マルチネイティブノード

プラットフォームごとにビルダーを用意してそれぞれでビルドさせることで、QEMUエミュレーションを回避する。

以下の記事がわかりやすい。

https://zenn.dev/haru_iida/articles/buildx_remote

linux/arm64はMac、linux/amd64は、x86_64なUbuntu 22.04サーバでビルドすることとする。

パスワード無しでログインできる環境である必要がある

docker -H ssh://kun432@REMOTE_HOST info
出力
Client:
 Version:    27.4.0
 Context:    default
 Debug Mode: false
(snip)

ローカル用・リモート用、それぞれのノードをlocal_remote_builderというビルダーで定義

docker buildx create \
    --name local_remote_builder \
    --node arm64 \
    --platform linux/arm64 \
    --driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=10000000 \
    --driver-opt env.BUILDKIT_STEP_LOG_MAX_SPEED=10000000

appendで追加

docker buildx create \
    --name local_remote_builder \
    --append \
    --node amd64 \
    --platform linux/amd64,linux/amd64/v2,linux/386 \
    ssh://kun432@REMOTE_HOST \
    --driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=10000000 \
    --driver-opt env.BUILDKIT_STEP_LOG_MAX_SPEED=10000000

確認

docker buildx ls --no-trunc
出力
NAME/NODE              DRIVER/ENDPOINT                  STATUS     BUILDKIT   PLATFORMS
local_remote_builder   docker-container
 \_ arm64               \_ desktop-linux                inactive              linux/arm64*
 \_ amd64               \_ ssh://kun432@REMOTE_HOST   inactive              linux/amd64*, linux/amd64/v2*, linux/386*
desktop-linux*         docker
 \_ desktop-linux       \_ desktop-linux                running    v0.17.3    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386

ステータスがinactiveになってるけどいいのかな???

とりあえずこれを使ってビルドしてみる。

docker build \
    --platform linux/amd64,linux/arm64 \
    --builder local_remote_builder \
    -t cowsay \
    --no-cache .

どうやら初回のみ?ビルド用のコンテナがローカル・リモートの両方で作成されるので少し時間がかかる模様。ただgo installしている箇所はQEMUエミュレーションと違って高速になっている。

出力
 => [linux/arm64 builder 2/2] RUN go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@latest                        7.3s
 => [linux/amd64 builder 2/2] RUN go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@latest                        7.8s

DockerHubにpushして確認

docker tag cowsay:latest kun432/cowsay:latest
docker push kun432/cowsay:latest

Mac側

docker inspect kun432/cowsay:latest | jq -r '.[0] | .Os + "/" + .Architecture'
出力
linux/arm64

Ubuntu側

docker pull kun432/cowsay
docker inspect kun432/cowsay:latest | jq -r '.[0] | .Os + "/" + .Architecture'
出力
linux/amd64
kun432kun432

クロスコンパイル

クロスコンパイルが可能な環境ではDockerfileでマルチプラットフォームビルドを吸収することができる。色々やり方がある模様だが、基本的にはマルチステージビルドと組み合わせる感じっぽい。

ここはあまり知見がないので割愛。

kun432kun432

マニフェスト

自分は過去意識したことがなかったのだけども、以下の記事を見ながらやってみる。

https://zenn.dev/ncdc/articles/25d03e908ce38e

Dockerにおけるマニフェストは、レイヤー・サイズ・ダイジェストなどの情報を含むイメージに関する情報を指す。マニフェストの目的は以下。

  1. イメージ情報
    マニフェストは、レイヤー・サイズ・ダイジェストなど、そのイメージの詳細な情報を持つ。これによりイメージをダウンロードしなくても、ユーザーやツールがそのイメージの構成や特性を把握することができる。
  2. マルチプラットフォームのサポート
    マニフェストリスト(「マルチアーキテクチャイメージ」とも言う)を使うことで、1つのイメージで複数のプラットフォームをサポートできる。これにより、さまざまなハードやソフトで実行できるポータブルなアプリケーションを作成することができる。
  3. レジストリとの通信
    Dockerクライアントがイメージをプルすると、レジストリはマニフェストのリストを返す。これを元にクライアントはホストのアーキテクチャに適したバリアントを自動的に選択することができる。
  4. メタデータストレージ
    イメージに関する追加のコンテキストや情報など、イメージのメタデータをマニフェストに保存することができる。
  5. バージョン管理とタグ付け
    上と同様に、イメージの異なるバージョンやタグをマニフェストに保存することができる。
  6. セキュリティ
    マニフェストにはイメージのダイジェスト情報が含まれており、転送中や保存中のイメージの改ざん等が行われていないか、イメージの完全性を検証することができる。
  7. 効率的な配布
    マニフェストリストを使うことで、レジストリは単一の参照でイメージの複数のバリエーションを保存できる。マルチアーキテクチャイメージの配布プロセスを合理化できる。

1つのプラットフォームにしか対応していないイメージの場合は以下となる。


referred from https://docs.docker.com/build/building/multi-platform/#difference-between-single-platform-and-multi-platform-images and translated into Japanese by kun432

マルチプラットフォームに対応したイメージは、各プラットフォームごとのマニフェストを持つマニフェストリストを持つ。


referred from https://docs.docker.com/build/building/multi-platform/#difference-between-single-platform-and-multi-platform-images and translated into Japanese by kun432

マニフェストの操作にはdocker manifestコマンドを使用する。実際に複数のプラットフォームごとのイメージからマニフェストリストを作成してみる。上の方で使用したmultiplatformのDockerfileをビルドしてDocker Hubにプッシュする。

Dockerfile
FROM alpine
RUN uname -m > /arch
docker build --platform linux/arm64 -t kun432/sample-arm64:latest .
docker push kun432/sample-arm64:latest

マニフェストを確認

docker manifest inspect kun432/sample-arm64:latest
出力
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.index.v1+json",
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 668,
         "digest": "sha256:acd826a5bb28ca16799ced9b39e1283f7c859b062dfdc2267a8d955ab6167481",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 566,
         "digest": "sha256:f482f546c9da4582944894967caddbc1cbb32c476af5abf76f7707b84bf8dfac",
         "platform": {
            "architecture": "unknown",
            "os": "unknown"
         }
      }
   ]
}

すでにこの時点でマニフェストリストになっていて、プラットフォームが"unknown"になっているのだが、Provenance attestationsというものらしい。詳しくは以下が参考になる。

https://zenn.dev/kuritify/articles/docker-descktop-setting-from-ecr-400-bad-request

今回はそこはあまり触れずに進める。

次にUbuntuでも。

docker build --platform linux/amd64 -t kun432/sample-amd64:latest .
docker push kun432/sample-amd64:latest

マニフェストを確認

docker manifest inspect kun432/sample-amd64:latest
出力
{
	"schemaVersion": 2,
	"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
	"config": {
		"mediaType": "application/vnd.docker.container.image.v1+json",
		"size": 818,
		"digest": "sha256:676507ffea28737fdff56dab8f22aeae30af713ae2b557eda0805ddc882827ad"
	},
	"layers": [
		{
			"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
			"size": 3642247,
			"digest": "sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870"
		},
		{
			"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
			"size": 137,
			"digest": "sha256:f18de9172e4f52a2f2ccf2277d7e2950266d8b09ea63b5e005cba2de13545836"
		}
	]
}

全然フォーマットが違うなー。Docker Desktopの設定でかなり変わるってことかなー。まあ、そこも一旦おいておく。

この2つのイメージをまとめて1つのマニフェストにする。

docker manifest create kun432/sample-multi:latest \
    kun432/sample-arm64:latest \
    kun432/sample-amd64:latest
出力
docker.io/kun432/sample-arm64:latest is a manifest list

どうもマニフェストリストとマニフェストを1つのマニフェストリストにはまとめれない様子。

MacのDocker Desktopの設定を変更することもできるけども、--provenance=falseを使えばProvenance attestationsを無効化できるはず。

再度ビルドし直し

docker rmi kun432/sample-arm64:latest 
docker build --platform linux/arm64 --provenance=false -t kun432/sample-arm64:latest . --no-cache 
docker push kun432/sample-arm64:latest

マニフェストを確認

docker manifest inspect kun432/sample-arm64:latest
出力
{
	"schemaVersion": 2,
	"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
	"config": {
		"mediaType": "application/vnd.docker.container.image.v1+json",
		"digest": "sha256:a711b39d196b0597b491c75f705b1c7cc2b089e4a95d82552bb12447fe9e5845",
		"size": 809
	},
	"layers": [
		{
			"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
			"digest": "sha256:6e771e15690e2fabf2332d3a3b744495411d6e0b00b2aea64419b58b0066cf81",
			"size": 3993029
		},
		{
			"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
			"digest": "sha256:1dd6892e0663966a7f1f0ada81ac64a4aea77118165be05749c57c2a1f7266f6",
			"size": 105
		}
	]
}

うむ、通常のマニフェストになっている。

再度マニフェストリストを作成。

docker manifest create kun432/sample-multi:latest \
    kun432/sample-arm64:latest \
    kun432/sample-amd64:latest
出力
Created manifest list docker.io/kun432/sample-multi:latest

今度は問題なし。Docker Hubにpushして、マニフェストリストも確認。

docker manifest inspect kun432/sample-multi:latest
出力
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 734,
         "digest": "sha256:044c54279f351ae6d84da7f035b56c7fac1e4a1daacaacc6532ee8bdb8bd1af4",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 696,
         "digest": "sha256:8359f4c0c1371d53b9edbf45bfd0641d53299bf4a9111bb0e4018549061ddf61",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

マルチプラットフォームなマニフェストリストになっているのがわかる。

Docker Hubにpush。マニフェストリストの場合はdocker manifest pushになる。

docker manifest push kun432/sample-multi:latest

Docker Hub上でもマルチプラットフォーム対応になっているのがわかる。

そういえば上の方でMac上でマルチプラットフォームイメージは--provenance=falseとかつけてなかったけど作成できたよな、と思っていたら、以下の記事にも書いてあった。

https://zenn.dev/kuritify/articles/docker-descktop-setting-from-ecr-400-bad-request

デフォルトのimage storeでも、Buildxであればマルチプラットフォームイメージをビルドできるじゃないか!?と思われた方もいると思いますが、Buildx経由の場合、image storeは経由せず、driver独自のキャッシュストレージでイメージは管理されます。Buildx経由であればマルチイメージプラットフォームイメージをビルドできるのはそのためです。

Unlike when using the default docker driver, images built using other drivers aren't automatically loaded into the local image store. If you don't specify an output, the build result is exported to the build cache only.
-- https://docs.docker.com/build/builders/drivers/#loading-to-local-image-store

この記事、本当にいろいろ参考になった。

kun432kun432

以下の記事を参考に、マルチプラットフォームビルドして、ghcr.ioにpushするGitHub ActionsのサンプルをChatGPTに書いてもらった。

https://zenn.dev/cybozu_ept/articles/build-multi-platform-image-with-arm64-runner

.github/workflows/build.yaml
name: Build and push multi-platform image

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  build_push:
    permissions:
      contents: read
      packages: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: amd64
            platform: linux/amd64
            runner: ubuntu-24.04
          - arch: arm64
            platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push image for ${{ matrix.arch }}
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: ${{ matrix.platform }}
          tags: ghcr.io/${{ github.repository }}:${{ matrix.arch }}-${{ github.ref_name }}
          push: true

  merge:
    name: Create and push manifest list
    runs-on: ubuntu-latest
    needs: build_push
    permissions:
      contents: read
      packages: write
    steps:
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Install jq
        run: sudo apt-get update && sudo apt-get install -y jq

      - name: Create manifest list and push
        run: |
          IMAGE="ghcr.io/${{ github.repository }}"
          TAG="${{ github.ref_name }}"
          
          echo "Creating manifest list for ${IMAGE}:${TAG}"
          
          DIGEST_AMD64=$(docker buildx imagetools inspect ${IMAGE}:amd64-${TAG} --raw | jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest')
          DIGEST_ARM64=$(docker buildx imagetools inspect ${IMAGE}:arm64-${TAG} --raw | jq -r '.manifests[] | select(.platform.architecture=="arm64") | .digest')
          
          echo "amd64 digest: $DIGEST_AMD64"
          echo "arm64 digest: $DIGEST_ARM64"
          
          docker manifest create ${IMAGE}:${TAG} \
            ${IMAGE}@${DIGEST_AMD64} \
            ${IMAGE}@${DIGEST_ARM64}
          
          docker manifest annotate ${IMAGE}:${TAG} ${IMAGE}@${DIGEST_ARM64} --os linux --arch arm64 --variant v8
          docker manifest annotate ${IMAGE}:${TAG} ${IMAGE}@${DIGEST_AMD64} --os linux --arch amd64
          
          docker manifest push ${IMAGE}:${TAG}

上の方で使ったcowsayのDockerfileをpushして、タグをpushすると自動的にビルドされる。

そしてパッケージとして利用できるようになる。

実際にMacとUbuntuの両方で試して問題ないことを確認できた。

docker run --rm ghcr.io/kun432/docker-multiplatform-build-github-action:v0.0.1
出力
 __
<  >
 --
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

なお、参考にさせていただいた記事ではdocker buildx imagetoolsを使っている様子。

https://docs.docker.com/reference/cli/docker/buildx/imagetools/

サンプルのレポジトリ

https://github.com/kun432/docker-multiplatform-build-github-action

このスクラップは2ヶ月前にクローズされました