ARM環境のRaspberry Pi OSイメージをDockerで動かす

※2019/6/24 buster対応により、imgファイルのbootパーティションのサイズが変わったため変更
※2024/12/27 bookworm版の作成について追記。また、Apple silicon Mac上でRaspberry Pi OS(64-bit)を動かす場合、ネイティブ動作になることを追記。

# はじめに

Raspberry Piを深く触っていると、実機上の作業では時間がかかりすぎて耐えられないことはないでしょうか。(ただし、最近は一時的にRaspberry Pi 5上で動かせば良いことも多いです)
例えば…

- Qt等、巨大なライブラリの最新版がどうしても必要となり、実機上でコンパイルしたら終わりそうにない or エラーのモグラ叩き(しかもスペックが貧弱なので、エラーが数時間おきで発生し収束しない)に陥ることが多々あります
  - パワフルなマシン上で、実機と同じ仮想環境を構築してコンパイルしたい
  - クロスコンパイルだと設定が複雑なので、時間は少しかかってもいいから単純にやりたい
- 実機と同じアーキテクチャ上の仮想環境で、実機より効率よく開発したい

ということで、お手持ちのPCをビルド環境や開発環境にするため、ARMアーキテクチャで動くRaspberry Pi OSイメージをDockerで動かす手順をまとめました。

ホスト機と仮想環境のアーキテクチャが異なる場合はqemuのシステムエミュレーションを使うので、ホストPCのスペックと比べたら動作は全体的に遅くなります。ターゲットとしているRaspberry Pi(zero等)の初期セットアップ後、SDカードをRaspberry Pi 5に差し込み、こちらで動かした方がシンプルだったりもします。

本頁は、以下のサイトの手順を簡易にしたものになります
http://blog.guiraudet.com/raspberrypi/2016/03/03/raspbian-image-for-docker.html

また、Dockerの基本的な操作は割愛します。コンテナの起動/ログイン/終了といった手順は別途お調べください。

# ざっくりとした手順
- Dockerをインストール
- Linux環境を用意 (上記のDocker上でも別マシン上でも良い)
- Linux環境でRaspberry Pi OSイメージを取得し、イメージファイルを展開
- ~~展開先にqemu-arm-staticを入れる~~ (不要です)
- tarで固め、Dockerのホスト環境へ移動
- Dockerイメージを作成
- Dockerを起動

なお、筆者が確認した環境ですが、
- 2019年記載時：Host環境はmacOS Mojave(x86_64)、Linux環境はDocker上のDebian(x86_64)、Raspberry Pi OSは32bit版(armhf)です。
- 2024年記載時：Host環境はmacOS Sequoia(arm64)、Linux環境はDocker上のDebian(arm64)、Raspberry Pi OSは64bit版(aarch64=arm64)と32bit版(armhf)です。

# 詳細な手順

## Dockerのインストール
Homebrewを使ったインストール方法は以下ですが、割愛します。
```console
$ brew install --cask docker
```

## Linux環境を用意
ここも詳細は割愛しますが、上述のDockerにDebianを入れた前提で記載を進めます。
別環境で行う場合、特権が必要なコマンドは頭にsudoをつけてください。

Dockerで行うときはイメージファイルがマウントできるよう、--privilegedオプションをつけてください。

```console
$ docker pull debian
$ docker run -it --privileged debian
(Debian環境にログイン)
#
```

## Linux環境でRaspberry Pi OSイメージを取得し、イメージファイルを展開
lite、desktop、recommended softwareなdesktop、なんでもいいです。
URLは[ダウンロードページ](https://www.raspberrypi.com/software/operating-systems/)を参照してください。
コマンド中の日本語は正しいファイル名に置き換えてください(「ダウンロードしたファイル」、「imgファイル」)

```console
# wget ダウンロードURL

(zipを展開)
$ unzip ダウンロードしたファイル

(imgを展開 ※offsetの箇所の解説は追記予定)
# mkdir image

(stretchまで)
# sudo mount -o loop,offset=$((512*98304)) imgファイル image
(buster)
# sudo mount -o loop,offset=$((512*532480)) imgファイル image
(bookworm)
# sudo mount -o loop,offset=$((512*1056768)) imgファイル image

※リリースによって変動がある模様。開始セクタを調べるには
# fdisk imgファイル
で、pコマンド(printの略)を実行
# Command (m for help): p

Device Boot                                 Start   End     Sectors Size Id Type
2024-11-19-raspios-bookworm-arm64-lite.img1    8192 1056767 1048576 512M  c W95 FAT32 (LBA)
2024-11-19-raspios-bookworm-arm64-lite.img2 1056768 5382143 4325376 2.1G 83 Linux

第2パーティションの開始セクタの数字を使います。この例だと1056768。

```

## ~~展開先にqemu-arm-staticを入れる~~
不要になりました。
```console
# apt-get -y install qemu-user-static
# mv image/etc/ld.so.preload image/etc/ld.so.preload.bak
# cp /usr/bin/qemu-arm-static image/usr/bin
```

## Dockerイメージを作成

いつ基準のものかをファイル名のYYYY-MM-DDにつけておくとあとでわかりやすいと思います。不要な場合は除去してください。

```console
# cd image
# tar cf ../docker-image-YYYY-MM-DD-raspios.tar .

何らかの手段でホスト環境に上記tarファイルをコピー

(Linux環境を抜けます)
# exit

(ホスト環境にて)
$ docker import docker-image-YYYY-MM-DD-raspios.tar raspios:YYYYMMDD
```

## Dockerを起動

ホスト環境にて実施します。docker run時は必要に応じてオプションをいじってください。下記の例ではポート5901番で起動したVNCに接続可にしてあります。

```console
$ docker run -p 5901:5901 -it raspios:YYYYMMDD /bin/bash
```

無事起動したら、`raspi-config`でタイムゾーン等を変更後、いつもの通り`apt-get update`、`apt-get upgrade`にて、環境が出来上がります。
ユーザpiで作業したい場合は、以下のようにします。

```console
# su pi
$ cd
(以下、/home/pi下で作業)
```
また、CPUやメモリをどのくらい割くかはDockerのオプションで指定してください。
CPU8コアメモリ4GBとか、贅沢な環境が手に入ります。

bookwormを動かす場合、kernelのアップデートの際に実行されるinitramfsの更新で異常終了してしまうので、あらかじめ以下のパッケージは削除した方が良さそうです。

```console
$ sudo apt remove linux-image* linux-headers* initramfs-tools
$ sudo apt auto remove
```

## 普段の運用

詳細は割愛しますが、以下の繰り返しになります。stopしないと裏でずっと回ってくれます。VNCを併用するのもいいと思います。
上記の手順を辿るとコンテナ名は不定になりますが、`docker rename (現在のコンテナ名) (新しいコンテナ名)` で変更できます。

```console
$ docker start (コンテナ名またはコンテナID)
$ docker attach (コンテナ名またはコンテナID)
(ログイン)
#
(作業)
# exit
(ログアウト)
$ docker stop (コンテナID)
```

## Apple siliconについて (2024/12/27追記)

Apple siliconを搭載したMacはarm64アーキテクチャで動いているので、Raspberry Pi OS(64-bit)のコンテナはネイティブの速度で動きます。(M4 Pro上で公式Debian arm64コンテナと比較)

Qt base 6.8.1のビルド時間の比較で検証しました。手順は [tal.org](https://www.tal.org/tutorials/building-qt-66-raspberry-pi-raspberry-pi-os) を参照し、必要最小限の範囲で行いました。(configure optionに `-DINPUT_opengl=no` を追加)

```console

$ cmake -G Ninja -DCMAKE_INSTALL_PREFIX=(インストール先) -DCMAKE_BUILD_TYPE=Release -DINPUT_opengl=no -DBUILD_PrintSupport=OFF -DBUILD_Testlib=OFF -DQT_BUILD_TESTS=OFF -DQT_BUILD_EXAMPLES=OFF -DQT_AVOID_CMAKE_ARCHIVING_API=ON (qtbase-everywhere-srcのフォルダ)

$ time cmake --build . --parallel
$ cmake --install .
```

| 環境 | 所要時間 [min:sec] |
| ---- | ---- |
| Raspberry Pi OS 64-bit (on Raspberry Pi 5 4CPU&8GB)| 26:20 |
| Docker: Debian arm64 (on M4 Pro 12CPU&8GB)| 2:26 |
| Docker: Raspberry Pi OS(64-bit) (on M4 Pro 12CPU&8GB)| 2:30 |
| Docker: Raspberry Pi OS(32-bit) (on M4 Pro 12CPU&8GB)| 27:22 |

Raspberry Pi Zero 2, Raspberry Pi 3, 4の64bit環境に対するビルド環境として使うなら大いに恩恵がありそうです。
逆にRaspberry Pi Zero (32bitのみ)向けなら、わざわざコンテナを作らずRaspberry Pi 5上で行った方が良さそうです。