概要

overlayfsはlinuxの標準機能で、ざっくり言うとあるディレクトリに別のディレクトリを被せ、書き込みは被せたディレクトリのみに行うというものです。これにより既存のディレクトリは読み取り専用にし、別のディレクトリに差分を書き込むことができます。突然の電源断からファイルシステムを守るのにも使えます。
以下の例では被せるディレクトリをtmpfsにすることで差分を揮発性にします。
Ubuntuだとoverlayrootパッケージがありますが、ArchLinuxにはないので手動で設定します。

overlayfsは以下のように使います。

1
mount -t overlay -o lowerdir=/mnt/overlay/lower,upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work none /mnt/overlay/merged
lowerdir もとのディレクトリ(書き込みされない)
upperdir 被せるディレクトリ(差分が書き込まれる)
workdir overlayfsが作業用に使うディレクトリ(upperdirと同じデバイスである必要がある)
/mnt/overlay/merged overlayによってマージされたディレクトリ(ここを使う)

ここで、overlayした結果が/mnt/overlay/mergedのところに来るので単純にここを/(ルート)にしてしまえば良さそうな気がしますが、システム起動後にルートディレクトリを変更することはできません(たぶん)。なのでシステム起動前にoverlayでマージされたディレクトリにルートを移しておく必要があります。

ということはinitcpioinitramfs,ramdisk)です。カスタムフックを作成し、initcpioに入れます。

initcpioのカスタムフック

inicpioで使われるファイル、ディレクトリは次のとおりです。

/etc/initcpio/ カスタムフック作成用
/usr/lib/initcpio/ システムのフック
/etc/mkinitcpio.conf initcpioの設定ファイル

今回はoverlayfshookという名前でカスタムフックを作ろうと思います。

/etc/mkinitcpio.conf

まず、/etc/mkinitcpio.confを修正します。

  • MODULESにoverlayを追加し、フック実行前にoverlayカーネルモジュールが読み込まれるようにします。
  • HOOKSにカスタムフックoverlayfshookを追加します。

上記以外の項目はお好みで。

1
2
3
4
MODULES=(fat vfat overlay)
BINARIES=()
FILES=()
HOOKS=(base udev modconf keyboard keymap block filesystems keyboard overlayfshook)

カスタムフックの実装

以下のスクリプトは起動前のinitcpio内ファイルシステムで実行されるため、使用できるコマンドに制限があります。ただ、/etc/mkinitcpio.confのBINARYに追加したり、installスクリプトでadd_binaryすることで依存ライブラリとともに追加できます。

/etc/initcpio/hooks/overlayfshook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/ash

run_hook() {
# udevがディスクを認識するまで待つ
sleep 2


# ルートディレクトリをuuidで指定
local rootdev=/dev/disk/by-uuid/...
mount -o defaults,nosuid,ro $rootdev /overlay/lower


# マウント先のディレクトリを作成
# upperとworkは一緒にマウントされてないといけないのでまとめる
mkdir -p /overlay/lower
mkdir -p /overlay/upper_work
mkdir -p /overlay/merged
# upperとworkにはtmpfsを割り当て
# tmpfsだと変更が保持されないので、永続化したい場合は別のパーティションにする
mount -t tmpfs none /overlay/upper_work
mkdir -p /overlay/upper_work/upper
mkdir -p /overlay/upper_work/work


# /overlay/merged に overlay でマウント
echo 'mounting as overlay'
mount -t overlay -o lowerdir=/overlay/lower,upperdir=/overlay/upper_work/upper,workdir=/overlay/upper_work/work none /overlay/merged


# merged内に移動
mkdir -p /overlay/merged/mnt/overlay/lower
mkdir -p /overlay/merged/mnt/overlay/upper_work
mount --move /overlay/lower /overlay/merged/mnt/overlay/lower
mount --move /overlay/upper_work /overlay/merged/mnt/overlay/upper_work

# switch_rootされる/new_rootにマウント
mount --move /overlay/merged /new_root
}

/etc/initcpio/install/overlayfshook

hook本体に加えて同名のインストールスクリプトも必要になります。

1
2
3
build () {
add_runscript
}

以上でほぼ完成ですが、ArchLinuxではoverlayでルートディレクトリが変更されることを想定していないのか、/new_rootにマウントしてもデフォルトのスクリプトが動いて別のデバイスが上からマウントされてしまうことがあります。それを修正するため以下の変更を行います。

/usr/lib/initcpio/init

1
2
# Mount root at /new_root
#"$mount_handler" /new_root <- コメントアウト

最後にinitcpioを作り直して完成です。

1
mkinitcpio -p linux

考察

upperdirがtmpfsの場合、重要なファイルを消してしまっても再起動すれば元通りになるためテスト環境として有用です。

ただ、もとのディレクトリに書き込みたいとき、そのパーティションをルートにマウントしてしまっているのでシステム起動中に再マウントできません。そのため書き込み時はLiveUSBなど別のシステムから触る必要があり面倒です。

他の実現方法

カスタムiso

UbuntuDesktopのLiveISOのようなライブ環境をカスタマイズしreadonlyであるisoにしておきます。ただ、こちらは書き込みがさらに面倒です。isoはマウントしてchrootできるものの、それ自体がreadonlyファイルシステムであるため、別にiso用のディレクトリツリーを用意しておかねばなりません。そしてそこから毎回isoを作り直すというと非常に面倒です。

disklessモード

alpine linuxにはdisklessモードというものがあります。これは永続化したいディレクトリを指定しておき、起動時にtmpfs上のルートディレクトリにそれを展開し、ルートツリー全体をメモリ上で管理する方式です。これにはlbuコマンドが付属しており、永続化が1コマンドでできます。他の方法と比べても書き込みは非常に楽です。ただ、ルートツリーが全てメモリに載るため、扱えるストレージ容量がメモリ量に制限されます。

参考