RaspberryPiでdocker構築に時間がかかった時のtips

ラズパイでdockerを構築したときに一日かかったので、どうどのような解決法があるか試行錯誤した結果を忘備録として残します。結論から言うと**piwheel.orgのレポジトリを使ってください**。それでも解決しない場合はラズパイのスペックが足りないので良いハードを買いましょう。`buildx`を活用する手も考えられますが未検証です。以下の記述は2021年3月執筆時点の情報です。

この記事はDockerをラズパイで動かそうシリーズ（？）の一つです

- [Raspberry Pi 4でUbuntuサーバーの初期設定を行う](https://qiita.com/k_ken/items/9a71655400798f84d636)
- [RaspberryPiにDockerを構築する(Ubuntu編)](https://qiita.com/k_ken/items/72fe1b86dda61ed34604)
- [ラズパイでdocker構築に時間がかかった時のtips](https://qiita.com/k_ken/items/1e5ec65554b5adfe22bf/)←今ここ

### 更新履歴
-  2021年03月07日 初版投稿
-  2021年04月24日 MacOS,Windows上でのDockerのメモリ制限について追記

## 背景
ラズパイで`numpy`や`pandas`が入った環境を構築したらビルド完了するのに半日がかかったので、なんとかならないか色々調べてみました。

以下の環境で行いました。

- Raspberry pi 4B 8GB RAM

| ソフト | バージョン |
|:-:|:-:|
|  Ubuntu | 20.04.2 LTS (64bit) |
| git | 2.25.1 |
|  docker | 20.10.5, build 55c4c88 |
| docker-compose | 1.28.5 |

- MacBook Pro (2017) / `buildx`実行環境

| ソフト | バージョン |
|:-:|:-:|
|  macOS | Big Sur(V11.2.2) |
|  docker | 20.10.2, build 2291f61 |
| docker-compose | version 1.27.4, build 40524192 |

## 解決策1:時間が解決する
対処でもなんでもないですが、一番確実な方法。
下記のように`nohup`コマンドを使用すれば端末を閉じてもログアウトしても処理が続きます。

```
sudo nohup docker-compose build &
```

## 解決策2:違うマシンでbuildしラズパイにインポートする
あらかじめ違うマシンでビルドしたのち、ビルドしたイメージをラズパイにインポートします。
下記ではsample-imageというイメージを保存したのち、ラズパイでロードします。
参考：[Dockerでイメージをインポート・エクスポートする | UX MILK](https://uxmilk.jp/55512)

```
# sample-imageをセーブする
docker save sample-image > sample-image.tar
# ラズパイでイメージをロードする
docker load < sample-image.tar
```

ただし、dockerイメージはCPUアーキテクチャ間の互換性がないのでIntel(`x_86`)でビルドしたイメージはラズパイ(`arm/v7`や`arm64`)で使えません。

`Raspberry Pi 3`で構築出来なかったものを`Raspberry Pi 4`でビルドしたのち転送してロードするのは可能です。私の場合はラズパイ3で`pandas`を構築した際に以下のエラーが発生しました。おそらく原因はメモリ不足です。

参考: 

- [gcc: fatal error: Killed signal terminated program cc1 compilation terminated during Docker build - rakhesh.com](https://rakhesh.com/linux-bsd/gcc-fatal-error-killed-signal-terminated-program-cc1-compilation-terminated-during-docker-build/)
- [v1.10.0rc5: Docker build fails with "gcc: fatal error: Killed signal terminated program cc1" · Issue #6889 · matrix-org/synapse](https://github.com/matrix-org/synapse/issues/6889)

```
[[[前略]]]
  gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -DNPY_NO_DEPRECATED_API=0 -Ipandas/_libs/src/klib -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/tmp/pip-build-env-61o34tpt/overlay/lib/python3.7/site-packages/numpy/core/include -I/usr/local/include/python3.7m -c pandas/_libs/join.c -o build/temp.linux-armv7l-3.7/pandas/_libs/join.o
  gcc: fatal error: Killed signal terminated program cc1
  compilation terminated.
  error: command 'gcc' failed with exit status 1
  ----------------------------------------
ESC[0mESC[91m  ERROR: Failed building wheel for pandas
```

ただし上記の方法はSDカードの容量をケチると重たいイメージが書き出せないので、なるべく容量の大きいSDカードを使用しましょう（そもそもラズパイの用途として向いていないと言われるとそれまでですが）

## 解決策3: buildxを使用する

**注意** : 以下の解決方法は試していましたが、私の場合では時間がかかりあまりうまくいきませんでした。

IntelのCPUでもraspberry piのDockerをビルドする方法はあります。Dockerの`buildx`機能を使用することで実現します。

- [Multi-arch build and images, the simple way - Docker Blog](https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/)
- [Dockerの「マルチCPUアーキテクチャ」に対応したイメージをビルドする | DevelopersIO](https://dev.classmethod.jp/articles/docker-multi-architecture-image-build/)
- [Dockerのマルチアーキテクチャイメージについて調べてみた | DevelopersIO](https://dev.classmethod.jp/articles/docker-multi-architecture-image/)

まずは`macOS`か`windows`でdockerを開き[Preferences]>[Experimental Features]から`Enable CLI experimental features`を有効化します。

`mac`の場合「設定」は右上の歯車ボタンを押せば開きます。

![スクリーンショット 2021-03-07 16.31.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235984/684d947f-200c-6dd2-794a-ee1895e71b3e.png)

以下を実行してどのような`builder`があるか調べます

```bash
$ docker buildx ls
NAME/NODE           DRIVER/ENDPOINT             STATUS  PLATFORMS
default *           docker                              
  default           default                     running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
```

`PlATFORMS`の箇所に`linux/arm64`や`linux/arm/v7`が並んでいます。これで`arm`アーキテクチャでビルドできることを示しています。以下のコマンドでは`arm64`をビルドしています。

```bash
docker buildx build \
--platform linux/arm64 \ 
--tag your-username/multiarch-example:buildx-latest .
```

### Dockerのメモリ上限を引き上げよう

ビルド中にエラーが出ていくらググっても解決策が見つからない場合は、とりあえずスペック不足を疑いましょう。`Windows`と`MacOS`はデフォルトだとDocker containerに2GBのメモリ制限があるのでその上限を引き上げてみよう。

- [How to assign more memory to docker container](https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container/44533437#44533437)

### スペックの問題ではない...？

最初、buildに時間がかかるのはスペックの問題だと思っていましたが、私が直面した問題ではどうやら違っていました。以下の場合、`numpy`は1時間以上`pandas`は8時間以上構築に時間がかかっています（下記参照）

```
% sudo docker buildx build --platform linux/arm/v7 .
[+] Building 37461.7s (15/17)                                                                                                                      
 => [internal] load build definition from Dockerfile                                                                                          0.1s
 => => transferring dockerfile: 1.01kB                                                                                                        0.0s
 => [internal] load .dockerignore                                                                                                             0.0s
 => => transferring context: 2B                                                                                                               0.0s
 => [internal] load metadata for docker.io/library/python:3.9.2                                                                               3.4s
 => [ 1/13] FROM docker.io/library/python:3.9.2@sha256:e2cd43d291bbd21bed01bcceb5c0a8d8c50a9cef319a7b5c5ff6f85232e82021                      47.2s
 => => resolve docker.io/library/python:3.9.2@sha256:e2cd43d291bbd21bed01bcceb5c0a8d8c50a9cef319a7b5c5ff6f85232e82021                         0.0s
 => => sha256:2d873e84995af4aea1f14a350eaf612030dbd30d856a1e21052da9332b31d399 8.33kB / 8.33kB                                                0.0s
 => => sha256:4c2a0a79594a20b9c2f0bfbd535f875ca1b079625052cdd801afb1cc0362d6d0 45.87MB / 45.87MB                                             18.7s
 => => sha256:06c11c595f6421f88e1b10286a766ae8db88f67c2c0f41cedd170640aee498ab 7.12MB / 7.12MB                                                2.7s
 => => sha256:e2cd43d291bbd21bed01bcceb5c0a8d8c50a9cef319a7b5c5ff6f85232e82021 2.11kB / 2.11kB                                                0.0s
 => => sha256:47cb22b679409f00e04b8d645444df7181f05cb2967ed73f1a377e7f774b6873 9.34MB / 9.34MB                                                3.5s
 => => sha256:63380245baa70552542d5e2b0debe9e2eb557a1bbedcbbbc08933d5f36ff9cdb 2.22kB / 2.22kB                                                0.0s
 => => sha256:5e90d4e143c069feee70f90eea83d55b7b0761c67fbbfb7f3734c16c0811ac13 47.36MB / 47.36MB                                             27.1s
 => => sha256:3d7064acb6373ca1f0f12ecbbace9c16a2f741e61286783b17096d3db440b91c 168.55MB / 168.55MB                                           39.0s
 => => sha256:8df02116face90b959309647c1079aba91692478f29e5d94d8273fd09a2d5b81 5.54MB / 5.54MB                                               21.2s
 => => extracting sha256:4c2a0a79594a20b9c2f0bfbd535f875ca1b079625052cdd801afb1cc0362d6d0                                                     1.7s
 => => extracting sha256:06c11c595f6421f88e1b10286a766ae8db88f67c2c0f41cedd170640aee498ab                                                     0.3s
 => => extracting sha256:47cb22b679409f00e04b8d645444df7181f05cb2967ed73f1a377e7f774b6873                                                     0.3s
 => => sha256:4c3170a9f3989580585e55075a7fa75d9ef013e2f35f717fad7dc4b3cc7020c4 18.01MB / 18.01MB                                             27.6s
 => => sha256:6abf7991c3a2713d4ec8aeb45f7bf97cb215cba09332474421baf22f1ab31e99 233B / 233B                                                   28.0s
 => => extracting sha256:5e90d4e143c069feee70f90eea83d55b7b0761c67fbbfb7f3734c16c0811ac13                                                     2.2s
 => => sha256:cf4935c4f818ff52eba24266f44e0aaa18dc8ce8eb758afb5dd92c2d00df255e 2.16MB / 2.16MB                                               29.4s
 => => extracting sha256:3d7064acb6373ca1f0f12ecbbace9c16a2f741e61286783b17096d3db440b91c                                                     6.2s
 => => extracting sha256:8df02116face90b959309647c1079aba91692478f29e5d94d8273fd09a2d5b81                                                     0.3s
 => => extracting sha256:4c3170a9f3989580585e55075a7fa75d9ef013e2f35f717fad7dc4b3cc7020c4                                                     0.6s
 => => extracting sha256:6abf7991c3a2713d4ec8aeb45f7bf97cb215cba09332474421baf22f1ab31e99                                                     0.0s
 => => extracting sha256:cf4935c4f818ff52eba24266f44e0aaa18dc8ce8eb758afb5dd92c2d00df255e                                                     0.2s
 => [internal] load build context                                                                                                             0.5s
 => => transferring context: 4.84MB                                                                                                           0.5s
 => [ 2/13] RUN mkdir /app/                                                                                                                   0.5s
 => [ 3/13] ADD requirements.txt /app/                                                                                                        0.0s
 => [ 4/13] RUN apt-get update -y                                                                                                            14.6s
 => [ 5/13] RUN apt-get install vim -y                                                                                                       17.0s
 => [ 6/13] RUN pip install --upgrade pip                                                                                                    17.7s
 => [ 7/13] WORKDIR /app/                                                                                                                     0.0s
 => [ 8/13] RUN pip install Cython numpy                                                                                                   4624.6s
 => [ 9/13] RUN apt-get install build-essential libssl-dev libffi-dev python3-dev cargo -y                                                   57.3s
 => [10/13] RUN pip install pandas                                                                                                        30112.7s
```

そして、なぜかコンパイルに失敗する。正直原因がわからず。

```
#15 214.7     File "/usr/local/lib/python3.8/subprocess.py", line 516, in run
#15 214.7       raise CalledProcessError(retcode, process.args,
#15 214.7   subprocess.CalledProcessError: Command '['cargo', 'metadata', '--manifest-path', 'src/rust/Cargo.toml', '--format-version', '1']' returned non-zero exit status 101.
```

### なぜ時間がかかるのか
[piwheels: making &quot;pip install&quot; fast - Raspberry Pi](https://www.raspberrypi.org/blog/piwheels/)に書かれている内容をざっくり要約するとラズパイは`ARM`アーキテクチャのCPUを使用しており、他のアーキテクチャで作られたwheelと互換性がないため、ソースからコンパイルしている状態とのこと。

## 解決策4:piwheel.orgのレポジトリを使う

解決策としてはあらかじめコンパイル済みのwheelをダウンロードできるレポジトリを使用すれば時間の節約になります。[piwheels.org](https://www.piwheels.org)がコンパイル済みのwheelを提供しており、Raspbianはデフォルトでpiwheelsを参照していますが、**ほとんどのdockerは参照していないと考えてよいでしょう**。なので、ラズパイでpipで必要なパッケージをインストールする際はDockerfileに必ず`--extra-index-url https://www.piwheels.org/simple`を付加することでpiwheelsを参照するようにします。

- [piwheels: making &quot;pip install&quot; fast - Raspberry Pi](https://www.raspberrypi.org/blog/piwheels/)
- [python - Installing libraries via pip is taking forever. Raspberry pi 3b+ - Raspberry Pi Stack Exchang](https://raspberrypi.stackexchange.com/questions/94585/installing-libraries-via-pip-is-taking-forever-raspberry-pi-3b)

以下は構築例です。`numpy`と`pandas`をインストールしています。

```Dockerfile:Dockerfile
FROM python:3.9.2

ARG project_dir=/app/
RUN mkdir $project_dir
ADD requirements.txt $project_dir

RUN apt-get update -y
RUN apt-get install vim -y
RUN pip install --upgrade pip setuptools

WORKDIR $project_dir
RUN pip install Cython numpy --extra-index-url https://www.piwheels.org/simple
RUN apt-get install build-essential libssl-dev libffi-dev python3-dev cargo -y
RUN pip install pandas --extra-index-url https://www.piwheels.org/simple
RUN pip install cryptography --extra-index-url https://www.piwheels.org/simple
RUN pip install -r requirements.txt --extra-index-url https://www.piwheels.org/simple
```

試しに`buildx`で実行結果した結果です。`numpy`は1分以内で構築できていますが`pandas`は12時間以上実行しても終わらない結果に......ちなみに下記ではplatformを`arm64`にしていますが`arm/v7`でも同様に`pandas`で長時間かかりました。また、`buildx`で構築したイメージが動作するかは未確認です。

```bash
sudo docker buildx build --platform linux/arm64 -t HOGEHOGE .
Password:
   [[中略]]
[+] Building 52839.0s (19/19) FINISHED
   [[中略]]
 => [ 8/14] RUN pip install Cython numpy --extra-index-url　https://www.piwheels.org/simple  43.2s
=> [ 9/14] RUN apt-get install build-essential libssl-dev libffi-dev python3-dev cargo -y   50.1s  
=> [10/14] RUN pip install pandas --extra-index-url https://www.piwheels.org/simple         52195.0s
   [[中略]]
```

ラズパイの実機（`Raspberry pi 4B (8GB)`)で上記と同じDockerfileで構築してみた結果１時間以内（3181秒）でビルドできました。実機で`build`するのと`buildx`を使うのとで差があるみたいですが、原因は私では分かりませんでした。

## 結論
ラズパイでDockerを構築する際は`piwheels.org`を指定すれば大幅な時間削減になるので、`--extra-index-url https://www.piwheels.org/simple`をおまじないとして使ってください。