LoginSignup
5
4

More than 1 year has passed since last update.

Read Only 化した Raspberry Pi で Docker を動かす

Last updated at Posted at 2022-07-08

はじめに

最近の 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

セットアップの流れ

  1. Docker のインストール
  2. イメージ退避用、復元用スクリプトの作成
  3. 自動起動用のスクリプトを作成
  4. スタートアップに登録
  5. スクリプト実行、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 がマウントされたタイミングでファイルを復元する、という手法をとっています。

参考資料

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4