mdadm による RAID1 を rootfs にしてハマった件


我が家のルータは Gentoo Box だが、朝起きると Kernel Panic になっていた。再起動してみると……

SSD has gone

BIOS から SSD を認識しないようになっていた……

きっかけ

今回の敗因は、我が家のゲートウェイサーバとして PPPoE やリバースプロクシが稼働している上に、普段使っているメールアドレスを運用しているという割と重要なポジションにあるマシンにもかかわらず、ディスクの多重化を行っていなかったということに尽きる。

他のサーバ群はすべてハードウェア RAID カードで RAID10 で運用しているが、ここだけ SSD 1 本で稼働していた。以前 SSD に交換してからかれこれ 5 年ほど経ち、何度か予兆もあったものの見なかったことにして運用してきていた。

幸いなことに今回は 12 時間ほどおくと SSD が復活してきたこともあり、データのロストは皆無で済んだ。さすがに重要なデータを保持しないゲートウェイにハードウェア RAID カードはオーバースペックと思うものの、ディスク障害時のダウンタイムをある程度短くしたいこともあって mdadm による RAID 1 を構築することにした。

ハマった原因

先にハマった原因を書いておくと、

  • Gentoo Linux で initramfs を使わないシンプルな運用にしていた。
  • この場合、initramfs を使わないと RAID デバイスノードに対して一意の名前が振られないため、ブート時の rootfs を正しく指定できない。
    • ベアなドライブなら GPT にして PARTUUID で無理矢理指定できるが、/dev/mdn デバイスの場合は PARTUUID が振られないので rootfsUUID を指定できない。
  • そこで initramfs を用意しておいて、カーネル読み込み時に mdadm でデバイスノードを UUID ベースで一意に割り振るようにしておき、カーネルパラメータで init 時のルートデバイスを root=/dev/md0 などと指定できるようにする。

Ubuntu や CentOS だとだいたい initramfs に含まれる busyboxmdadm でよしなにやって貰えるので意識する必要はないが、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 を使っていたので /bootv0.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 の起動はざっくりと

  1. ブートローダ実行
  2. カーネルイメージ展開・起動。
  3. カーネルパラメータ root で指定されたデバイスを read only でマウントし、init プロセス起動。
  4. root で指定されたデバイスをアンマウントし、/etc/fstab に従ってマウント。

だが、initramfs を作って mdadm を入れておかないと 3. で rootfs で指定するデバイスを正しくマウントできず、kernel panic になってしまう。

Gentoo Linux だと、場合によっては initramfs と縁遠い生活をしている場合がある。そのような場合でも /dev/mdnrootfs に指定する場合は 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 がチェックされているかを確認してカーネルを再構築する。

make menuconfig

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