💻

【Raspberry Pi】RAMディスク化するとDockerが起動できなくなる

に公開

はじめに

現在、Raspberry Piをベースとした、新しい監視カメラシステムを開発中ですが、そこで利用している様々な技術的なトピックをメモ代わりにまとめていきたいと思います。

今回は、RAMディスク化すると、Dockerが起動できなくなる問題への対策を説明します。

※RAMディスク化の方法についてはこちらの記事を参照してください
https://zenn.dev/daddy_yukio/articles/a722e6c024efaf

概要

Raspberry Piを使用して、IoTデバイスを開発し、実運用していく為に必要なRAMディスク化ですが、一つ大きな問題があります。

それは、Dockerが起動できなくなってしまうのです。

RAMディスク化とは、rootファイルシステムを読み取り専用(read-only)にし、RAMディスク上にオーバーレイ(差分書き込み)を作成して、SDカードへの書き込みを禁止することでSDカードの寿命を延ばし、突然の電源断などでSDカードが壊れるのを防げるというメリットがあります。

しかし、Dockerは、サービス起動時に、以下のデータ格納領域(data-root)が書き込み可能かどうかを確認します。

/var/lib/docker

ここには、作成したイメージや起動したコンテナなどの情報が格納されるのですが、ここが書き込み不可(read-only)だと、Dockerは起動不可と判断してして、起動できなくなってしまうのです。

以下は、RAMディスク化した状態で起動したときのdocker.serviceのエラー出力です。

docker.service起動エラー
systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: docker.service: Failed with result 'exit-code'.
systemd[1]: Failed to start docker.service - Docker Application Container Engine.

解決策

このエラーを解決するには、大きく2つあります。

一つ目は、/var/lib/dockerを書き込み可能な領域に移動することです。

下記で紹介したように、書き込み可能な領域を作成して、dockerのデータ格納領域(data-root)をそちらに移動する方法です。
https://zenn.dev/daddy_yukio/articles/15cd2bd013e1b2

しかし、この方法だと、DockerによるSDカード上への書き込み処理が発生してしまい、SDカードの寿命を縮めてしまう可能性があります。

何とかdockerも実メモリ上への書き込みだけで動かせないか、いろいろと調べたところ、ありました!

Dockerのデータ格納領域を、tmpfsを使用してRAM上に構築したRAM上の一時領域にマウントさせる、という方法です。

処理手順

ほぼ、以下の元ネタそのままですが、、、
https://grafolean.medium.com/run-docker-on-your-raspberry-pi-read-only-file-system-raspbian-1360cf94bace

処理手順は以下です。

  1. RAMディスク化する前に、/var/lib/docker配下の情報を、別の場所(persistent storage)に保存する
  2. RAMディスク化したら、/var/lib/dockerをtmpfsにマウントして、保存しておいた情報を戻してからdocker.serviceを起動する

1. /var/lib/docker配下の情報を保存

まずは、RAMディスク化する前に、/var/lib/docker配下の情報を、別の場所(persistent storage)に保存します。

これを実行するシェルスクリプトは、元ネタに書かれているものをそのまま引用します。

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

必要なDockerのイメージビルドなどを行った後に、このシェルスクリプトを実行して、永続化したい情報を退避させておきます。

2. /var/lib/dockerをtmpfsにマウントして、保存しておいた情報を戻す

RAMディスク化した後のdocker起動の手順は以下になります。

  1. docker.sericeを停止
    • sudo systemctl stop docker.service
  2. /var/lib/dockerをtmpfsにマウント
    • sudo mount -t tmpfs tmpfs /var/lib/docker
  3. 保存しておいた情報を元に戻す
    • 下記シェルスクリプトを実行
  4. docker.serviceを起動
    • sudo systemctl start docker.service

以下は、「3.保存しておいた情報を元に戻す」処理を行うシェルスクリプトです。
(こちらも元ネタのものをそのまま引用します)

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"

RAMディスク化した後は、毎回起動時にこの処理を行う必要がありますが、手動で行うのは大変なので、この処理自体をシェルスクリプト化して、systemdでサービスとして登録するのが良いと思います。

その方法については、下記の記事に説明がありましたので、参考にしてください。
https://qiita.com/takkaO/items/611e2a4d620bdcb8885c

最後に

これでRaspberry PiのSDカードをRAMディスク化しても、Dockerを起動することができるようになりました。

ちなみに、こんな感じのものを開発中です。

このシステムで使用している技術の概要は以下のBookにまとめていますので、ご興味のある方は読んでみてください。
https://zenn.dev/daddy_yukio?tab=books
また、近日中にテスターの募集を行う予定ですので、お手伝いしていただける方はよろしくお願いします。
(後日、どこかで募集します。。。)

Discussion