CentOS で aufs (another unionfs) を使う
- フラッシュメモリデバイスなディスクに Linux をインストールしたい
- ⇒ 書き換え限度回数が心配だよ
- ⇒ CDROM bootable な OS にすればいいよ
- ⇒ システムの変更やアップデートのときが面倒だよ
- ⇒ read only filesystem の上にかぶせることができる UnionFS を使えばいいよ
- ⇒ UnionFS より aufs のほうがおすすめだよ
ということで,CentOS 5.2 に aufs をいれてみました。ちなみに「やってみた」レベルのお話です。
aufs とは
aufs とは,スタッカブルな「単一化」ファイルシステムです。まぁーつまり,単一ファイルツリーに複数の「ブランチ」を透過的に重ね合わせることができます。
KNOPPIX 5.1 以降で使われています。
ビルドする
残念ながら RPM パッケージは用意されてないので,CVS で最新版をダウンロードして自分でビルドする必要があります。CVS といっても作者によると常に安定版をアップロードするようにこころがけていらっしゃるそうなのでそんなに心配する必要はありません。
CVS によるダウンロードとビルドについては下記記事が参考になります。
CentOS(というか RedHat 系)の kernel 2.6.18 では,ファイルシステムのソースから i_blksize
がなくなっているので,下記のようなパッチを当てる必要があります。
--- fs/aufs/cpup.h.orig 2008-04-14 08:38:24.000000000 +0900 +++ fs/aufs/cpup.h 2008-07-11 12:51:26.000000000 +0900 @@ -34,7 +34,7 @@ static inline void au_cpup_attr_blksize(struct inode *inode, struct inode *h_inode) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +#if 0 && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) inode->i_blksize = h_inode->i_blksize; #endif }
あとはビルドすればいいのですが,一応 local.mk
にあるビルド時オプションを下記のようにいじっておきましょう。
export CONFIG_AUFS_HINOTIFY = y
ビルド時オプションの CONFIG_AUFS_HINOTIFY
については aufs のマニュアルや下記記事を参照してください。
kernel ソースツリーに組み込むには,make -f local.mk kconfig
してから,指示されたとおりにあれやこれややる必要があるのですが,カーネルモジュールとしてビルドするには,下記のようにするだけでよいです。
$ make -f local.mk
ビルドできたら,適宜ディレクトリにコピーします。
$ sudo mkdir /lib/modules/`uname -r`/kernel/fs/aufs $ sudo cp aufs.ko /lib/modules/`uname -r`/kernel/fs/aufs $ sudo depmod -a
周辺ツールもついてくるのですが,普通に使うぶんにはカーネルモジュール単体をコピーするだけで十分です。depmod -a
すればリブートせずとも aufs が使えるようになります((depmod -a
しただけでは lsmod
には現れませんが,mount
すれば自動的にロードしてくれます。))。
ためしに使ってみる
まず,下記のような2つのディレクトリがあったとして,
$ ls /tmp/a 1 $ ls /tmp/b 2 3
read-only な /tmp/b
に /tmp/a
を read-write で重ねて,/tmp/c
にマウントしてみましょう。
$ sudo mount -t aufs -o br:/tmp/a:/tmp/b=ro none /tmp/c
aufs ではマウント元のファイルシステムを指定する必要はありません(ので none
としています)。マウントするディレクトリ等はすべてオプションで指定します。
オプションの br:
というのは「ブランチ」の略です。上記設定は,
- 最上位階層の branch として
/tmp/a
を指定- オプションはデフォルトで
rw
(read-write)
- オプションはデフォルトで
- 第2階層の branch として
/tmp/b
を指定- オプションは
ro
(read-only)
- オプションは
になります。これを /tmp/c
にマウントする,ということです。
なお,ro
というのは,あくまで aufs からみて read only にするというだけの意味です。この例では,直接 /tmp/b
ディレクトリにファイルを追加・削除することはできます((ただし UDBA で inotify を指定しないと /tmp/c
に反映されません。))。
重ね合わせた結果は,
$ ls /tmp/c 1 2 3
のように,/tmp/a
と /tmp/b
下のファイルが両者とも見えています。
では,/tmp/c
に新しいファイルを作ってみましょう。
$ touch /tmp/c/4 $ ls /tmp/c 1 2 3 4 $ ls /tmp/a 1 4
実際には rw な最上位階層の /tmp/a
に新しいファイルが作られました。
ro な下階層のファイルを削除したらどうなる?
/tmp/c/3
というファイルは /tmp/b
branch のものが見えているわけですが,この read only な 3
を削除するとどのようになるでしょうか。
$ rm /tmp/c/3 rm: remove regular empty file `/tmp/c/3'? y $ ls /tmp/c 1 2 4 $ ls /tmp/a 1 4 $ ls /tmp/b 2 3
/tmp/c
から 3
がなくなりました。しかし,マウント元の /tmp/b
には依然残っています。
このように read only branch のファイル削除情報は,上位階層の branch に「ホワイトアウト(whiteout)」ファイルとして残っています。
$ ls -a /tmp/a . .. 1 4 .wh.3 .wh..wh.aufs .wh..wh.plink
.wh.3
というのが,「3
というファイルがあるけど whiteout してね」という意味になるわけです。
この whiteout はアンマウントしても rw branch に残っているので,再マウントするとふたたび見えなくなります。
$ sudo umount /tmp/c $ sudo mount -t aufs -o br:/tmp/a:/tmp/b=ro none /tmp/c $ ls /tmp/c 1 2 4
ハードリンクはどうなる?(Pseudo Link)
branch 間でハードリンクを貼ってみましょう。
$ ln /tmp/c/2 /tmp/c/5 $ ls /tmp/c 1 2 4 5
inode 番号をみてみると,
$ ls -li /tmp/c total 0 11 -rw-r--r-- 1 root root 0 Jul 14 10:43 1 14 -rw-r--r-- 2 root root 0 Jul 14 10:43 2 15 -rw-r--r-- 1 root root 0 Jul 14 10:44 4 14 -rw-r--r-- 2 root root 0 Jul 14 10:43 5
たしかに,2
と 5
は同じ inode 番号を示しています。
各ブランチの inode 番号をみてみると,
$ ls -li /tmp/a total 0 324916 -rw-r--r-- 1 root root 0 Jul 14 10:43 1 324921 -rw-r--r-- 1 root root 0 Jul 14 10:44 4 324924 -rw-r--r-- 2 root root 0 Jul 14 10:43 5 $ ls -li /tmp/b total 0 324917 -rw-r--r-- 1 root root 0 Jul 14 10:43 2 324918 -rw-r--r-- 1 root root 0 Jul 14 10:43 3
このように違う番号になっています。このような branch 間ハードリンク情報はメモリ内に格納されています。
では 2
の内容を変更してみましょう。
$ echo 'Hello!' >> /tmp/c/2 $ cat /tmp/c/2 Hello! $ cat /tmp/c/5 Hello! $ ls -li /tmp/c total 8 11 -rw-r--r-- 1 root root 0 Jul 14 10:43 1 14 -rw-r--r-- 2 root root 7 Jul 14 10:45 2 15 -rw-r--r-- 1 root root 0 Jul 14 10:44 4 14 -rw-r--r-- 2 root root 7 Jul 14 10:45 5
依然 2
と 5
の実体は同一のままですが,内的には……
$ ls -li /tmp/a total 8 324916 -rw-r--r-- 1 root root 0 Jul 14 10:43 1 324924 -rw-r--r-- 3 root root 7 Jul 14 10:45 2 324921 -rw-r--r-- 1 root root 0 Jul 14 10:44 4 324924 -rw-r--r-- 3 root root 7 Jul 14 10:45 5 $ ls -li /tmp/b total 0 324917 -rw-r--r-- 1 root root 0 Jul 14 10:43 2 324918 -rw-r--r-- 1 root root 0 Jul 14 10:43 3
最上位 branch に新しい 2
と 5
が生成されています。
このように copy-on-write 的に働きます。が,さきほど述べたように,on-write する前は,メモリ上に情報が格納されているだけですので,アンマウントすると,branch 間ハードリンク情報がロストします。このことの対処法や Pseudo Link についてのより詳しい情報は aufs のマニュアルの Pseudo Link (hardlink over branches) 項を参照してください。
aufs で可能なこと,不可能なこと
これまでの例では,各階層の branch とマウント先ディレクトリは別物として指定していましたが,各 branch 自身にマウントすることもできます。
たとえば,最上位階層の rw な branch にマウントしてみます。
$ sudo mount -t aufs -o br:/tmp/a:/tmp/b=ro none /tmp/a $ ls /tmp/a 1 2 3 $ touch /tmp/a/4 $ ls /tmp/a 1 2 3 4 $ umount /tmp/a $ ls /tmp/a 1 4
うまく動いています。
また,下位階層の ro branch にマウントすることもできます。
$ sudo mount -t aufs -o br:/tmp/a:/tmp/b=ro none /tmp/b $ ls /tmp/b 1 2 3 $ touch /tmp/b/4 $ ls /tmp/b 1 2 3 4 $ umount /tmp/b $ ls /tmp/b 2 3 $ ls /tmp/a 1 4
これもうまく動きますね。先ほどの例と異なり,こちらは read only なディレクトリツリーに writable な階層を導入できるので,なかなか有用です。
いっぽう,すでに aufs でマウントされたツリーを下位階層としてマウントすることはできません((ビルド時に CONFIG_AUFS_ROBR
を y
にしておくと,read only branch としてはマウントできるようになります。))。/tmp/c
ディレクトリが既に aufs でマウントされているとして,
$ mount -t aufs none on /tmp/c type aufs (rw,br:/tmp/a:/tmp/b=ro) $ sudo mount -t aufs -o br:/tmp/d:/tmp/c=ro none /tmp/e mount: wrong fs type, bad option, bad superblock on none, missing codepage or other error In some cases useful info is found in syslog - try dmesg | tail or so $ dmesg | tail -1 aufs test_add:353:mount[4536]: nested aufs /tmp/c
このように nested aufs
として怒られてしまいます。
また,下位ディレクトリを下位階層 branch として指定することもできません。
$ sudo mount -t aufs -o br:/tmp/f/a:/tmp/f=ro none /tmp/f mount: wrong fs type, bad option, bad superblock on none, missing codepage or other error In some cases useful info is found in syslog - try dmesg | tail or so $ dmesg | tail -1 aufs test_add:390:mount[4608]: /tmp/f is overlapped
このように overlapped
として怒られます*1。なお,下位ディレクトリにある loopback file を branch として指定しようとしても,ちゃんと検出して同じように怒られます。
CentOS 5.2 で root filesystem を Read Only にして運用してみる
いよいよ実用的に使ってみましょう。
全ファイルシステムを read only mount して,書き換えの可能性がある /var
ディレクトリと /tmp
ディレクトリ((よくよく考えたら /tmp
ディレクトリは直接 tmpfs マウントすればよかったですね。))を aufs でマウントしてみます。
まず ramdisk として用いる tmpfs 用ディレクトリを掘っておきます。
# mkdir -p /memdisk/var /memdisk/tmp
/etc/fstab
は以下の通りです。
LABEL=/ / ext3 defaults,ro 1 1 LABEL=/boot /boot ext3 defaults,ro 1 2 tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 LABEL=SWAP-hda2 swap swap defaults 0 0 tmpfs /memdisk/var tmpfs defaults 0 0 tmpfs /memdisk/tmp tmpfs defaults 0 0 none /var aufs defaults,br:/memdisk/var:/var=ro 0 0 none /tmp aufs defaults,br:/memdisk/tmp:/tmp=ro 0 0
サンプル環境ということで,基本 /
と /boot
(と swap
)しかパーティションを切っていないです。両者を ro
としています。そして tmpfs
で /memdisk/var
と /memdisk/tmp
を作成し*2,aufs で両者をマウントしています。
リブートしてみたら,うまく動きました。ramdisk の内容をみてみると,
# ls /memdisk/var/ empty lib lock log run spool # ls /memdisk/tmp/ ssh-oFxtlY1998
たしかにこちらに読み書きしているみたいです。シャットダウン時に file system busy で怒られる(ため時間がかかる)のですが,実害はおそらくないので無視しています。
より実用的に使うには,rc.sysinit
や rc0.d
,rc6.d
あたりにマウント・アンマウント処理を埋め込むべきでしょう。状況によってはアンマウント時に ro branch に write back するようにするとかっこいいかも。