x86_64のWSL2でRaspberry Pi OS(aarch64)向けコンテナをビルド

x86_64 な Windows 11 の WSL2 上の Docker で、aarch64 な Raspberry Pi OS 上の Docker 向けコンテナをビルドする手順です。

基本的に [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)をなぞった内容で、ポイントは以下の通り。

```shell-session
wsl2$ docker run --privileged --rm tonistiigi/binfmt --install all
wsl2$ docker build --platform linux/arm64 -t alpine_aarch64 .
```

## WSL2 側の準備

#### Docker Engine のインストール

1. Ubuntu(Latest を対象) をインストール。インストール済みなら次へ
2. Install Docker Engine on Ubuntu の [apt repository からのインストール](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) に従って Docker Engine をインストール
    * 一般ユーザーで `docker` コマンドを使いたいなら `sudo usermod -a -G docker $USER` を実行
3. `docker run --rm hello-world` で動作確認

扱えるアーキテクチャは `docker buildx ls` で確認できます。
この時点では x86 アーキテクチャのみです。

```shell-session
wsl2$ docker buildx ls --no-trunc
NAME/NODE     DRIVER/ENDPOINT   STATUS    BUILDKIT   PLATFORMS
default*      docker
 \_ default    \_ default       running   v0.22.0    linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386
 ```

#### Multi-platform builds

Multi-platform builds の [QEMU](https://docs.docker.com/build/building/multi-platform/#qemu) に沿って binfmt をインストール。

```shell
docker run --privileged --rm tonistiigi/binfmt --install all
```

```shell-session:実行例
wsl2$ docker run --privileged --rm tonistiigi/binfmt --install all
installing: mips64le OK
installing: mips64 OK
installing: ppc64le OK
installing: riscv64 OK
installing: arm OK
installing: s390x OK
installing: loong64 OK
installing: arm64 OK
{
  "supported": [
    "linux/amd64",
    "linux/amd64/v2",
    "linux/amd64/v3",
    "linux/amd64/v4",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/loong64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "WSLInterop",
    "WSLInterop-late",
    "qemu-aarch64",
    "qemu-arm",
    "qemu-loongarch64",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}
```

再度 `docker buildx ls` をすると、扱えるアーキテクチャが増えていることが確認できます。
Raspberry Pi OS(64-bit) の `aarch64` は `linux/arm64` です。

```shell-session
wsl2$ docker buildx ls --no-trunc
NAME/NODE     DRIVER/ENDPOINT   STATUS    BUILDKIT   PLATFORMS
default*      docker
 \_ default    \_ default       running   v0.22.0    linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/loong64, linux/arm/v7, linux/arm/v6
```

#### ビルドしてみる

```Dockerfile:Dockerfile
FROM alpine
```

まずはデフォルトとなる x86 アーキテクチャ向けでビルド。

```shell-session
wsl2$ ls
Dockerfile
wsl2$ docker build -t alpine_x86 .
wsl2$ docker run --rm alpine_x86 uname -m
x86_64
```

aarch64 アーキテクチャ向けでビルド。
※ `docker run` の際に `--platform` をつけ忘れるとWARNINGが出ます（が、実行してくれます）

```shell-session
wsl2$ docker build --platform linux/arm64 -t alpine_aarch64 .
wsl2$ docker run --platform linux/arm64 --rm alpine_aarch64 uname -m
aarch64
```

### コンテナをアーカイブ

Raspberry Pi に持ち込むため、コンテナをアーカイブします。
※ `docker push` とかリポジトリ経由でもいいです

```shell
docker save alpine_aarch64 | gzip - > alpine_aarch64.tar.gz
```

`alpine_aarch64.tar.gz` を Raspberry Pi に（何らかの方法で）コピーします。

## Raspberry Pi 側の準備

#### Docker Engine のインストール

1. Docker Engine は [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script) に沿って `get-docker.sh` を実行してインストール
    * 一般ユーザーで `docker` コマンドを使いたいなら `sudo usermod -a -G docker $USER` を実行
2. `docker run --rm hello-world` で動作確認

扱えるアーキテクチャは `docker buildx ls` で確認できます。
当然ながら Arm アーキテクチャが扱えます。

```shell-session
rpi$ docker buildx ls --no-trunc
NAME/NODE     DRIVER/ENDPOINT   STATUS    BUILDKIT   PLATFORMS
default*      docker
 \_ default    \_ default       running   v0.22.0    linux/arm64, linux/arm/v7, linux/arm/v6
 ```

`alpine_aarch64.tar.gz` を取り込んで実行してみます。

```shell-session
rpi$ cat alpine_aarch64.tar.gz | gunzip -c | docker load
a16e98724c05: Loading layer [==================================================>]  8.461MB/8.461MB
Loaded image: alpine_aarch64:latest
rpi$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED        SIZE
alpine_aarch64   latest    df9cbdbfb963   3 months ago   8.17MB
rpi$ docker run --rm alpine_aarch64 uname -m
aarch64
```

無事、WSL2 側でビルドした aarch64 向けコンテナが、Raspberry Pi 上で実行できました。

#### Raspberry Pi OS(64-bit) 上で x86 向けコンテナをビルドする

逆のパターンです。
といっても、Raspberry Pi OS 上で `docker run --privileged --rm tonistiigi/binfmt --install all` をすれば `--platform linux/amd64` が使えるようになります。

## トラブルシュート

### `docker run --platform ..` で `exec format error` 

```shell-session
wsl2$ docker run --platform linux/arm64 --rm alpine_aarch64
exec /bin/uname: exec format error
```

こうなった場合は、再度 `docker run --privileged --rm tonistiigi/binfmt --install all` を実行します。

```shell-session
wsl2$ docker run --platform linux/arm64 --rm alpine_aarch64
exec /bin/uname: exec format error
wsl2$ docker run --privileged --rm tonistiigi/binfmt --install all
 ...
wsl2$ docker run --platform linux/arm64 --rm alpine_aarch64
aarch64
```

`tonistiigi/binfmt` は QEMU に対して透過的にアクセスできるようにします。
この状態はホスト側の `/proc/sys/fs/binfmt_misc/` 内の `qemu-(arch)` ファイルで確認できます。ざっくり言うなら、このファイルがあれば `--platform` オプションが機能するという理解でOKです([詳細](https://en.wikipedia.org/wiki/Binfmt_misc)はこちら)。

```shell-session
wsl2$ ls /proc/sys/fs/binfmt_misc/qemu-*
ls: cannot access '/proc/sys/fs/binfmt_misc/qemu-*': No such file or directory
wsl2$ docker run --privileged --rm tonistiigi/binfmt --install all
 ...
wsl2$ ls /proc/sys/fs/binfmt_misc/qemu-*
/proc/sys/fs/binfmt_misc/qemu-aarch64      /proc/sys/fs/binfmt_misc/qemu-mips64el
/proc/sys/fs/binfmt_misc/qemu-arm          /proc/sys/fs/binfmt_misc/qemu-ppc64le
/proc/sys/fs/binfmt_misc/qemu-loongarch64  /proc/sys/fs/binfmt_misc/qemu-riscv64
/proc/sys/fs/binfmt_misc/qemu-mips64       /proc/sys/fs/binfmt_misc/qemu-s390x
wsl2$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: POCF
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
```

このファイルは再起動後には無くなります。そのため必要時に `docker run --privileged --rm tonistiigi/binfmt --install all` を実行してください。

EoT