Docker におけるCPUアーキテクチャ選定を理解する

# 目的

Docker のアーキテクチャ選定をしっかり理解して、ビルドエラー/実行時エラーを回避する。

# 状況

Docker イメージを作成する際に、CPUアーキテクチャ依存のパッケージをインストールしなければいけないことがある。

「他の人は動いているように見えるのに、自分の環境だとビルド時のパッケージのインストールに失敗する...（あるいはビルドは通っても実行時に謎のエラーになる...）」みたいな時は、パッケージが依存しているアーキテクチャと異なるアーキテクチャでビルドしている可能性がある。

その場合、ビルド時に適切なアーキテクチャを指定する必要がある。

# 基本的にはベースイメージのアーキテクチャが引き継がれる

まず基本的な考え方として、**ビルドしたイメージのアーキテクチャはベースイメージのものと同じになる。**
つまり、`FROM ~~~` で指定したベースイメージのアーキテクチャが AMD64 であればビルドしたイメージも AMD64 になる。

# ビルド時のマシンのアーキテクチャが考慮される

**Docker はデフォルトでビルド時のマシンのアーキテクチャに対して最適なイメージを pull してくる。**

多くのメジャーなイメージは、マルチプラットフォームに対応している。
例えば ubuntu の公式イメージの「OS/ARCH」欄を見ると複数のプラットフォーム用のイメージがあることが分かる。

https://hub.docker.com/_/ubuntu/tags

![スクリーンショット 2024-11-16 1.37.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/22fae9cc-27cc-6915-3f2c-63aa92f7d3ea.png)


さてそれでは、自分の macOS マシンでシンプルに ubuntu の latest を pull すると、一体なんのアーキテクチャに対応したイメージがやってくるのだろうか。

:::note info
Apple Silicon の macOS のアーキテクチャは ARM64。
（Intel のチップが入っていた時代の macOS は AMD64）
:::

```shell
$ docker pull ubuntu:latest

# イメージの情報を確認するコマンド
$ docker image inspect ubuntu:latest --format '{{.Architecture}}'
arm64
```

arm64 と出た。ホストマシンである macOS と同じアーキテクチャのイメージが自動で選ばれている。

つまり、**マルチプラットフォームに対応したベースイメージを使って何の気無しに自分のマシンでビルドすると、自分のマシンのアーキテクチャのイメージがビルドされるということになる。**

（当然、マルチプラットフォーム対応イメージだとしても、自分のマシンのアーキテクチャと同じものがなければ話は違う）

Dockerfile の中でインストールしているパッケージの依存アーキテクチャが自分のマシンと同じであれば特に問題はないが、違った場合には、ビルドがコケたり実行できなかったりするだろう。

# 明示的に指定することで `pull` を制御する

これを明示的に指定すると、別のイメージを pull することもできる。:arrow_down:

```shell
$ docker pull --platform linux/amd64 ubuntu:latest

# イメージの情報を確認するコマンド
$ docker image inspect ubuntu:latest --format '{{.Architecture}}'
amd64
```

イメージのリストには以下のように表示されており、アーキテクチャ違いのイメージが2個（arm64とamd64）あることが分かる。

```shell
$ docker images | grep ubuntu
ubuntu  <none>  f825f99d3d8a  5 weeks ago  101MB
ubuntu  latest  59ab366372d5  5 weeks ago  78.1MB

$ docker image inspect f825f99d3d8a --format '{{.Architecture}}'
arm64
$ docker image inspect ubuntu:latest --format '{{.Architecture}}'
amd64
```

`pull` せずに、`build` コマンドで指定しても良い。

```shell
# 一旦 ubuntu のイメージをローカルから全て消した上で

# ベースイメージだけを指定した Dockerfile を用意
$ cat Dockerfile
FROM ubuntu:latest

$ docker build --platform=linux/amd64 . -t test
# ベースイメージの pull が自動で走ってビルドが完了

$ docker image inspect test --format '{{.Architecture}}'
amd64
```

ちゃんと AMD64 のイメージがビルドされた。

ちなみにこの `build` 時の `--platform` の指定はあくまでもベースイメージを `pull` するときの指定であって、**「ARM64 のベースイメージを AMD64 のイメージに変更する」みたいなアーキテクチャの変更をするわけではない。**

例えば、以下のような AMD64 のみ対応のイメージをベースイメージにして、`--platform=linux/arm64` （対応していないアーキテクチャ）を指定してビルドしてみる。

https://hub.docker.com/r/buildkite/puppeteer/tags

![スクリーンショット 2024-11-16 2.19.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/b94a0ef1-4bdc-041d-ec6f-49622494c602.png)


```shell
$ cat Dockerfile
FROM buildkite/puppeteer:latest

$ docker build --platform=linux/arm64 . -t test
（中略）
1 warning found (use docker --debug to expand):
 - InvalidBaseImagePlatform: Base image buildkite/puppeteer:latest was pulled with platform "linux/amd64", expected "linux/arm64" for current build (line 1)
```

さて、ビルドは成功したが、何やら warning が出ている。

> Base image buildkite/puppeteer:latest was pulled with platform "linux/amd64", expected "linux/arm64" for current build (line 1)

「linux/arm64 が指定されたけど、実際には linux/amd64 のイメージが pull されたよ。」とのこと。
arm64 を指定したところで、無いものを引っ張ってくることは出来ない。
上で書いた通りで、`--platform`はあくまでも `pull` の指定であり、アーキテクチャの変更を行うわけではないということが分かる（そんなことはそもそも出来ない）。

# ベースイメージに、必要なアーキテクチャが無い場合

これは必要なアーキテクチャがあるイメージに変えるしかない。


# まとめ

- イメージのアーキテクチャはベースイメージのアーキテクチャで決まる。
- `pull` や `build` の `--platform` では、ベースイメージを `pull` する際のアーキテクチャを指定できる。
  - あくまでも `pull` の指定なので、必ずそのアーキテクチャのイメージが作れるわけではない。