
mdadm による RAID1 を rootfs にしてハマった件
我が家のルータは Gentoo Box だが、朝起きると Kernel Panic になっていた。再起動してみると……
BIOS から SSD を認識しないようになっていた……
Table of Contents
きっかけ
今回の敗因は、我が家のゲートウェイサーバとして PPPoE やリバースプロクシが稼働している上に、普段使っているメールアドレスを運用しているという割と重要なポジションにあるマシンにもかかわらず、ディスクの多重化を行っていなかったということに尽きる。
他のサーバ群はすべてハードウェア RAID カードで RAID10 で運用しているが、ここだけ SSD 1 本で稼働していた。以前 SSD に交換してからかれこれ 5 年ほど経ち、何度か予兆もあったものの見なかったことにして運用してきていた。
幸いなことに今回は 12 時間ほどおくと SSD が復活してきたこともあり、データのロストは皆無で済んだ。さすがに重要なデータを保持しないゲートウェイにハードウェア RAID カードはオーバースペックと思うものの、ディスク障害時のダウンタイムをある程度短くしたいこともあって mdadm による RAID 1 を構築することにした。
ハマった原因
先にハマった原因を書いておくと、
- Gentoo Linux で initramfs を使わないシンプルな運用にしていた。
- この場合、initramfs を使わないと RAID デバイスノードに対して一意の名前が振られないため、ブート時の rootfsを正しく指定できない。- ベアなドライブなら GPT にして PARTUUIDで無理矢理指定できるが、/dev/mdnデバイスの場合はPARTUUIDが振られないのでrootfsでUUIDを指定できない。
 
- ベアなドライブなら GPT にして 
- そこで initramfsを用意しておいて、カーネル読み込み時にmdadmでデバイスノードを UUID ベースで一意に割り振るようにしておき、カーネルパラメータで init 時のルートデバイスをroot=/dev/md0などと指定できるようにする。
Ubuntu や CentOS だとだいたい initramfs に含まれる busybox と mdadm でよしなにやって貰えるので意識する必要はないが、Gentoo Linux の場合は genkernel していないと initramfs を敢えて使わないのでハマるかも知れない。
結局、久しぶりに半日以上 VFS: Unable to mount root fs on unknown-block(0,0) で kernel panic になる問題にハマってしまった。
手順
まず最初に
mdadm noob なのでライブで RAID1 構築できないか色々模索してみたが、mdadm --create してスーパーブロックを書き換えると再度 mkfs する必要がありそうで、ライブで RAID1 にはできなかった。
今回は古い SSD からの移行なので、サクッと新しい SSD で mkfs.ext4 してからデータを書き込むことにし、minimal iso でまず再起動した。
アレイ構築
パーティション作成
まずはアレイ構築が必要だが、パーティションそのものは普通にドライブごとに fdisk なりで切っていく。このときに気をつけるのは、同一アレイに含めるパーティションのサイズを完全一致させていく必要があること。
パーティションが切れたら、t コマンドでパーティションタイプを 0xfd (Linux raid autodetect) にしておく。
アレイ構築
パーティションが用意できたらアレイを構築するが、まずはデバイスノードが存在するか確認して必要に応じて作っておく。我が家のルータの場合は /dev/md0 しかなかったが、swap パーティション以外に 3 パーティションに分けて運用しているので、/dev/md2 までは自分で mknod した。
# mknod /dev/md1 b 9 1 # mknod /dev/md2 b 9 2
デバイスノードができたらサクッとアレイを構築する。
# mdadm --create /dev/md0 --level=1 --metadata=0.90 --raid-disks=2 /dev/sda1 missing
mdadm: /dev/sda1 appears to contain an ext2fs file system
       size=262144K  mtime=Sat Jun 11 03:16:15 2016
Continue creating array? y
mdadm: array /dev/md0 started.
今回は旧 SSD からのデータのコピーが必要で、SATA3 ポートが 2 ポートしかなかったので、いったんは 1 台でアレイを作っておき、コピーが終わった段階で 2 台構成に変更する。
なお、当初はブートローダで grub 0.99 を使っていたので /boot は v0.9 を明示したが、最終的に grub2 に移行したのでこれは不要だったと思う。
mdadm --create が完了したら mkfs する。
# mkfs.ext4 /dev/md0
最近は btrfs や ZFS on Linux が熱かったりするが、ここは安パイで ext4 にしておく。そもそもブートパーティションを ext4 にする必要はないと思うが……
mkfs が終わったらアレイの構築は完了となる。
データの移行
アレイが構築できれば、旧ドライブから新ドライブにデータを移す。
# mkdir /mnt/source # mkdir /mnt/dest # mount /dev/sdb1 /mnt/source # mount /dev/md0 /mnt/dest # rsync -av /mnt/source/* /mnt/dest
データを移す方法はdd でコピーするのが定番だと思うが、今回は SSD の容量が倍増したこともあり、resizefs が面倒なので rsync でコピーした。シンプルに cp するのもいいが、意外に時間がかかることとデバイスノードが面倒なので rsync が楽でいいと思う。
カーネル再構築と initramfs の生成
chroot 環境
今回一番ハマったところがここ。ここからは chroot 環境で作業をするので、まず chroot ためにアレイとスペシャルデバイスをすべてマウントする。
# mount /dev/md1 /mnt/gentoo # mount /dev/md0 /mnt/gentoo/boot # mount /dev/md2 /mnt/gentoo/home # mount -t proc proc /mnt/gentoo/proc # mount --rbind /dev /mnt/gentoo/dev # mount --rbind /sts /mnt/gentoo/sys
次に、mdadm --detail --scan の結果を /etc/mdadm/mdadm.conf として保存する。
# mdadm --detail --scan > /mnt/gentoo/etc/mdadm.conf
このファイルは initramfs にも含めるし、RAID 環境下で正しくデバイスノードを割り振るための必須ファイルとなる。
ここまでできたら必要に応じて /etc/resolv.conf をコピーして chroot する。
# cp /etc/resolv.conf /mnt/gentoo/etc/resolv.conf # chroot /mnt/gentoo /bin/bash # source /etc/profile
initramfs
さて、Linux の起動はざっくりと
- ブートローダ実行
- カーネルイメージ展開・起動。
- カーネルパラメータ rootで指定されたデバイスを read only でマウントし、initプロセス起動。
- rootで指定されたデバイスをアンマウントし、- /etc/fstabに従ってマウント。
だが、initramfs を作って mdadm を入れておかないと 3. で rootfs で指定するデバイスを正しくマウントできず、kernel panic になってしまう。
Gentoo Linux だと、場合によっては initramfs と縁遠い生活をしている場合がある。そのような場合でも /dev/mdn を rootfs に指定する場合は initramfs を用意する必要がある。
Gentoo の場合は genkernel で initramfs を作るので、まず genkernel をインストールする。
emerge genkernel
無事インストールできれば、genkernel コマンドで initramfs を生成する。
genkernel --mdadm --mdadm-config=/etc/mdadm.conf initramfs
カーネル再構築
別に genkernel してもいいのだが、make menuconfig に慣れているので普通にカーネルを再構築する。
基本的に Device Drivers > Multiple devices driver support (RAID and LVM) 以下にある RAID support がチェックされているかを確認してカーネルを再構築する。
grub2 の設定
結構前から運用しているとリスク回避でブートローダーは grub 0.99 のままだったりすると思うが、うちのルーターの場合はこれを機に grub2 に移行した。
grub2 の場合、まず /etc/default/grub に以下を追記する。
GRUB_CMDLINE_LINUX_DEFAULT="domdadm"
追記したら /etc/grub/grub.cfg を書き出す。
# grub-mkconfig -o /boot/grub/grub.cfg
grub2 の場合、grub 0.99 と違って grub-mkconfig することで現在のマウント状況からよしなに rootfs を書いてくれるので、カーネルオプションについて色々考えなくていいのが楽でいいと思う。
/etc/fstab の書き換え
RAID1 に移行したことにより、/etc/fstab に書くデバイスノード名も /dev/sdxn から /dev/mdn に変わるので書き換えるか、これを機に UUID に移行するといいと思う。個人的には UUID は見通しが悪くなる気がするので /dev/mdn 形式で書いているが……
/dev/md0 /boot ext4 noatime,noauto 0 1 /dev/md1 / ext4 noatime,discard 0 1 /dev/md2 /home ext4 noatime,discard 0 1 /dev/sda2 none swap sw 0 0
なお、SSD に無駄にデータを書かれても寿命を削るだけということもあり、うちのルータでは最低限のスワップ領域利用に抑えるために /etc/sysctl.conf に以下を追記しており、ほぼほぼスワップしていない。そのため /dev/sdxn のままにしているが、メモリを多めに使う場合は不慮の OOM killer 祭りに備えてスワップ領域も RAID にしておくといいと思う。
vm.swappiness = 0
これで設定は完了なので、minimal iso を抜いて再起動する。
RAID1 への移行
無事に起動すれば、/dev/sdxn から /dev/mdn で稼働するようになっているはずだが、まだシングルデバイスで動作しているにすぎない状態となる。
そこで各アレイに別途用意したドライブのパーティションを追加し、RAID1 としてミラーリングさせるようにする。
# mdadm --add /dev/md0 /dev/sdb1
これだけだが、複数パーティションある場合は、リビルドが完了するまで次の mdadm --add を待っておく。完了したかどうかは cat /proc/mdstat で確認できる。
# cat /proc/mdstat 
Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4] [multipath] [faulty] 
md2 : active raid1 sdb4[2] sda4[0]
      174598272 blocks super 1.2 [2/1] [U_]
      [>....................]  recovery =  0.2% (450304/174598272) finish=12.8min speed=225152K/sec
      bitmap: 2/2 pages [8KB], 65536KB chunk
md1 : active raid1 sdb3[2] sda3[0]
      67043328 blocks super 1.2 [2/2] [UU]
      
md0 : active raid1 sdb1[1] sda1[0]
      262080 blocks [2/2] [UU]
      
unused devices: <none>
すべてのアレイに対して作業が済めば無事アレイ構築完了となる。
ブートローダのインストール
あとは /dev/sdb にも grub2 をインストールしておく。
# grub-install /dev/sdb Installing for i386-pc platform. Installation finished. No error reported.
インストールしなくてもデータは保持されるが /dev/sdb 側で起動しなくなるのでイマイチ高可用性に欠ける事態になってしまう。
こうやって書くと意外にシンプルだが、なんだかんだで 24 時間ほどかかってしまった。次からは普通にハードウェア RAID カードにするかも知れない。


