はじめに
最近の Raspberry Pi は性能の向上が著しく、Docker も難なく動かすことができます。
Docker でサービスを構築しておくと、普通のデスクトップ PC と Raspberry Pi の間でサービス環境を共通化することができるので非常に便利です。
しかし、Raspberry Pi には故障しやすいという欠点があります。
故障の原因は様々ですが、電源ぶち切りによる SD カードのデータ破損が主な原因のようです。
これについては su氏
が下記の記事で検証しております。
記事内でいくつかの対策が提案されていますが、そのうちの1つとして SD カードを読み込み専用にしてシステムデータを RAM 上に展開する、いわゆる Read Only 化(以下、RO化)があります。
RO化自体は非常に簡単でコマンド一発でできるのですが、RO化した状態で Docker を利用するためには一工夫必要だったので、備忘録としてこの記事に残しておこうと思います。
本記事は「Run Docker on your Raspberry Pi read-only file system (Raspbian) | by Grafolean | Medium」を基にしています。
検証環境
- Raspberry Pi4 (raspios-buster-arm64 21-05-07)
- Docker 20.10.16
セットアップの流れ
- Docker のインストール
- イメージ退避用、復元用スクリプトの作成
- 自動起動用のスクリプトを作成
- スタートアップに登録
- スクリプト実行、RO化
セットアップ方法
1. Docker のインストール
基本的には Docker の公式ドキュメントに記載されている最新の方法でインストールします。
検証環境では以下のコマンドでインストールを行いました。
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
sudo apt install docker-compose-plugin
sudo reboot now
2. イメージ退避用、復元用スクリプトの作成
A. イメージ退避用スクリプト
/usr/local/bin/dockersave.sh
を作成し、下記のコードを記載します。
スクリプトは参考元からそのまま引用しています。
#!/bin/bash
set -e
PERSIST_DIR="/var/lib/docker.persist"
# make sure docker is NOT running:
systemctl is-active -q docker && (echo "Docker service must NOT be running, please stop it first!"; exit 1)
# containers need to be writeable, so we don't allow saving when they exist either:
[ `ls /var/lib/docker/containers/ | wc -l` -eq 0 ] || (echo "We can't save Docker configuration with containers; remove them first!"; exit 1)
mkdir -p -m 700 "$PERSIST_DIR"
# copy from tmpfs to persistent storage:
# but be careful when copying overlay/ - it might contain links to the persistent storage, which we must simply leave there:
for subdir in `ls "/var/lib/docker/"`
do
if [ "overlay2" != "$subdir" ]
then
rm -rf "$PERSIST_DIR/$subdir"
cp -ra "/var/lib/docker/$subdir" "$PERSIST_DIR/$subdir"
else
# remove all links beecause we don't want to overwrite the persistent storage with them:
find "/var/lib/docker/overlay2/" -maxdepth 1 -type l -exec rm '{}' ';'
# and overlay2/l/ will be copied verbatim, so remove it in persistent storage:
rm -rf "$PERSIST_DIR/overlay2/l"
# everything else is copied over to persistent storage: (l/ and any new image overlays)
mkdir -p -m 700 "$PERSIST_DIR/overlay2"
cp -ra /var/lib/docker/overlay2/* "$PERSIST_DIR/overlay2/"
fi
done
スクリプトに実行権限を付与します。
sudo chmod +x /usr/local/bin/dockersave.sh
B. イメージ復元用スクリプト
/usr/local/bin/systemctl.dockerload.sh
を作成し、下記のコードを記載します。
スクリプトは参考元からそのまま引用しています。
#!/bin/bash
set -e
PERSIST_DIR="/var/lib/docker.persist"
# make sure docker is NOT running:
systemctl is-active -q docker && (echo "Docker service must NOT be running, please stop it first!"; exit 1)
if [ ! -d "$PERSIST_DIR" ]
then
echo "Docker configuration was never saved yet (dockersave.sh), nothing to load. Exiting."
exit 0
fi
# existing directory structure should be copied verbatim, except for overlay2/ (but
# only on top level - image/overlay2 should be copied)
/bin/rm -rf /var/lib/docker/*
for subdir in `ls "$PERSIST_DIR/"`
do
if [ "overlay2" != "$subdir" ]
then
cp -ra "$PERSIST_DIR/$subdir" "/var/lib/docker/$subdir"
fi
done
# overlay2/ must be read-write, but the existing subdirectories can be links to a
# persistent (readonly) location:
mkdir -m 700 /var/lib/docker/overlay2
for layer_hash in `ls $PERSIST_DIR/overlay2 | grep -v "^l$"`
do
ln -s "$PERSIST_DIR/overlay2/$layer_hash" "/var/lib/docker/overlay2/$layer_hash"
done
cp -ra "$PERSIST_DIR/overlay2/l" "/var/lib/docker/overlay2/l"
スクリプトに実行権限を付与します。
sudo chmod +x /usr/local/bin/systemctl.dockerload.sh
3. 自動起動用のスクリプトを作成
任意の場所に自動起動用のスクリプトを作成します。
筆者は ~/docker-ro-mode.sh
を作成し、下記のスクリプトを記載しました。
#!/bin/bash
echo "stop docker..."
service docker stop
echo "mount tmpfs"
mount -t tmpfs tmpfs /var/lib/docker
echo "run copy script"
bash /usr/local/bin/systemctl.dockerload.sh
echo "restarting docker..."
service docker start
# Complete restore docker images
# Write docker compose or docker run script below
ここにコンテナ起動スクリプトを書く
例)cd ~ && docker compose up -d
4. スタートアップに登録
/etc/systemd/system/docker-ro-mode.service
を作成し、下記のコードを記載
[Unit]
Description = "Run docker in read-only mode"
[Service]
ExecStart = bash /home/pi/docker-ro-mode.sh
Type = simple
[Install]
WantedBy = multi-user.target
5. スクリプト実行、RO化
Docker のイメージ退避を行うので、使用するイメージをダウンロードしておきます。
とりあえず起動したいコンテナを一度起動しておけば OK です。
ダウンロードが完了したら下記のコマンドを実行し、Docker イメージの退避を行います。
sudo service docker stop
sudo bash /usr/local/bin/dockersave.sh
sudo systemctl daemon-reload
スタートアップを有効化します。
sudo systemctl enable docker-ro-mode
最後に、RO化して完了です。
sudo raspi-config nonint enable_overlayfs
補足1
RO化した状態では再起動をかけると変更がすべてリセットされます。
もし何かしらの設定を変えたい場合は一度RO化を解除する必要があります。
# RO化解除
sudo raspi-config nonint disable_overlayfs
sudo reboot now
設定変更が終わったら再度RO化してください。
補足2
RO化すると Docker が起動できない理由ですが、/var/lib/docker
が読み込み専用になってしまうからです。
Docker は起動時に /var/lib/docker
が書き込み可能な状態であるか確認し、
書き込みができない状態だと起動を中断してしまいます。
この問題を回避するために tmpfs
という一時的なファイルシステムを利用しています。
tmpfs
を /var/lib/docker
にマウントすることで、RO化したシステムでも /var/lib/docker
を書き込み可能な状態にできます。
しかし、/var/lib/docker
内が tmpfs
に上書きされる、すなわち空のディレクトリになってしまうため、予め /var/lib/docker
内のファイルを適当な場所に避難させておき、Raspberry Pi の起動し tmpfs
がマウントされたタイミングでファイルを復元する、という手法をとっています。