🐳

docker-buildxとmulti-platform build周りについてまとめ

2022/11/20に公開

最近docker buildxを使ったmulti-platform build周りについての知見がある程度溜まってきたので必要そうな情報をまとめておく。
buildx自体が実際に使うとハマりどころが多いので、すんなりと納得できるような文章がかけてないとは思うけど、実際に触る人がハマったり疑問に思ったりする内容の穴埋めはある程度できてるとは思ってる。

ちなみにこの記事を書いてる時点のdocker-buildxの最新バージョンがv0.9.1なので、貼ってあるbuildxのリンクについては基本このバージョンのものになる。

docker-buildxってなに?

リポジトリを見ると

docker buildのようなUI
コンテナードライバーによる完全な BuildKit 機能
複数のビルダーインスタンスのサポート
クロスプラットフォーム イメージのマルチノードビルド
Compose ビルドのサポート
高レベルのビルド構造 (bake)
コンテナー内ドライバーのサポート (Docker と Kubernetes の両方)

と書かれていて「?」ってなってしまうけど、一般的な利用用途としてはコンテナイメージを作成する際にamd64アーキテクチャ向けのイメージだけじゃなくて、arm向けのイメージとかも作りたいときに使うものだと思えばOKだと理解してる。
詳しく知りたい人は下記のリポジトリのドキュメントを読むと良さそう。

https://github.com/docker/buildx

インストール方法

https://docs.docker.com/build/buildx/install/

インストール方法については、このリンク先に書いてある通りDocker Desktopを使っていれば始めから入っているらしい。
自分の場合はLinux環境で動かすことが多かったので $HOME/.docker/cli-pluginshttps://github.com/docker/buildx/releases/latest にあるバイナリを docker-buildx として配置すればOK。
設置すれば docker buildx サブコマンドが使えるようになる。

なのでLinux環境に手動インストールするには下記のようなコマンドを実行すればインストール完了

$ mkdir -p $(HOME)/.docker/cli-plugins \
  && curl -o $(HOME)/.docker/cli-plugins/docker-buildx \
    https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64 \
  && chmod +x $(HOME)/.docker/cli-plugins/docker-buildx

また、buildxにはイメージビルドを行う際に複数のドライバが選択できるようになっている。

https://github.com/docker/buildx/blob/master/docs/manuals/drivers/index.md

しかし、デフォルトだと docker ドライバが選択されているが、これではmulti-platform buildを行うことができないため

$ docker buildx create --use

コマンドを実行して docker-container ドライバを利用するためのインスタンスを作成する必要がある。
(普通にmulti-platformなコンテナイメージの作成を行いたいだけであれば多分 kubernetesremote のドライバを使うことは無い気がしてる)

QEMU

buildxを使って例えばamd64環境でarm向けのイメージを作成する場合は、buildx本体以外にもQEMUというエミュレータをインストールしておく必要があるらしい。
QEMUについて詳しく知りたい人は下記を読むと良さそうだけど、多分buildxについては下記の記事にある user mode emulation を使ってarmなどのCPUアーキテクチャをエミュレーションを行うために利用しているっぽい。

https://www.qemu.org/docs/master/about/index.html

QEMUのインストール方法は下記のコマンドでできる。

$ docker run --privileged --rm tonistiigi/binfmt --install all

一部環境のエミュレータだけで十分な場合は下記のように指定できる。

$ docker run --privileged --rm tonistiigi/binfmt --install linux/amd64,linux/arm64/v8

上記ツールは下記にリポジトリがあるので興味がある人は中を見てみると良さそう。

https://github.com/tonistiigi/binfmt

インストール方法まとめ

Docker Desktopを利用している場合

Docker Desktopを利用している場合は特に設定は不要なので、 docker-container ドライバ用のインスタンスを作成するだけでOK。

$ docker buildx create --use

手動インストールする場合

Dockerをすでにインストールしている前提で下記コマンドを実行する。
(インストールするdocker-buildxバイナリは自分の環境向けのものに書き換えて実行する)

$ mkdir -p $(HOME)/.docker/cli-plugins \
  && curl -o $(HOME)/.docker/cli-plugins/docker-buildx \
    https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64 \
  && chmod +x $(HOME)/.docker/cli-plugins/docker-buildx
$ docker buildx create --use
$ docker run --privileged --rm tonistiigi/binfmt --install all

multi-platform向けのDockerfileの書き方

構築するイメージによっては特に意識することは無い場合もあるが、例えばGoのようにバイナリをビルドして、それを成果物のコンテナにインストールしたい場合だと下記のような書き方をすることになる。

FROM --platform=$BUILDPLATFORM golang:1.18-buster AS build-env
ARG TARGETARCH
COPY . /workdir
WORKDIR /workdir
RUN GOARCH=${TARGETARCH} go build -o output .

FROM ubuntu:18.04
COPY --from=build-env /workdir/output /output
ENTRYPOINT ["/output"]

上記の例だと build-env にイメージで TARGETARCH アーキテクチャ向けのバイナリをbuildして、それを ubuntu:18.04 のイメージにインストールしたコンテナをビルドすることになる。
上記Dockerfileに対して下記のコマンドで指定したmulti-platform毎のイメージをbuild~pushできる。

$ docker buildx build --no-cache --push \
  --platform linux/amd64,linux/arm64/v8 \
  -t some-image:latest \
  .

また TARGETARCHBUILDPLATFORM などはbuildxが自動的に設定する変数になるが、これらは下記のリンクで一覧がまとめられている。

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

また下記のドキュメントも参考になると思う。

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

docker-buildxを使ったビルド

上記の docker buildx build の例でも --push オプションを付けて実行しているが、オプション名の通り上記コマンドを実行するとイメージのpushも同時に実行されることになる。
ただちょっとbuildを試したいだけのときに単純に --push オプションを外しても、今度はbuild自体が実行されないという問題があり、こんな感じで docker buildx build コマンドは通常の docker build に比べてクセが少し強いので docker buildx build を使ったbuildのためのコマンドのパターンをいくつか紹介する

ローカル向けビルド

1つ目は下記のように --local オプションを付けて実行するパターン。

$ docker buildx build --no-cache --local -t some-image:latest .

これを実行すると実行結果のイメージがローカルのレジストリに保存されるので docker run some-image:latest のように実行することができる。
ただし --local オプションを付けて実行した場合は --platform linux/amd64,linux/arm64/v8 のようなオプションを付けたmulti-platform buildを行うことができない。
そのため --local オプションを付けて実行するのはローカルでの開発環境向けで実行するケースが多くなると思われる。
手元でbuild ~ イメージを実行して動作確認を行う、といったケースではこの方法でイメージをbuildするのが良さそうに思える。

build ~ push

buildしたイメージを実際にpushまで行う場合では

$ docker buildx build --no-cache \
  --push \
  --platform linux/amd64,linux/arm64/v8 \
  -t some-image:latest \
  .

のように --push オプションを付けて実行すればmulti-platformなイメージのbuild~pushができる。
例えばDokcer Hubにpushした場合は下記画像のように OS/ARCH のところに複数のイメージが登録されていることが確認できる。

multi-platform buildしたイメージをpushしたくない場合

例えばmulti-platform buildがちゃんとできるのかを確認したい場合などは

$ docker buildx build --no-cache \
  -o type=tar,dest=some-image.tar \
  --platform linux/amd64,linux/arm64/v8 \
  -t some-image:latest \
  .

などのようにtarで成果物を出力するように設定しても実行が可能なようなので、ローカルで動作確認を行いたい場合はこんな感じで対応している。
このあたりのtipsとかいい感じの方法を知ってる人がいれば是非教えてもらいたい。

ちなみに docker buildx build のオプションなどをWebで確認したい場合は下記で見ることができる。

https://github.com/docker/buildx/blob/v0.9.1/docs/reference/buildx_build.md

Dockerfile & buildまとめ

  • docker buildx build multi-platform向けのイメージ操作を行う関係かbuild ~ pushがまとめて行われる仕組みになっている
  • --platform linux/amd64,linux/arm64/v8 のように --platform をつけることでmulti-platform build対象のプラットフォームを設定可能
  • ローカル向けにビルドする場合は --local オプションを使えば良いがmulti-platform buildができないことに注意
  • multi-platform buildを行いたい場合は --push オプションを付けてpushまで行うか -o type=tar,dest=some-image.tar などをつけてbuildxがmulti-platform buildを行ってくれる条件を整えてあげる必要あり
  • buildxを利用した際にDockerfileで利用できる変数がここのリンクにあるので生成するバイナリなども対象のplatform向けに生成するようにDockerfileを書く必要あり

イメージタグを設定する場合

multi-platformなイメージにタグを設定する場合 docker tag コマンドだと、実行している環境向け(amd64とかarmとか)のイメージにしかタグを設定することができないという問題があり、別アプローチを取る必要があるためこれを紹介する。

docker buildx build でタグを設定する

docker buildx build では下記のように -t オプションで複数のタグを設定することができるので、複数のタグを設定する必要がある場合はこのように設定するのがおそらく一般的なのだと理解している。

$ docker buildx build --no-cache --push \
  --platform linux/amd64,linux/arm64/v8 \
  -t some-image:latest \
  -t some-image:v1.0.0 \
  .

docker buildx imagetools create でタグを設定する

docker buildx imagetools create というmulti-platformイメージ向けにタグを設定するサブコマンドがあるのでこれを利用するという方法もある。

https://docs.docker.com/engine/reference/commandline/buildx_imagetools_create/

この方法だと一度作ってからpushしたイメージに後からタグを追加できるので、後から追加する必要がある場合はこのコマンドを利用することになる。
(というよりもこの他の方法を知らないので、知ってる人いたら教えて)

$ docker buildx imagetools create --tag some-image:latest some-image:v1.0.0 

のように

  • --tag オプションにベースとなるイメージタグを設定
  • 引数に新規に設定したいイメージタグを設定

することで新たにタグを設定 ~ pushまでを実行してくれる。

Github Actionsでmulti-platform buildイメージをpushする

Github Actionsで実際にイメージをpushする際はGithub Actionsの各Actionを組み合わせるパターンだとこんな感じに書くことができる

name: ci
on:
  push:
    branches:
      - 'main'
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Login to DockerHub
        uses: docker/login-action@v2 
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: user/app:latest

https://github.com/docker/buildx/blob/v0.9.1/docs/guides/cicd.md

上記で紹介している各Actionのリポジトリはそれぞれ下記にある

各アクションでそれぞれどんなことを行っているかについては下記のようになる

docker/setup-qemu-action

他のActionも含めてサクッと眺めるくらいなら簡単なので軽く見ていると良いと思うけど、(オプションで若干変わるけど)要は下記のコマンドを実行しているだけ。

$ docker run --privileged --rm tonistiigi/binfmt --install all

https://github.com/docker/setup-qemu-action/blob/v2.1.0/src/main.ts#L10-L53

docker/setup-buildx-action

これもだいたい下記のコマンド実行しているのとあんまり変わらない

$ mkdir -p $(HOME)/.docker/cli-plugins \
  && curl -o $(HOME)/.docker/cli-plugins/docker-buildx \
    https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64 \
  && chmod +x $(HOME)/.docker/cli-plugins/docker-buildx
$ docker buildx create --use

例えばインストール処理はこんな感じ

https://github.com/docker/setup-buildx-action/blob/v2.2.1/src/buildx.ts#L240-L262

docker/login-action

これに至ってはほぼ docker login してるだけ(ECRサポートとかもしてるみたいだけど)

docker/build-push-action

これもほぼ docker buildx build してるだけ

https://github.com/docker/build-push-action/blob/master/src/main.ts#L44-L54

Github Actionsまとめ

ということで各Actionはそれぞれかなり単純なコマンドを実行してるだけなので、Actions使っても良いし柔軟に設定したりローカルでも実行できるようにしたい場合はMakefileなどでタスクを書いてそれを実行するのでも良いと思う。
このあたりはどんな感じに設定したいかってことと、あとは趣味の話しだったりするので、便利な範囲でそれぞれ好きに使えば良さげ。

buildx周りの情報の調べ方

  • ブログ記事を探す: 公式情報がまとまってなかったり少なかったりするので有用
  • 公式ドキュメントを見る: buildxについてはあまり触れられてないので公式ドキュメントだけだと厳しい
  • buildxのリポジトリ: buildxについてはこのリポジトリが一番情報が多い気がするので基本ここを見るのが良いと思う(一部は公式ドキュメントにしか書いてないこともある)
  • BuildKitのリポジトリ: buildxはBuildKitを利用しているのでBuildKit側の情報が役に立ったりもした

まとめ

multi-platformなイメージの作成・管理のために普通にdocker使うのと比べると色々と制約が増えているので慣れるまでには少し時間がかかる印象。
(buildx周りのドキュメントもあまり充実していないし、使ってる人も少ないので調べるのもいちいち時間かかる)
なのでmulti-platformなイメージ作成の要件とかが無いのであれば普通に今まで通り docker build を使った方が楽だと思った。

あと全然関係ないけど、buildxとかBuildKitのリポジトリを見てて(当たり前っちゃ当たり前なのかもだけど)Akihiro Sudaさんのcommitがちょいちょい出てきて「さすがだー」って気持ちになった。

そしてBuildKitは結構内部実装に関する解説資料があるんだけど、buildxはそこらへんが全然無いのでどっかに資料ないかなーと思ってる。

オマケ

調べる過程のメモとか消化しきれてないことのメモです。

BuildKit内部アーキテクチャ

https://docs.google.com/presentation/d/1tEnuMOENuoVQ3l6viBmguYUn7XpjIHIC-3RHzfyIgjc/edit#slide=id.g12ee78aa779_0_309

上記スライドにBuildKitのアーキテクチャが解説されてたのでちょっと興味持った。
今度暇なときがあれば見てみたい。

また下記ページでライフサイクルが図で紹介されている。

https://github.com/moby/buildkit/blob/master/docs/dev/request-lifecycle.md

LLB

アーキテクチャのスライドでも出てきてたけどBuildKitはDockerfileのデータなどをLLBという中間データフォーマット?に変換して処理をするような仕組みらしいので、これも暇があればこのあたりの仕組みを調べてみたい。

https://github.com/moby/buildkit/blob/master/docs/dev/dockerfile-llb.md

でDockerfileがLLBに変換する処理について解説されているっぽい。

tonistiigi/xx

https://github.com/tonistiigi/xx

これがBuildKitの内部で使われてるってことでいいんだろうかーとかそもそもこれって何なのかとかが調べきれてなくて消化不良な感じ。

その他参考リンク

https://blog.mobyproject.org/introducing-buildkit-17e056cc5317

https://medium.com/@tonistiigi/faster-multi-platform-builds-dockerfile-cross-compilation-guide-part-1-ec087c719eaf

Discussion