2014年2月4日

ルートファイルシステムはいじってはいけない

数年前から稼働している我が家のバックアップ専用サーバは2TBのHDDを6台使っている。パーティションは三つのRAIDデバイスで
  • md0 - RAID1 /boot
  • md1 - RAID5 //varを含むLVM
  • md2 - RAID6 /var/backup バックアップデータ専用
と言う構成。このバックアップ専用サーバ構築の前に使っていたHDD×4のRAID5(メインのファイルシステム)+HDD×2のJBOD(バックアップ専用)という構成のメインの単一の共通サーバのメインのRAID5のHDD2台が同時に死に、辛うじてバックアップ専用のJBODから数十年分の家族の写真ファイルなどを救出できたという経験からバックアップの重要性を思い知らされたので作った。一日24時間、週7日間我が家の全てのPCとサーバのデータをバックアップしている。

最初は、このサーバは4台のHDDで作成され、後にHDDを2台追加した。md0はHDD×4のRAID1からHDD×6のRAID1に、md2はHDD×4のRAID5からRAID6に拡張されたが、md1はHDD×4のRAID5+スペアHDD×2と言う構成のままだった。
 
本業の仕事でRAIDの内部を扱うことになり、何となく我が家のバックアップサーバを眺めて、md1がRAID5のままのことに気がつき、深く考えもせずに残りの2台を組み込んだRAID6に拡張したほうが良いような気がして作業を始めてしまったが、これが大きな誤りだったことに後で気がつく。今回はこの顛末記。

何が誤りだったかと言うと
  • 不注意: mdadmの--growオプションを--backup-file=無しで走らせた。
  • そもそもの誤り: ルートファイルシステムはみだりに変更してはいけない。
と言うこと。

まず、--backup-file=について。

RAIDの構成を変更する(RAIDのレベルを上げたりHDDを増やす)ときは、もしクリティカルセクション実行時に異常が起きても回復できるように--backup-file=を使って回復のためのデータを保持しておくべきなのだが、これを怠った。そしてマーフィの法則が示すように、構成変更作業中に停電が起きた。もちろんサーバはUPSを使っていたが、停電は数時間に及び、電源の回復前にシャットダウンしてしまった。ちなみに、当地はシリコンバレーと言う土地柄にも関わらず、日本では考えられないぐらい停電が多い。例年、3~4回の停電はざらだ。多くは嵐で立ち木が倒れて電線を切断するためだが、今回の停電は穏やかな日の夜明け前に起こり、原因は何だだったのか知らない。

サーバを立ち上げようとしたが、「ルートファイルシステムが見つからない」と言うエラーでフリーズしてしまう。冷や汗が流れてくるが、幸いなことに本機はバックアップサーバだから、1週間ぐらい死んでいても実用上問題はない。と言うことで、落ち着き、缶ビールを開けて修復作業を始める。

FedoraのインスタレーションCDを使ってレスキューモードで回復を図るが、何度やってもmd1のクリティカルデータが壊れているのでリシェープできないと言う。クリティカルセクションのバックアップファイルを作っておかなかった報いだ。

仕方がないので回復は諦め、md1のパーティションを作り直すことにする。幸いなことに、我が家にはもう一台バックアップ専用のアプライアンスがあり、こちらにこのバックアップサーバのバックアップデータ以外はすべて定期的にバックアップしてある(つまり我が家のシステムは別の部屋に設置した別のハードウェアに二重にバックアップしている)から、ファイルシステムを作り直してバックアップから回復すれば良いだろう。

と、希望的に考え、粛々と回復作業を行うが、一向に回復しない。ここで行った試行錯誤から、徐々に失敗の理由が明らかになってきた。すなわち、
ルートファイルシステムは他のファイルシステムと違い、ブートローダと密接にリンクしている
と言うことだ。

だから、単にファイルシステムを回復しただけではブートローダに認識されない。ルートファイルシステムそのもの(このサーバではルートファイルシステムを含むLVMが存在するRAID)とブートローダのポインタがちゃんと整合していなければならないのだ。

さて、実際の回復の道のりを思い出すだけ書いてみる。

対象のホストは「shadow」と言う名前で、問題のルートファイルシステム(オリジナル)は
  • LVM上の/dev/mapper/vg_shadow-LogVol00
  • LVMは4台のドライブからなるRAID5の/dev/md1
  • RAID5の構成物理ドライブは
    /dev/sda2
    /dev/sdb2/dev/sdc2/dev/sdd2(アクティブ)
    /dev/sde2
    /dev/sdf2(スペア)
これをRAID6に変換しようと言うスケベ心を出したのが間違いだったので、元通りのRAID5を復活させる。 方針として、各種設定ファイルはオリジナルのままとして、RAIDやLVM、FSなどをオリジナルの設定ファイルに整合するように再構築する。

整合してなければならないことが確認できたのは以下の識別子。 

/boot/grub/grub.conf内のブートメニュー項目のデバイス名とUUID。
shadow# cat /boot/grub/grub.conf
...
title Fedora (2.6.34.9-69.fc13.i686.PAE)
        root (hd5,0)
        kernel /vmlinuz-2.6.34.9-69.fc13.i686.PAE ro root=/dev/mapper/vg_shadow-LogVol00 rd_MD_UUID=cc8c918f:5b720e12:5c2a14a8:2e918d19 rd_LVM_LV=vg_shadow/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.9-69.fc13.i686.PAE.img

...


●LVMを構築する時に使う/etc/lvm/archive/vg_shadow_00000.vgの物理ボリューム(PV)のUUID。
shadow# cat /etc/lvm/archive/vg_shadow_00000.vg
...
         physical_volumes {

                pv0 {
                        id = "rLojRP-Ms7b-VKlA-p7Cj-n1nL-0MAy-nLy4Ty"
                        device = "/dev/md1"     # Hint only

...

●実際に構築されたRAIDデバイスのRAIDレベル、メタデータバージョン、ディスク台数、UUID。
shadow# mdadm -Esv
ARRAY /dev/md/0 level=raid1 metadata=1.0 num-devices=6 UUID=ec0da04a:e6ae42d2:bc3eb03c:7373ea18 name=localhost.localdomain:0
   devices=/dev/sdd1,/dev/sdb1,/dev/sdf1,/dev/sde1,/dev/sdc1,/dev/sda1
ARRAY /dev/md/1 level=raid5 metadata=1.1 num-devices=4 UUID=cc8c918f:5b720e12:5c2a14a8:2e918d19 name=localhost.localdomain:1
   spares=2   devices=/dev/sdd2,/dev/sdb2,/dev/sdf2,/dev/sde2,/dev/sdc2,/dev/sda2
ARRAY /dev/md/2 level=raid6 metadata=1.2 num-devices=6 UUID=74c85cf7:e8d75a3f:6daa34d5:db1226e7 name=shadow.arkusa.com:2
   devices=/dev/sdd3,/dev/sdb3,/dev/sdf3,/dev/sde3,/dev/sdc3,/dev/sda3
shadow# blkid /dev/md1
/dev/md1: UUID="rLojRP-Ms7b-VKlA-p7Cj-n1nL-0MAy-nLy4Ty"

●そして最後にINITRAMFS内の/etc/mdadm.confのRAIDレベル、ディスク台数、UUID
shadow# mkdir /tmp/initrd
shadow# cd /tmp/initrd
shadow# cp /boot/initramfs-2.6.34.9-69.fc13.i686.PAE.img .
shadow# gunzip -c initramfs-2.6.34.9-69.fc13.i686.PAE.img | cpio -i
shadow# cat etc/mdadm.conf
ARRAY /dev/md1 level=raid5 num-devices=4 UUID=cc8c918f:5b720e12:5c2a14a8:2e918d19


ファイルシステム、あるいはそのファイルシステムが存在するLVMやRAIDを認識する識別子には以下の3種類がある。
  1. デバイスファイル名(例: /dev/md1
  2. ラベル(例: LABEL=/
  3. UUID
下に行くほどモダンで曖昧さが薄れる。

驚くべきことに、UUIDには少なくとも3種類の表現方法があるらしい。
  1. UUID="67947017-6b60-1dbe-8457-1880d35f9d0e" 「blkid」の出力など
  2. UUID="67947017:6b601dbe:84571880:d35f9d0e" 「mdadm -Esv」の出力など
  3. UUID="nGR8l1-6jaI-SNbg-ILSp-Oh8o-53bM-oXm45a" LVMの「blkid」の出力、/etc/lvm/archive/xxxx.vgなど
上記の三つは、同じオブジェクトのUUIDで、全て同じ値を示す。1と2はハイフンとコロンの桁区切りの違いだけのようだが、3はBase64風で何だかよく分からない。

なお、Universally Unique IDと言っても、RAIDの構成メンバの個々のUUIDとRAIDそのもののUUIDは全て同じ。例えば、/dev/md0/dev/sda1/dev/sdb1/dev/sdc1/dev/sdd1から成り立っていると、これらの5台のオブジェクトのUUIDは全て共通。

システムをFedoraのCDのレスキューモードで起動。ネットワークを使えるようにしておくと便利。レスキューするファイルシステムはマウントしないようにする。

まずRAIDを再構築。RAID5以上の作成には--createオプションを使う。以前にRAIDに使われていたディスクを使うときはシグネチャを検査して上書きの警告をしてくるが、「OK」を選択する。
shadow# mdadm --create /dev/md1 --level=5 --raid-devices=4 /dev/sda2 /dev/sdb2 /dev/sdc2 /dev/sdd2

このままではランダムなUUIDが与えられてしまう。
shadow# mdadm -Esv
ARRAY /dev/md1 level=raid5 num-devices=4 UUID=86f7ac38:284c3bb5:bfe78010:bc810f04

他の正常なRAIDデバイス名が「/dev/md/N」の形で表示されるのに「/dev/md1」であり、「metadata」の表示もない。
そこでちょっとトリックを使う。
一旦RAIDを停止し--metadataオプションを追加して作成し直し。
shadow# mdadm --stop /dev/md1
shadow# mdadm --create /dev/md1 --metadata=1.1 --level=5 --raid-devices=4 /dev/sda2 /dev/sdb2 /dev/sdc2 /dev/sdd2

もう一度RAIDを停止してUUIDをアップデートするオプションでアセンブルし直し。
shadow# mdadm --stop /dev/md1
shadow# mdadm --assemble /dev/md1 --update=uuid --uuid=cc8c918f:5b720e12:5c2a14a8:2e918d19 /dev/sda2 /dev/sdb2 /dev/sdc2 /dev/sdd2
shadow# mdadm -Esv

ARRAY /dev/md/1 level=raid5 metadata=1.1 num-devices=4 UUID=cc8c918f:5b720e12:5c2a14a8:2e918d19
    devices=/dev/sda2,/dev/sdd2,/dev/sdc2,/dev/sdb2 
shadow# blkid /dev/md1
/dev/md1: UUID="rLojRP-Ms7b-VKlA-p7Cj-n1nL-0MAy-nLy4Ty"

めでたくRAIDが正しいUUIDで復活した。

mdadmの--addオプションを使って、残りの2台のディスクをスペアとして参加させておく。スペアがあれば、アクティブなディスクのどれかが死んだときにすぐに代替ディスクとして組み込まれ、リシンクが実行される。リシンク完了までに他のアクティブディスクが死ななければデータは完璧に保存される。実は、RAID6はこのリシンクの作業をリアルタイムに分散して実行していると見ることもできる。
shadow# mdadm /dev/md1--add /dev/sde2 /dev/sdf2


次に、LVMの設定ファイルを使ってLVMを復活させる。1番目のvgrestoreでレイアウトを復活させ,2番目のvgchangeでLVMを起動する。
shadow# lvm vgcfgrestore --file vg_shadow_00000.vg vg_shadow
shadow# lvm vgchange -a y vg_shadow
  Restored volume group vg_shadow
  2 logical volume(s) in volume group "vg_shadow" now active
blkid /dev/md1
/dev/md1: UUID="rLojRP-Ms7b-VKlA-p7Cj-n1nL-0MAy-nLy4Ty" TYPE="LVM2_member"

このLVM設定ファイルにはルート(/)と/varの二つのLVが記述されていたので、それぞれが作成される。UUIDはランダムな値が設定されるが、これはどことも整合させる必要がないのでそのまま。
shadow# ls -l /dev/mapper
total 0
crw-rw----. 1 root root  10, 62 2014-02-04 07:41 control
brw-rw----. 1 root disk 253,  1 2014-02-04 08:27 vg_shadow-LogVol00
brw-rw----. 1 root disk 253,  0 2014-02-04 08:27 vg_shadow-LogVol01_var

ここまできたらほとんどできたも同然。それぞれのLVにmkfsでファイルシステムを作り、バックアップからファイルをリストアして、レスキューCDの/mnt/sysimageに仮マウントできることを確認したらリブート。

まあ、人間必ずチョンボがあるので、一発で完全回復はできないかもしれないが、完璧なバックアップさえあれば順序よく誤りを潰していけば必ず復活できる。

ちなみに小生が犯した最後のエラーは、バックアップに/var/runを含めていなかったこと。この頃のFedora13のディストロでは、/var/run内にサブディレクトリを作るデーモンのいくつかは、サブディレクトリをランタイムに作成せずインスタレーション時に作成することを仮定していたので、それらのデーモンが機動できなかった。これは、他のFedora13の/var/runをそっくりコピーして完治。バックアップファイルセットから/var/runの除外を消すことも実施して完璧な修復ができた。

教訓をもう一度。

ルートファイルシステムはブートローダと密接な関係があるので決して構成を変えてはいけない。

延べ3日の作業は高い代償だったが、RAIDとLVMの修復の経験を積んだので価値ありとする。