仙石浩明の日記

2007年9月18日

NFS と AUFS (Another Unionfs) を使って、ディスクレス (diskless) サーバ群からなる低コスト・高可用な大規模負荷分散システムを構築する hatena_b

ディスクレス (diskless) サーバを多数運用しようとしたときネックとなるのが、 NAS (Network Attached Storage) サーバの性能。 多数のディスクレスサーバを賄え、かつ高信頼な NAS サーバとなると、 どうしても高価なものになりがちであり、 NAS サーバ本体の価格もさることながら、 ディスクが壊れたときの交換体制などの保守運用費用も高くつく。

それでも、多数のハードディスク内蔵サーバ (つまり一般的なサーバ) を 運用して各サーバのディスクを日々交換し続ける (運用台数が多くなると、 毎週のようにどこかのディスクが壊れると言っても過言ではない) よりは、 ディスクを一ヶ所の NAS にまとめたほうがまだ安い、 というわけで NAS/SAN へのシフトは今後も進むだろう。 そもそも CPU やメモリなどとハードディスクとでは、 故障率のケタが違うのだから、 両者の台数を同じように増やせば破綻するのは当たり前。 要は、滅多に故障しないものは増やしてもいいが、 普通に故障するものは増やしてはいけない。 サーバの部品で故障する確率が桁違いに高いのはハードディスクだから、 大規模負荷分散環境においてディスクレス化は論理的必然だろう。

ハードディスクの故障率が高いのは可動部品が多いから、 というわけでハードディスクをフラッシュメモリで 置き換えようとする傾向もあるようだ。 確かに高価な NAS サーバを導入するよりは、 各サーバにフラッシュメモリを搭載する方が安上がりである可能性もある (比較的低容量であれば)。 しかしながら、 以下に述べるように NAS サーバを普通の PC サーバで実現できてしまえば、 ディスクレス化のほうが安いのは当たり前である。 サーバの台数が多くなればなるほど、 各サーバにディスク/フラッシュメモリを必要としない ディスクレス方式の方が有利になる。

とはいえ、 PC サーバの価格に慣れてしまうと、 超高価な専用サーバの世界にはもう戻れない。 そこで、 どうすればサーバ群のディスクレス化を、 低コストで行なうことができるか考えてみる。 そもそもなぜ NAS サーバが高価かと言えば、 高パフォーマンス性と高信頼性を兼ね備えようとするから。 多数のディスクレスサーバにストレージサービスを提供するのだから 高パフォーマンス性は譲れない。 となると犠牲にしてよいのは高信頼性ということになる。

信頼できなくてもよい、 つまり時々ディスクが壊れて、 書込んだはずのデータが失われても良いなら、 そこそこ高性能な PC サーバで用が足りてしまうだろう。 壊れた場合に備えて冗長化しておけば、 高信頼ではないものの、無停止性は達成できる。 もちろんマスターサーバが壊れてスレーブサーバに切り替われば、 マスターサーバにしか書込めなかったデータは失われる。

そんな NAS サーバは使えない、と言われてしまいそうであるが (^^;)、 ディスクに書込むデータで消えては困るデータとは何だろうか? 例えば Web サーバなどでは、 永続化が必要なデータは DB サーバへ書込むのが普通で、 それ以外のデータは消えては困るとは必ずしも言えないのではないか? というか消えては困るデータは DB サーバへ書けばいいのである。

さらに考えを一歩進めて、 ディスクに書込むデータは消えてもよい、 と割りきってしまうことができれば、 NAS サーバにデータを書く必要性すらなくなってしまう。 つまり NAS サーバのディスクを読み込み専用 (Read Only 以下 RO と略記) でマウントし、 書き込みはローカルな読み書き可能な (Read Write 以下 RW と略記) RAM ディスクに対して行なう。 NAS サーバは RO だから 内容が同じ NAS サーバを複数台用意して負荷分散させれば、 高パフォーマンスと故障時のフェールオーバを同時に達成できてしまう。 NAS サーバのクラスタリングが難しいのはデータを書込もうとするからであって、 書込む必要がなければ話は一気に単純になる。

もちろん全く何も書込めない NAS サーバというのはナンセンスだろう。 ここで「書く必要がない」と言っているのは、 アプリケーション実行中にアプリケーションの動作に同期して (つまり動作結果を) 「書く」必要性である。 アプリケーションとは非同期な書込み、 例えば何らかのコンテンツを配信する Web サーバを考えたとき、 あらかじめ大量の「コンテンツ」を NAS サーバへ事前に保存しておく場合や、 あるいは「コンテンツ」を定期的に更新する場合は、 (アプリケーションの動作結果とは無関係な書き込みなので) Web アプリケーションが NAS へ書込む必要はない。
むしろ、 大量の「コンテンツ」を NAS サーバに集中することは、 コンテンツ更新が素早く行なえるというメリットとなる。 多数の Web サーバそれぞれにハードディスクを内蔵して 同じコンテンツをコピーしていては、 コンテンツの更新頻度が上がってくると 全 Web サーバの内容を同期させるのが難しくなってくるからだ。

ここで重要なのは、 上記「RO NAS サーバ + RW ローカル RAM ディスク」が、 ディスクレスサーバ上で動くソフトウェア (例えば Web アプリケーションやミドルウェア) から見ると、 普通の RW ローカルディスク (つまり普通に書込み可能なハードディスク) に見えなければならないという点である。 もしソフトウェア側で特別な対応が必要だと、 ソフトウェアの改修コストがかかってしまう。 ハードウェアのコストを下げようとして ソフトウェアのコストが上がってしまっては本末転倒である。 ディスク上のデータが RO な NAS サーバから読み込まれたものであり、 ディスクへ書込んだデータが、実は RAM ディスクに書込んだだけで、 再起動によって消えてしまうものであったとしても、 ソフトウェアから見れば、 普通の RW ハードディスクディスクのように 振る舞わなければならないのである。

このように、 複数のディスク (RO NAS と RW RAM ディスク) を重ねて 一つのディスクとして見せる仕掛けを、 重ね合わせ可能な統合ファイルシステム (Stackable Unification File System) と呼ぶ。 Linux 2.6.20 以降の場合、 二種類の統合ファイルシステムが利用可能である。 後発の Aufs (Another Unionfs) を利用して、 ディスクレスサーバを作ってみた。
(あいかわらず) 前フリが長いが (^^;)、ここからが本題である。

まず、Aufs のページの手順に従い、 CVS からダウンロードして make すると、 Linux モジュール aufs.ko ができる。

senri:/usr/src % mkdir aufs.wcvs
senri:/usr/src % cd aufs.wcvs
senri:/usr/src/aufs.wcvs % cvs -d:pserver:anonymous@aufs.cvs.sourceforge.net:/cvsroot/aufs login
Logging in to :pserver:anonymous@aufs.cvs.sourceforge.net:2401/cvsroot/aufs
CVS password:(改行)
senri:/usr/src/aufs.wcvs % cvs -z3 -d:pserver:anonymous@aufs.cvs.sourceforge.net:/cvsroot/aufs co aufs
cvs checkout: Updating aufs
U aufs/COPYING
U aufs/History
U aufs/Kconfig.in
U aufs/README
U aufs/ksize.patch
U aufs/lhash-2.6.22.patch
U aufs/lhash.patch
U aufs/local.mk
        ...(中略)...
U aufs/util/fsck.aufs
U aufs/util/mount.aufs
U aufs/util/umount.aufs
senri:/usr/src/aufs.wcvs % cd aufs
senri:/usr/src/aufs.wcvs/aufs % make KDIR=/usr/src/linux-2.6.20.19 -f local.mk 
make -C /usr/src/linux-2.6.20.19 M=/usr/src/aufs.wcvs/aufs/fs/aufs modules
make[1]: Entering directory `/usr/src/linux-2.6.20.19'
make -C /usr/src/linux-2.6.20.19 O=/usr/src/linux-2.6.20.19 modules
  CC [M]  /usr/src/aufs.wcvs/aufs/fs/aufs/module.o
  CC [M]  /usr/src/aufs.wcvs/aufs/fs/aufs/super.o
  CC [M]  /usr/src/aufs.wcvs/aufs/fs/aufs/sbinfo.o
  CC [M]  /usr/src/aufs.wcvs/aufs/fs/aufs/xino.o
        ...(中略)...
make[1]: Leaving directory `/usr/src/aufs.wcvs/aufs/util'
ln -s util/aufs.5 aufs.5
ln -s util/aufind.sh aufind.sh
test -x util/mount.aufs || chmod a+x util/mount.aufs
ln -s util/mount.aufs mount.aufs
ln -s util/auplink auplink
ln -s util/aulchown aulchown
ln -s util/umount.aufs umount.aufs
senri:/usr/src/aufs.wcvs/aufs % 

できた aufs.ko を /lib/modules/2.6.20.19 下にコピーして (2.6.20.19 は上の実行例におけるカーネルのバージョン) depmod を実行すれば、 aufs を使ってみることができる。

senri:/usr/src/aufs.wcvs/aufs # mkdir /lib/modules/2.6.20.19/kernel/fs/aufs
senri:/usr/src/aufs.wcvs/aufs # cp aufs.ko /lib/modules/2.6.20.19/kernel/fs/aufs/
senri:/usr/src/aufs.wcvs/aufs # depmod -a

試しに使ってみる。 RO ディレクトリ /boot の「上」に、 RW ディレクトリ /tmp/rw を「重ねて」、 /mnt へマウントする。 すると、/mnt には /boot 下と同じファイル群が現われる。

senri:/ # mkdir /tmp/rw
senri:/ # mount -t aufs -o br:/tmp/rw:/boot=ro none /mnt
senri:/ # ls -lt /mnt
total 6928
-rw-r--r-- 1 root root 1733404 Sep  9 20:47 initz-2.6.20.19
-rw-r--r-- 1 root root 1202608 Sep  9 16:20 linuz-2.6.20.19
-rw-r--r-- 1 root root 1513158 Sep  8 10:09 initz-2.6.22.6-co-0.8.0
-rw-r--r-- 1 root root 2613070 Sep  8 10:04 linux-2.6.22.6-co-0.8.0
drwxr-xr-x 2 root root    2048 Apr  8 11:53 grub

「mv /mnt/grub /mnt/grub2」などと /mnt に変更を加えても、 元のディレクトリ /boot は RO なので変化せず、 変更は /tmp/rw に対して行なわれる。

senri:/ # mv /mnt/grub /mnt/grub2
senri:/ # ls -lt /mnt /boot
/mnt:
total 6928
-rw-r--r-- 1 root root 1733404 Sep  9 20:47 initz-2.6.20.19
-rw-r--r-- 1 root root 1202608 Sep  9 16:20 linuz-2.6.20.19
-rw-r--r-- 1 root root 1513158 Sep  8 10:09 initz-2.6.22.6-co-0.8.0
-rw-r--r-- 1 root root 2613070 Sep  8 10:04 linux-2.6.22.6-co-0.8.0
drwxr-xr-x 2 root root    2048 Apr  8 11:53 grub2

/boot:
total 6928
-rw-r--r-- 1 root root 1733404 Sep  9 20:47 initz-2.6.20.19
-rw-r--r-- 1 root root 1202608 Sep  9 16:20 linuz-2.6.20.19
-rw-r--r-- 1 root root 1513158 Sep  8 10:09 initz-2.6.22.6-co-0.8.0
-rw-r--r-- 1 root root 2613070 Sep  8 10:04 linux-2.6.22.6-co-0.8.0
drwxr-xr-x 2 root root    2048 Apr  8 11:53 grub
senri:/ # ls -lta /tmp/rw
total 8
drwxr-xr-x  4 root root 2048 Sep 15 16:42 .
drwxrwxrwt 29 root root 2048 Sep 15 16:41 ..
-r--r--r--  2 root root    0 Sep 15 16:10 .wh..wh.aufs
drwx------  2 root root 2048 Sep 15 16:10 .wh..wh.plink
-r--r--r--  2 root root    0 Sep 15 16:10 .wh.grub
drwxr-xr-x  2 root root 2048 Apr  8 11:53 grub2

「/tmp/rw/.wh.名前」というファイルは、 「/mnt/名前」が削除されたことを示し、 「.wh.」から始まらないファイル/ディレクトリは、 そのまま「/mnt」に現われる。 上の例で言うと、 「mv /mnt/grub /mnt/grub2」という変更を行なったことにより、 「/tmp/rw/.wh.grub」というファイルが作られて「/mnt/grub」が見えなくなり、 「/tmp/rw/grub2」というディレクトリが作られて「/mnt/grub2」が現われた。

ちなみに、「.wh.」から始まるファイルを作ろうとすると、

senri:/ # touch /mnt/.wh.grub3
touch: setting times of `/mnt/.wh.grub3': Operation not permitted

などと、エラーになる。

では次に、NFS マウントしたディレクトリの上に重ねあわせを行なってみる。 残念なことに現在の Linux カーネルのパス名ルックアップ関数 lookup_one_len には、 少々問題があるらしい。 この関数は引数のファイル名からハッシュ値を算出し、 ファイルシステムのハッシュリストを検索して、 ファイル名に対応する dentry 構造体を返すのであるが、 その際、そのファイルに至るディレクトリの dentry 構造体の参照カウンタを +1 してしまう。 この挙動が NFS に混乱をもたらす (らしい ^^;)。

NFS に影響を及ぼさない形で参照カウンタを元に戻すには、 nameidata 構造体を独自に設定した上でルックアップを行なう必要があるが、 現在の Linux カーネルにはそのようなインタフェースが無い。 そこで aufs では、 カーネル内の関数を一つ (__lookup_hash 関数) モジュールから呼び出せるようにするべく、 EXPORT_SYMBOL(__lookup_hash) するパッチ (lhash.patch) をあてている。 このパッチをあててカーネルを再構築した後に、 aufs の AUFS_LHASH_PATCH オプションを y に設定すればよい。

senri:/usr/src/linux-2.6.20.19 # patch -p1 < /usr/src/aufs.wcvs/aufs/lhash.patch
patching file fs/namei.c
patching file include/linux/namei.h

local.mk の「CONFIG_AUFS_LHASH_PATCH」の設定を以下のように「y」に変更し、
再度「make KDIR=/usr/src/linux-2.6.20.19 -f local.mk」を実行して aufs.ko を作り直す。

CONFIG_AUFS_LHASH_PATCH = y

以上で NFS マウントしたディレクトリの上に、 別のファイルシステムを重ねることが可能になった。 試しに NFS マウントしてみる、といいたいところだが、 Linux 起動時に NFS マウントするには、 まずネットワークを使えるようにしなければならない。 ネットワーク・インタフェース (NIC) の認識は、 「initramfs (initrd) の init を busybox だけで書いてみた」で説明した方法でハードウェアに応じたモジュールを読み込めばよい。 その後「ifconfig eth0 up」を実行して NIC を有効にし、 DHCP クライアント udhcpc (busybox に含まれる) を使って IP アドレスを割当てる。

[    4.132912] 8139too Fast Ethernet driver 0.9.28
[    4.133116] ACPI: PCI Interrupt 0000:04:01.0[A] -> GSI 19 (level, low) -> IRQ 19
[    4.133944] eth0: RealTek RTL8139 at 0xf8858000, 00:0b:97:xx:xx:xx, IRQ 19
[    4.134062] eth0:  Identified 8139 chip type 'RTL-8101'
# ifconfig eth0 up
[   11.128281] eth0: link up, 100Mbps, full-duplex, lpa 0xC5E1
# udhcpc
udhcpc (v1.7.0) started
eth0      Link encap:Ethernet  HWaddr 00:0B:97:XX:XX:XX  
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:180 (180.0 B)  TX bytes:0 (0.0 B)
          Interrupt:19 Base address:0x8000 

Sending discover...
Sending select for 192.168.1.132...
Lease of 192.168.1.132 obtained, lease time 172800
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

これでネットワークにアクセスできるようになったので、 NFS サーバ 192.168.1.1 の /fs/nfs/puppy ディレクトリを /mnt へ RO マウントし、 その上に RW なディレクトリ /.rw を重ねて、 /.root へマウントしてみる。 NFS マウントオプションとして nolock を指定しているが、 これは aufs では重ね合わせ後のファイルシステムにおいて ファイル・ロック機能をサポートしているため、 重ね合わせ前のファイルシステムにはロック機能が不要という理由による。

ディレクトリ名から想像される通り、 このディレクトリは 1CD Linux の一つ、 Puppy Linux の ルートディレクトリをコピーしたものである。 Puppy Linux を使う必然性は何もなく、 /sbin/init が起動可能なルートディレクトリならなんでもいいのであるが、 手軽に実験してみたいときなど、 1CD Linux のルートディレクトリはサイズがコンパクトなので便利。

# mount -onolock,ro 192.168.1.1:/fs/nfs/puppy /mnt
# ls -lt /mnt
drwxr-xr-x   21 0        0            4096 Sep  9 21:09 etc
drwxrwxrwt    2 0        0            4096 Sep  9 20:39 tmp
drwxr-xr-x   25 0        0            4096 Sep  5 22:50 root
drwxr-xr-x   11 0        0            4096 Jul 18 03:49 usr
drwxr-xr-x    7 0        0            4096 Jul 17 19:50 dev
drwxr-xr-x    4 0        0            4096 Jul 17 19:48 lib
drwxr-xr-x    2 0        0            4096 Mar 13  2007 bin
drwxr-xr-x    2 0        0            4096 Aug 10  2006 sbin
drwxr-xr-x   11 0        0            4096 Aug  9  2006 var
drwxr-xr-x   11 0        0            4096 Apr  7  2006 mnt
drwxr-xr-x    4 0        0            4096 Dec 12  2003 proc
# mkdir /.rw /.root
# mount -t aufs -o br:/.rw:/mnt=ro none /.root
[   67.794180] aufs 20070820
# mount
rootfs on / type rootfs (rw)
none on /proc type proc (rw)
192.168.1.1:/fs/nfs/puppy on /mnt type nfs (ro,vers=3,rsize=32768,wsize=32768,hard,nolock,proto=udp,timeo=7,retrans=3,sec=sys,addr=192.168.1.1)
none on /.root type aufs (rw,xino=/.rw/.aufs.xino,br:/.rw=rw:/mnt=ro)

これで /.root に Puppy Linux のルートディレクトリが RW な形でマウントできた。 続いて pivot_root を行なって /.root を / へマウントしなおし、 /sbin/init を exec すれば Puppy Linux をブートすることができる。

というわけで、 いよいよ initramfs の /init にて NFS マウントと AUFS の重ねあわせを行なうわけであるが、 一つ問題がある。 initramfs では pivot_root ができない。 initramfs の / (root) は何もマウントしていない状態なので、 /.root と交換 (pivot) できるものが無いのである。

解決策としては二通りあって、 一つは構わず /.root を / へマウントしてしまう方法。 もう一つは、 いったん tmpfs を / へマウントしてから、 NFS と AUFS をマウントし、pivot_root する方法。

initramfs の流儀からすれば、 前者の方法が望ましいかも知れないが、 /.rw や /mnt ディレクトリが見えなくなってしまう (見えない方がいい、というケースもありそうであるが)。 /init スクリプトを書く前に、 実際に手入力で実行してみる:

# cd /.root
# mount --move . /
# mount
rootfs on / type rootfs (rw)
none on /proc type proc (rw)
192.168.1.1:/fs/nfs/puppy on /mnt type nfs (ro,vers=3,rsize=32768,wsize=32768,hard,nolock,proto=udp,timeo=7,retrans=3,sec=sys,addr=192.168.1.1)
none on / type aufs (rw,xino=/.rw/.aufs.xino,br:/.rw=rw:/mnt=ro)
# umount /proc
# exec sbin/chroot . bin/sh
bin/sh-3.00# mount -t proc none /proc
bin/sh-3.00# mount
192.168.1.1:/fs/nfs/puppy on /mnt type nfs (ro,vers=3,rsize=32768,wsize=32768,hard,nolock,proto=udp,timeo=7,retrans=3,sec=sys,addr=192.168.1.1)
none on / type aufs (rw,xino=/.rw/.aufs.xino,br:/.rw=rw:/mnt=ro)
none on /proc type proc (rw)
bin/sh-3.00# ls -la /.rw /mnt
ls: /.rw: No such file or directory
/mnt:
total 40
drwxr-xr-x  11 root root 4096 Apr  7  2006 .
drwxr-xr-x  14 root root    0 Sep 15 18:36 ..
drwxr-xr-x   2 root root 4096 Apr 22  2003 cdrive
drwxr-xr-x   2 root root 4096 Apr 22  2003 cdrom
drwxrwxrwt   2 root root 4096 Sep 15 18:12 data
drwxr-xr-x   2 root root 4096 Apr 22  2003 flash
drwxr-xr-x   2 root root 4096 Apr 22  2003 floppy
drwxr-xr-x   2 root root 4096 Sep 21  2004 msdos
drwxr-xr-x   2 root root 4096 May 25  2003 ram1
drwxr-xr-x   2 root root 4096 Mar  4  2005 swap
drwxr-xr-x   2 root root 4096 Jan  5  2004 zip

/.root ディレクトリへ cd し、 「mount --move . /」を実行することによって、 Puppy Linux のルートディレクトリを / へマウントする。 続いて chroot を exec して / を Puppy Linux のルートディレクトリへ切り替える。 通常であればここで /sbin/init を起動して Puppy Linux に制御を渡すのであるが、 ここでは実験のため /bin/sh を exec した。

ここでマウントポイントを確認してみると、 「192.168.1.1:/fs/nfs/puppy on /mnt」と表示されるが、 NFS マウントした /mnt ディレクトリは見えず、 Puppy Linux の /mnt ディレクトリしか見えない。 もちろん /.rw というディレクトリも存在しない。 chroot したのだから、 旧ルート下のこれらのディレクトリが見えなくなるのは当然であるが、 /.rw などを参照したいケースもあるだろう。 そういうときはもう一つの方法である pivot_root する方法を使う。

すなわち、 initramfs の /init スクリプト の冒頭で、 次のように tmpfs をマウントして initramfs の内容を全て tmpfs へコピーし、 switch_root して再度自分自身 (/init スクリプト) を呼び出せばよい。

if [ -z "$INIT_TMPFS" ]; then
    mount -t tmpfs none /mnt
    cd /mnt
    cp -a `echo /* | sed 's@/mnt @ @'` .
    mkdir mnt
    export INIT_TMPFS=1
    exec switch_root . /init
fi

自分自身を呼び出す前に export INIT_TMPFS=1 することによって、 呼び出しが繰り返されることを防いでいる。 これで / には tmpfs がマウントされる。 mount コマンドで確認してみると、 「none on / type tmpfs (rw)」と表示されるので、 tmpfs が / にマウントできていることが分かる:

# mount
rootfs on / type rootfs (rw)
none on / type tmpfs (rw)
none on /proc type proc (rw)

続いて NFS と /.rw を AUFS で重ねて /.root へマウントした後、 pivot_root する。

# mount -onolock,ro 192.168.1.1:/fs/nfs/puppy /mnt
# mkdir /.rw /.root
# mount -t aufs -o br:/.rw:/mnt=ro none /.root
[  422.703779] aufs 20070820
# cd /.root
# mkdir initrd
# pivot_root . initrd
# mount -t proc none /proc
# mount
none on /initrd type tmpfs (rw)
192.168.1.1:/fs/nfs/puppy on /initrd/mnt type nfs (ro,vers=3,rsize=32768,wsize=32768,hard,nolock,proto=udp,timeo=7,retrans=3,sec=sys,addr=192.168.1.1)
none on / type aufs (rw,xino=/initrd/.rw/.aufs.xino,br:/initrd/.rw=rw:/initrd/mnt=ro)
none on /proc type proc (rw)
# ls -la /initrd/.rw
total 0
drwxr-xr-x   5 root root 120 Sep 15 19:09 .
drwxrwxrwt  17 root root 360 Sep 15 19:07 ..
-r--r--r--   1 root root   0 Sep 15 19:07 .wh..wh.aufs
drwx------   2 root root  40 Sep 15 19:07 .wh..wh.plink
drwxr-xr-x   2 root root  60 Sep 15 19:28 etc
drwxr-xr-x   2 root root  40 Sep 15 19:09 initrd

pivot_root 実行前の / は、 上記実行例のように /initrd にマウントされるので、 NFS マウントしたディレクトリは /initrd/mnt で見ることができるし、 重ねあわせの RW ディレクトリは /initrd/.rw で見ることができる。

以上の実験結果を踏まえて、 「initramfs (initrd) の init を busybox だけで書いてみた」で書いた /init スクリプトに、 NFS および AUFS をマウントする機能を追加してみた:

#!/bin/ash
export PATH="/bin:/sbin:/usr/bin:/usr/sbin"
KERNVER="`uname -r`"
FILES=`echo /* | sed 's@/mnt @ @'`
if [ -n "$AUFS" ]; then
    if [ -z "$INIT_TMPFS" ]; then
        mount -t tmpfs none /mnt
        cd /mnt
        cp -a $FILES .
        mkdir mnt
        export INIT_TMPFS=1
        exec switch_root . /init
    fi
fi

mount -t proc none /proc
ROOTFLAG="ro"
INIT="/sbin/init"
for p in `cat /proc/cmdline`; do
    v=`echo $p | sed 's/.*=//'`
    case $p in
        root=*)
        ROOT=$v
        ;;
        rootfstype=*)
        ROOTPARM="-t $v"
        ;;
        rootflags=*)
        ROOTFLAG=$v
        ;;
        nfsroot=*)
        NFSROOT=`echo $v | sed 's/,.*//'`
        NFSOPTS=`echo $v | sed 's/[^,]*,*//'`
        NFSPARM='-t nfs'
        if [ -n "$NFSOPTS" ]; then
            NFSPARM="$NFSPARM -o $NFSOPTS"
            unset NFSOPTS
        fi
        ;;
        init=*)
        INIT=$v
        ;;
    esac
done
unset p v
ROOTPARM="$ROOTPARM -o $ROOTFLAG"
unset ROOTFLAG
hwclock --hctosys

mount -t sysfs none /sys
for d in /sys/bus/pci/devices/*; do
    if [ -r $d/vendor -a -r $d/device ]; then
        read VENDOR < $d/vendor
        read DEVICE < $d/device
        pat=`echo "^\([^ ][^ ]*\) *$VENDOR  *$DEVICE .*" | sed 's/0x/0x0*/g'`
        mod=`sed -n "s/$pat/\1/p" /lib/modules/$KERNVER/modules.pcimap`
        if [ -n "$mod" ]; then
            for m in $mod; do
                modprobe $m
            done
        fi
    fi
done
unset d pat mod m VENDOR DEVICE
umount /sys
modprobe af_packet
modprobe unix
modprobe ext3
modprobe xfs

if [ -n "$NFSROOT" ]; then
    ifconfig lo 127.0.0.1
    if ifconfig eth0; then
        ifconfig eth0 up
        sleep 3
        udhcpc --retries=2
        mount -t nfs $NFSPARM $NFSROOT /mnt
    fi
elif [ -n "$ROOT" ]; then
    mount $ROOTPARM $ROOT /mnt
fi

if [ -n "$INIT_DEBUG" ]; then
    set
    exec /bin/sh
fi

umount /proc
if [ -n "$AUFS" ]; then
    mkdir /.rw /.root
    mount -t aufs -o br:/.rw:/mnt=ro none /.root
    cd /.root
    if [ -n "$INIT_TMPFS" ]; then
        test -d initrd || mkdir initrd
        pivot_root . initrd
        initrd/bin/busybox rm -rf `echo $FILES /.root | sed 's@/@initrd/@g'`
        exec $INIT
    else
        cp -a /bin/busybox .
        rm -rf $FILES
        ./busybox mount --move . /
        exec ./busybox chroot . /bin/sh -c "rm ./busybox; exec $INIT"
    fi
fi
exec switch_root /mnt $INIT

ブートパラメータとして「INIT_DEBUG=1」を指定すると、 この /init スクリプトは /sbin/init を起動する代わりに /bin/sh を実行する。 つまり initramfs 環境でコマンドラインの手入力が可能になるので、 /init スクリプトのデバッグに役立つ。 実は前述した実行例は、 このデバッグ環境を利用したものである。

「nfsroot=」で NFS の設定を行なう。 そして「AUFS=1」を指定した場合は RW な tmpfs を重ねてマウントする。 例えば上述の例の、 NFS サーバ 192.168.1.1 の /fs/nfs/puppy ディレクトリをマウントする場合であれば、 次のようにブートパラメータを設定すればよい (pxelinux ブートローダの場合):

label puppy
kernel boot/vmlinuz
append initrd=boot/initrd.gz AUFS=1 nfsroot=192.168.1.1:/fs/nfs/puppy,ro,nolock root=/dev/nfs
Filed under: システム構築・運用,プログラミングと開発環境 — hiroaki_sengoku @ 06:53

1 Comment

  1. [vfs] lookup_one_len関数

    スタッカブルファイルシステムでのlookupに利用する。 普通に下層レイヤのlookup関数を呼び出せばいい気がするけれど、どうも駄目らしい。 static struct dentry *myiop_lookup(struct inode *dir, struct dentr

    Comment by fs_n314 — 2008年4月16日 @ 02:32

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.