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


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

この記事はDockerをラズパイで動かそうシリーズ(?)の一つです

更新履歴

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

背景

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

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

  • 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

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

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

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

参考:

[[[前略]]]
  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機能を使用することで実現します。

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

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

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

$ 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/arm64linux/arm/v7が並んでいます。これでarmアーキテクチャでビルドできることを示しています。以下のコマンドではarm64をビルドしています。

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

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

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

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

最初、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 "pip install" fast - Raspberry Piに書かれている内容をざっくり要約するとラズパイはARMアーキテクチャのCPUを使用しており、他のアーキテクチャで作られたwheelと互換性がないため、ソースからコンパイルしている状態とのこと。

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

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

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

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で構築したイメージが動作するかは未確認です。

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で構築してみた結果1時間以内(3181秒)でビルドできました。実機でbuildするのとbuildxを使うのとで差があるみたいですが、原因は私では分かりませんでした。

結論

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