goプログラムのマルチプラットフォームコンテナイメージをちょっと早く作る

3行要約

  • Docker Buildxを使うとステージごとに実行するプラットフォームを変えられる
  • のでビルドはネイティブマシンで実行して、実行するイメージを作るところだけをQEMUを使うと良さそう
  • Goだとクロスコンパイルができるのでネイティブマシンでビルドをすることにした

目次です。

イントロ

マルチプラットフォームイメージ使っていますか?

Dockerが提供するイメージはマルチプラットフォームイメージに対応していて、例えばUbuntu*1では次のプラットフォームで同じタグを使うことができます。

こうすることで別のアーキテクチャのマシンで同じタグのコンテナを動かすことができます。嬉しい。

Dockerを用いる場合、Docker Buildxを使って複数のプラットフォームに対応したイメージを作ることができます。 しかしGitHub Actionsでコンテナイメージを作成するときは、GitHub Actionsのランナーがamd64のものしか存在しないため別のアーキテクチャのイメージを作るときはQEMUを使うことになります。

でもQEMUは遅いです。エミュレータなので仕方なし。

ということでイメージのビルドを高速にしたいならばQEMUをなるべく使わない方向に持っていくのが良さそうです。 実際に今回取った手法だとQEMUの使用を最小限に押さえることができます。

ステージごとに実行する環境を変える

ちゃんと公式ドキュメントにも記述があります。

docs.docker.com

Using a stage in Dockerfile to cross-compile to different architectures

とあります。

そして続くドキュメントには以下の例が掲載されています。

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
FROM alpine
COPY --from=build /log /log

# コードブロック内は https://docs.docker.com/build/building/multi-platform/ より引用

FROM --platform=$BUILDPLATFORMというのがミソで、platformの指定をすることによりQEMUの使用を回避することができます。

完成品

FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
WORKDIR /app
ARG TARGETOS
ARG TARGETARCH
ENV GOOS=${TARGETOS}
ENV GOARCH=${TARGETARCH}
COPY . .
RUN go build -o app .
FROM gcr.io/distroless/base-debian12:nonroot AS runner
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]

ドキュメントにあった例をgoのアプリケーションに適用しました。そうするとこのようなDockerfileになります。

goではビルド時にGOOS, GOARCH環境変数を指定することにより他環境で動くバイナリを生成することができます。今回はその仕組みを活用しました。

終わりに

QEMU をできる限り使わずに別プラットフォームで動くコンテナイメージを作成する方法を紹介しました。

この方法だとイメージの作成時間を短縮できます。

今回はgoの例を上げましたが、Rustのようなクロスコンパイルが出来る言語やjavascalaのようなjvmで動く言語で書かれたアプリケーションなら同じように解決できるのではないかと考えています。