Ubuntu Weekly Recipe

第750回LXDとUbuntuサーバーで、RAID障害時の再構築の予行演習と監視について確認する

今回はmdraidを使ったソフトウェアRAID環境における再構築(リビルド)を試してみましょう。RAIDにおける再構築とは、簡単に言うと「ストレージ障害の回復作業中にもう一台を壊す、もしくは実は壊れていたことに気づく」仕組みです。きちんと運用されていたら、壊れることになくシステムが回復する可能性は高いのですが、実際のところは作業者のKarma値ないしLuck値次第となります。1%の確率で起きる事象を100%ひいてしまう人が世の中にはいるのです。

そこで今回は少しでも再構築作業がスムーズに進むように、その予行演習を行いましょう。今回もLXDで構築した仮想RAID環境を利用します。LXDを用いてソフトウェアRAID環境を構築する方法は、次の回を参考にしてください。第748回ではRAID 1のみ、第749回ではそれ以外のRAIDレベルを紹介しています。

RAIDにおける再構築(リビルド)とは

RAIDは複数のストレージにデータを複製して書き込んだり、データのパリティを書き込むことで耐障害性をあげる仕組みです。つまりどれかのストレージが壊れたとしても、壊れていないストレージが一時的にデータを補完してくれます。

ポイントは「一時的」の部分です。ストレージがが壊れたまま使い続けても動くことは動くのですが、ここでさらにもう一台壊れると復旧はできなくなります[1]。そこでなるべくはやく壊れたストレージを交換して、回復作業を行わなくてはなりません。回復時は旧ストレージに保存されていた複製データやパリティなども新ストレージに書き込むことになります。この作業が再構築(リビルド)です。

言い換えると再構築というのは、RAID構成においてどれか1台以上が壊れたときに、システム管理者が人生をかけて祈りをこめる時間です。壊れていないほうのストレージの視点に立つと、なんか仕事仲間がいきなり行方不明になった上に、その仕事仲間の分以上の負荷をいきなりかけてきて、こちらをぶっ壊しに来ているんじゃないかと感じる時間でもあります。立場によって印象が変わる良い例でもあります。

再構築を試すには、RAIDに参加するストレージが「故障した」⁠うまく同期できていない」と判断されなければなりません。方法はいろいろありますが、一番手っ取り早いのが次のいずれかです。

  • 故障扱いとするストレージを取り外す
  • 故障扱いとするストレージを適当な値で上書きする
  • mdadmコマンドの--failオプションを使う

中途半端に読み書きできるとか、たまに特定のATAコマンドが失敗するとかそういう細やかなエラーをシミュレートするのは大変ですが、⁠特定のブロックが読めなくなった」とか「ストレージを認識しなくなった」程度であれば上記でも十分です。

ちなみに2番目の上書き方式についてはすぐにエラーを検知してくれるとは限りません。また、たとえばRAID 1の場合「どちらのデータが間違いか」というのがわからないため、正しい修復ができるかどうかは未知数です。

今回利用するLXD環境だと、2番目の上書き方式や3番目のmdadmコマンドを使う方式はオンラインで実施できますが、1番目のストレージの取り外しは一度インスタンスをシャットダウンしないといけない点に注意が必要です。

RAID 1でエラーを起こす

まずは第748回で構築したRAID 1環境(raid1インスタンス)を用いて、⁠ストレージを取り外して起動する」方法を試してみましょう。raid1インスタンスではストレージを1台(raid1b)を追加して、2台構成のRAID 1環境を構築していました。今回は単にこの2台のうち片方を取り外せばいいことになります。

第748回だとRAID 1は「LXDが最初から作るストレージ(root⁠⁠」と「あとから追加したカスタムストレージボリューム(raid1b⁠⁠」の2台構成で構築しました。このうちrootのほうは取り外すのが面倒です。実際に取り外そうとすると次のようなエラーになってしまうはずです。

$ lxc stop raid1
$ lxc config device remove raid1 root
Error: Cannot update root disk device pool name to "default"

LXDの場合、インスタンス生成時にdefaultプロファイルから自動的にrootストレージが作成されます[2]。これを正しく消すにはいろいろ手順がやっかいです。これが「raid1b」のほうならもっと簡単です。

ちなみにUbuntu上で/dev/sda/dev/sdbのどちらが、LXD的にどちらのストレージかを確認するには、ストレージのシリアル番号を見ることになります。具体的にはLXDのストレージは、シリアル番号にlxd_名前が入るようになっています。たとえばRAID 1の場合は、自動的に追加される「root」とRAID 1のために追加した「raid1」が、それぞれlxd_rootlxd_raid1bとなるわけです。

シリアル番号を確認する方法はいろいろありますが、Ubuntuに最初から入っているツール等を使うならlsblkコマンドを実行する、カーネルが作るファイルを直接見る、lshwコマンドの結果を読むあたりでしょうか。前者の2種類をそれぞれ紹介しましょう。

$ lsblk --nodeps -o name,serial /dev/sda
NAME SERIAL
sda  lxd_root

$ ls -l /dev/disk/by-id/ | grep "sda$"
lrwxrwxrwx 1 root root  9 Feb 11 11:52 scsi-0QEMU_QEMU_HARDDISK_lxd_root -> ../../sda
lrwxrwxrwx 1 root root  9 Feb 11 11:52 scsi-SQEMU_QEMU_HARDDISK_lxd_root -> ../../sda

これで/dev/sdaはLXD的にはrootであり、/dev/sdbはLXD的にはlxd_raid1bであることがわかります。そこで今回は、取り外しやすいlxd_raid1bを削除する前提で話を進めましょう。まず、インスタンスは終了しているものとします。

$ lxc storage volume detach raid raid1b raid1

これでraid1インスタンスからraid1bボリュームが見えなくなりました。試しにraid1インスタンスを起動しましょう。起動したら、次のようにステータスを確認してみてください。

$ cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid1 sda2[1]
      40806400 blocks super 1.2 [2/1] [_U]

unused devices: <none>

activesda2だけが表示され、状態がUUから_Uに変わっています。片方が見えなくなっているようです。

$ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Sat Feb 11 14:27:13 2023
        Raid Level : raid1
        Array Size : 40806400 (38.92 GiB 41.79 GB)
     Used Dev Size : 40806400 (38.92 GiB 41.79 GB)
      Raid Devices : 2
     Total Devices : 1
       Persistence : Superblock is persistent

       Update Time : Sat Feb 11 14:39:43 2023
             State : active, degraded
    Active Devices : 1
   Working Devices : 1
    Failed Devices : 0
     Spare Devices : 0

Consistency Policy : resync

              Name : ubuntu-server:0
              UUID : 17576c13:c379fb90:efd533d3:041cd124
            Events : 372

    Number   Major   Minor   RaidDevice State
       -       0        0        0      removed
       1       8        2        1      active sync   /dev/sda2

こちらもStatus : active, degradedになり、Total Devices等の数字や最後のデバイス一覧が変わっていますね。現在は1台だけで動いていることがわかります。これでRAID 1で、片側のストレージが見えていない状態になりました。

ちなみに「mdadmを使う方式」の場合は次のコマンドを実行するだけです。

$ sudo mdadm --fail /dev/md0 /dev/sdb2

こちらはmdadm --detailでみるとfaulty spareとなり、⁠アレイには参加しているけれども、activeではない」という扱いになります。

RAID 1のdegraded状態からの回復

さて、ここからRAID 1の回復を試みましょう。ここら先については状況次第で手順が変わります。なるべく一般的な方法で説明していきましょう。

もしmdadm --failで故障させた場合は、次のようにRAIDアレイから取り外しておきます。この状態で再起動すると、ストレージが1台見えないのと同じ状況になります。

$ sudo mdadm --remove /dev/md0 /dev/sdb2

次にストレージの交換です。マシンにもよっては電源が入ったまま入れ替えることも可能ではありますが、個人で使うPCならとりあえず電源を切ってからストレージの入れ替えになるでしょう。今回の環境はLXDなので、次のように新しいカスタムストレージボリューム「raid1c」を作成し、それをraid1インスタンスにアタッチしておきます。

$ lxc storage volume create raid raid1c --type=block size=40GiB
Storage volume raid1c created
$ lxc storage volume attach raid raid1c raid1

ここでマシンを起動することになるわけですが、できれば生き残っているストレージを優しく扱いところです。方法はいくつかあります。

  1. UbuntuサーバーのインストールISOファイルを使って、Live環境から回復させる
  2. Ubuntuの「recovery mode」で、他のプロセスを動かさずに回復させる
  3. 気にせず普通に起動して、普通に回復させる

ストレージに頻繁に読み書きするタスクが動く状況なら、できれば1番目か2番目の方法をとりたいところです。

ちなみに1番目については、RAIDアレイが自動的に読み込み専用(auto-read-only)で認識される点に注意が必要です。

図1 Live環境で起動すると「auto-read-only」となり、デバイス名も「/dev/md127」となっている(これはRAID 5環境の例)
図1

余計なことをせずにデータを退避したい場合は読み込み専用にしておくと安心ではありますが、もしLive環境で回復したければまず次の方法で書き込み可能にしておいてください。

# mdadm --readwrite /dev/md127

2番目の「recovery mode」については第639回のUbuntuに「トラブル時に」ログインするいろいろな方法にあるリカバリー用にシングルユーザーモードでログインするが参考になります。

1番目・2番目、いずれも起動してしまえばmdadmコマンドが使えるようになるので、ここから先は3番目と手順は同じです。

パーティションレイアウトの複製

今回はESPを同じストレージに保存した都合上、ストレージ全体がRAIDアレイに追加されているわけではなく、一部のパーティションのみが追加されている状態です。よって新しいストレージも前回と同じパーティションレイアウトにしないといけません。特にRAID 1の場合はRAIDアレイに参加している既存のストレージのうち最も小さいサイズに揃ってしまう都合上、パーティションのサイズにも気をつける必要があります。

パーティション分割せずにストレージ全部をまとめてRAIDアレイに参加させて良い場合は、この方法はスキップしてもOKです。ただしその場合、この先のmdadmコマンドに渡すデバイスファイル名が変わる(例:/dev/sdb1/dev/sdbになる)点に注意が必要となります。

パーティションレイアウトは、RAIDアレイに参加する他のストレージと同じにして良いなら、sfdiskを使うのが楽です。まずは次のように設定をスクリプトに変更します。

$ sudo sfdisk -d /dev/sda > /run/user/$UID/sda.part
$ cat /run/user/$UID/sda.part
label: gpt
label-id: 5A20BB4C-7401-480A-9D83-CAC8DAF8D8BC
device: /dev/sda
unit: sectors
first-lba: 34
last-lba: 83886046
sector-size: 512

/dev/sda1 : start=        2048, size=     2201600, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=4EE3366D-7B56-4F79-8DDF-56A65097E3C6
/dev/sda2 : start=     2203648, size=    81680384, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=57A881D9-556B-451C-932E-8AA3BBD5A044

このまま取り込むとlabel-iduuid等が/dev/sda/dev/sdbで同じになってしまいますので、消してしまいます。

$ grep -v "^lavel-id" /run/user/$UID/sda.part | sed -e 's/, *uuid=[0-9A-F-]*//' > /run/user/$UID/sda.part.new

これを/dev/sdbに反映しましょう。

$ sudo sfdisk /dev/sdb < /run/user/$UID/sda.part
$ sudo parted /dev/sdb print
Model: QEMU QEMU HARDDISK (scsi)
Disk /dev/sdb: 42.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name  Flags
 1      1049kB  1128MB  1127MB                     boot, esp
 2      1128MB  42.9GB  41.8GB

これで追加するストレージの準備ができました。

パーティションをRAIDアレイに参加させ再構築を開始する

パーティションの準備ができたので、RAIDアレイに参加させます。

$ sudo mdadm --add /dev/md0 /dev/sdb2
mdadm: added /dev/sdb2

重要なのは、追加するデバイスを間違えないことです。RAIDで問題が起きている状況は、およそ常人にとって正常な判断が行える状態ではありません。念には念を入れ、猫ではなく自分自身で指差し確認するのはもちろんのこと、一度軽く歩き回って深呼吸してからもう一度見るぐらいはやっておいても損はないでしょう。

もちろん今回はテスト環境なので失敗したところで大したことはないのですが、テスト環境から慎重に作業できないような人が、本番環境のトラブル時に慎重になれるはずがありません。

無事に追加されたら、今回のケースだと自動的に再構築が始まります。

$ cat /proc/mdstat
Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10]
md0 : active raid1 sdb2[2] sda2[1]
      40806400 blocks super 1.2 [2/1] [_U]
      [>....................]  recovery =  1.9% (801792/40806400) finish=2.4min speed=267264K/sec

unused devices: <none>

$ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Sat Feb 11 14:27:13 2023
        Raid Level : raid1
        Array Size : 40806400 (38.92 GiB 41.79 GB)
     Used Dev Size : 40806400 (38.92 GiB 41.79 GB)
      Raid Devices : 2
     Total Devices : 2
       Persistence : Superblock is persistent

       Update Time : Sat Feb 11 16:59:14 2023
             State : clean, degraded, recovering
    Active Devices : 1
   Working Devices : 2
    Failed Devices : 0
     Spare Devices : 1

Consistency Policy : resync

    Rebuild Status : 4% complete

              Name : ubuntu-server:0
              UUID : 17576c13:c379fb90:efd533d3:041cd124
            Events : 743

    Number   Major   Minor   RaidDevice State
       2       8       18        0      spare rebuilding   /dev/sdb2
       1       8        2        1      active sync   /dev/sda2

今回はサイズが小さくシンプルな構成なので、数分で終わりそうですがサイズが大きかったり、パリティ計算が必要だったりするとそれなりの時間がかかります。その間はできる限りそっとしておきましょう。必死にがんばっている時に、ちょくちょく「進捗どう?」って聞きにこられるとうざったいですよね。それと同じです。

/proc/mdstatfinish=に書いてある推定時間が経過したら、もう一度状況を確認してみましょう。無事に完了していたら万々歳です。もしLive環境やリカバリーモードなら、一度再起動して止めていたサービスなどを復旧させましょう。

回復後のESPの同期

システム用のストレージをRAIDとして組む時、ESP(EFI System Partition)はRAIDから除外した別のパーティションとして作る必要があります。第748回では、Ubuntuのインストール時に複数のパーティションをESPとして設定しておくことで、どのストレージから起動してもきちんと動いてくれるようインストーラーが適切に設定してくれることを紹介しました。

しかしながら、今回の手順で再構築した場合、ESPの領域は空のままになってしまいます。そこで、次の手順を用いて手動で同期しておきましょう。

/dev/sdb1をFATでフォーマットしておく
$ sudo mkfs.vfat /dev/sdb1
mkfs.fat 4.2 (2021-01-31)

念の為、/etc/default/grubをバックアップ
$ sudp cp /etc/default/grub{,.bak}

ESPを同期する
$ sudo dpkg-reconfigure grub-efi-$(dpkg --print-architecture)
Installing grub to /var/lib/grub/esp.
Installing for x86_64-efi platform.
Installation finished. No error reported.
Installing grub to /boot/efi.
Installing for x86_64-efi platform.
Installation finished. No error reported.
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.15.0-60-generic
Found initrd image: /boot/initrd.img-5.15.0-60-generic
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done
Processing triggers for shim-signed (1.51+15.4-0ubuntu9) ...

最後のコマンドは画面にさまざまな設定が表示されます。最初のコマンドラインの設定はUbuntuデスクトップならquiet splashが入っていますが、サーバーなら空のはずです。特別な設定が不要なら、どの項目も空のままOKかYESを押すだけで問題ありません。また、これらはコマンド実行後に、最初にバックアップをとった/etc/default/grub.bak/etc/default/grubに戻してsudo update-grub実行することでも元に戻せます。

一番重要なのが最後に表示される「どのデバイスにGRUBをインストールするか」を設定する画面です。ここで複数のパーティションを選択しておけば、それで同期は完了です。

図2 2台構成のRAID 1の場合は、このように2台の候補があらわれる。両方にチェックをいれておけばOK
図2

ここまで設定しておけば、GRUBパッケージ等の更新があった場合も、dpkgが自動的にすべてのESPのGRUBを更新してくれます。

RAIDのスーパーブロックについて

Linux MD/mdadmはストレージ上の「スーパーブロック」と呼ばれる領域にメタデータを書き込むことで、RAIDアレイを構成するストレージを認識します。RAIDアレイにストレージを追加する際に「過去のスーパーブロック」が残っていると、余計な問題を起こすことがあるので削除しましょう。

スーパーブロックの位置や内容はLinux MD/mdadmのメタデータのバージョンによって異なります。メタデータのバージョンはcat /proc/mdstatmdadm --detail /dev/md0で確認でき、現時点でRAIDを構築した場合はおそらく1.2になっていることでしょう。

$ cat /proc/mdstat
Personalities : [raid6] [raid5] [raid4] [linear] [multipath] [raid0] [raid1] [raid10]
md0 : active raid5 sdc2[1] sdb2[0] sda2[3]
      81612800 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
                            ^^^ ここがバージョン

$ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
                     ^^^ ここがバージョン

1.2の場合は「ストレージボリュームの先頭から4KiBの位置」にメタデータが保存されます。試しに見てみましょう。今回の構成例だと物理ストレージには最初にESPがあり、その後ろにRAIDに所属する物理ボリューム(パーティション)が用意されています。まずはESPの位置を確認してみます。

$ sudo parted /dev/sda unit KiB print
Model: QEMU QEMU HARDDISK (scsi)
Disk /dev/sda: 41943040kiB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start       End          Size         File system  Name  Flags
 1      1024kiB     1101824kiB   1100800kiB   fat32              boot, esp
 2      1101824kiB  41942016kiB  40840192kiB

Ubuntuサーバーのインストーラーの設定をそのまま使えば、ESPは上記のように1024KiBから1GiB程度確保されており、RAID用のパーティションは先頭から1101824KiBの位置にあるようです。つまり/dev/sda2なら先頭から4KiBの位置に、/dev/sdaならプラス4KiBして1101828KiBにスーパーブロックが存在することになります。

$ sudo dd if=/dev/sda2 status=none bs=4KiB count=1 skip=1 | hd
00000000  fc 4e 2b a9 01 00 00 00  00 00 00 00 00 00 00 00  |.N+.............|
00000010  65 e6 f3 27 8b 22 8a 65  00 0d e3 d1 5a e1 da 1c  |e..'.".e....Z...|
00000020  75 62 75 6e 74 75 2d 73  65 72 76 65 72 3a 30 00  |ubuntu-server:0.|

最初のfc 4e 2b a9(little-endianで0xa92b4efc⁠⁠」がメタデータのマジックナンバーです。このデータから、ここがLinux MDのスーパーブロックであることがわかります。

特定のストレージないしパーティションのスーパーブロックは次のコマンドで削除します。古いストレージを再構築で追加することは早々ないとは思いますが、もしそういう事態になったときは忘れないようにしておきましょう。

/dev/sda2のスーパーブロックの削除
$ sudo mdadm --zero-superblock /dev/sda2

mdadmの監視と通知

mdadmパッケージにはRAIDのステータスを監視し、通知を送る仕組みが最初から組み込まれています。

  • mdmonitor.service:mdadm --monitor --scanを起動し、RAIDの状態が変わった場合は/etc/mdadm/mdadm.confのメールアドレスに通知を送る
  • mdmonitor-oneshot.timer:毎日mdadm --monitor --scan --oneshotを実施し、その度に通知を送る

メールの送信はsendmailコマンドがインストールされている必要があります(postfixパッケージ等⁠⁠。また、mdadm.confPROGRAM行を追加することで任意のコマンドを実行可能です。今ならメールを送るよりも、任意のコマンドを使ってSlack等のチャットシステムに通知するほうがうれしいかもしれません。

PROGRAMの使い方についてはmdadm.confのmanページを参照してください。

定期的なスクラビング

RAIDはデータブロックにアクセスして初めて問題を認識します。つまりサイレントに何か問題が発生し、そのまま誰もアクセスしなかったら、誰も気がつけない状態となります。一番やっかいなのが「再構築するためにアクセスをした時にはじめて、実は問題ないほうも問題を抱えていたことが今さら判明した」パターンです。そこで必要になるのが定期的な全領域検査(scrubbing)です。

Linux MD/mdraidの場合、/sys/block/md0/md/sync_actioncheckを書き込むとスクラビングを開始します。スクラビング中は/proc/mdstatにもその旨が表示され、それなりの時間がかかります。ちなみにcheckだと問題を見つけても自動的に回復するわけではありません。詳細はmd(4)のmanページ「SCRUBBING AND MISMATCHES」を参照してください。

さて、Ubuntuの場合はこのスクラビングもmdcheck_continue.serviceが実施しています。実際の処理は/usr/share/mdadm/mdcheckで、これは単なるシェルスクリプトです。RAIDアレイを順番にスクラビングして、その完了を最大6時間待つという実装になっています。ただし、実施した結果、ミスマッチがどれくらい発生したか/sys/block/md0/md/mismatch_cntの値)は確認していないようです。

このスクリプトはsystemdのtimer機能により、⁠毎月最初の日曜の午前1時から24時間以内のどこか」で開始するようになっています。もし、この時間だと都合が悪いならsystemctl edit mdcheck_start.timerで、drop-inファイルを作って、OnCalendarRandomizedDelaySecだけ書き換えると良いでしょう。

$ systemctl cat mdcheck_start.timer
# /lib/systemd/system/mdcheck_start.timer
#  This file is part of mdadm.
#
#  mdadm is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.

[Unit]
Description=MD array scrubbing

[Timer]
OnCalendar=Sun *-*-1..7 1:00:00
RandomizedDelaySec=24h
Persistent=true

[Install]
WantedBy=mdmonitor.service
Also=mdcheck_continue.timer
$ systemd-analyze calendar 'Sun *-*-1..7 1:00:00'
  Original form: Sun *-*-1..7 1:00:00
Normalized form: Sun *-*-01..07 01:00:00
    Next elapse: Sun 2023-03-05 01:00:00 UTC
       From now: 3 weeks 0 days left

ちなみにシステムのシャットダウン時にスクラビングが動いている場合は、blk-availability.serviceによって実行された/sbin/blkdeactivateの中で、スクラビングが完了するまで待つことになります。つまり、スクラビングに時間がかかる環境だと、月始めの日曜日にシャットダウンしようとすると、シャットダウンまでに数時間かかることも起こりえます。

気になるようなら、blk-availability.serviceのdrop-inファイルを作って、ExecStop-r waitを消してしまいましょう。もしくはシャットダウン前にidlesync_actionに書くようにしておくのも良いでしょう。

最後に重要なことを。第748回からここまで3回にわたってRAIDの記事を読んだ方なら自明かもしれませんがRAIDはバックアップではありません⁠。

おすすめ記事

記事・ニュース一覧