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 カードにするかも知れない。