Chienomi

ディスク故障でbtrfsの機能を使い倒す

snapshot

対応方法

ディスク容量が逼迫しているので何らかの対処が必要である。 とりあえず考えられる方法としては

  • ディスクを大容量のものに交換する
  • USBあるいはeSATA接続のディスクを追加する
  • NASを追加してディスクを追加する
  • AoEを使って他ホストのディスクを追加する
  • 内蔵ディスクを追加する

のいずれかだ。そもそも取り扱うデータ量が多いため、削減ということは考えられない。 もちろん、削減自体は行っているが、その精査にあてる時間のほうが重要で、無効な対策であると考えられる。それほど引き延ばすこともできない。

大容量への交換は効果的だが、現在3TBを使用しており、4TBの場合は33%の容量増となり、不足である。 6TB以上は高額すぎるため却下された。

USB/eSATAを最も有力な候補としていたが、台数搭載するためには金額的にも高く、またいずれの製品も信頼性に劣るようだ。

多数搭載のケースを使用するのであればReadyNASも有力な候補だった。ReadyNASはiSCSIに対応するが、「ReadyNASに搭載されているディスクを指定する」ことはできない。何台搭載しても、btrfs的に見れば1台にしかならないのだ。

そのため、btrfsで管理するためにはReadyNASを複数台追加する必要がある。ReadyNAS全体でbtrfsから見て1台のディスクであり、ReadyNASの台数=ディスク台数になる。 btrfsは不均等なRAID1をサポートするため、例えばReadyNASに12TBを搭載すれば12TB RAID1を構成することが可能だが、あまりうれしくはない状態になる。

AoEを使うのは追加費用のかからない方法だ(ディスクとホストはあるため)。 だが、問題はAoEを使う方法は常に

  • マウント前にターゲットホストを起動していなければならない
  • アンマウント前にターゲットホストを終了してはいけない
  • ネットワークを切断してはいけない

と結構厳しい条件になる。 ターゲットホストが消費電力が大きく、また冗長系システムであり、日常稼働しているものであることを考えるとこれは厳しい。

そこで内蔵ディスク追加を選択した。

Z400の3.5inchシャドウベイの数は2、SATAポート数は6である。 現在、iStarUSAの5.25inchベイ*2を3.5inch HDD*3に変換するリムーバブルラックを使用してシステムSSDを含む計5台を搭載している。 そのため、2台追加となると、マウント的にもSATAポート的にも足りない。

採用した方法はエアリアのSATA*2 PCI-e 1xカードによりポートを2増設し、DVDドライブを除去してiStarUSAの5.25inch*3をHDD*5に変換するリムーバブルラックを搭載するというもの。

かなり無茶だが、さらに無茶なのが、そもそもビデオカードがGeForce GTX750Tiに変更されているために、隣のPCI-e 4xが埋まってしまっており、逆側のPCI-e 4xは使用済みなので、PCI-e 16xに接続した、ということか。

ディスク追加手順

ディスクの追加は、ディスクを装着した状態で

# btrfs device add /dev/sdx /path/to/volume
# btrfs device add /dev/sdy /path/to/volume
# btrfs balance /path/to/volume

のようになる。 balanceの所要時間は果てしなく、うちのケースでは40時間ほどかかった。

そして、balanceがまもなく終わろうという頃に、もともとあったほうのディスクが壊れた。

故障ディスクの除去と交換

故障ディスクが正常にアクセスできない状態にあるとき、btrfsファイルシステムへのアクセスはシステム全体を止めてしまう。 この状態ではbtrfs device deleteもできない。

もしこれが可能な状態であればdeleteよりも新規ディスクも装着した状態でのreplaceのほうが早い。

故障ディスクはオフラインの状態でまず除去してしまい、その上で新規ディスクに交換する。 そして、新規ディスクに対して

# btrfs device add /dev/sdx /path/to/volume

したあと、

# btrfs device delete missing /path/to/volume

するとreplaceに近い挙動になる。

しかし、実際にやるとシステムが死んでしまった。ログにがっつりRIPが残っていた。

ファイルシステム新規作成

もはや修復は不可能なので、ファイルシステムを再作成してバックアップから書き戻す。

dm_crypt上に作成するのであれば、dmデバイスを用意したうえで

$ sudo mkfs.btrfs -L labelname -d raid1 -m raid1 /dev/mapper/crypt_dev_*

こうしてみると気づくのだが、btrfsのミラーリングはミラーレッグの指定ができない。 ジャーナリングの強化版というコンセプトなので当然なのかもしれないが、結構不安。本物のRAID1がほしい人はLVMを使うほうが良さそう。LVM+Btrfsであれば、2台ずつ容量を揃える必要があるが、容量の異なるディスクを混在できる。

バックアップのbtrfsファイルシステムからのリストア

btrfsにはsend/receive機能があり、簡単にバックアップができる。

btrfs sendは差分も可能でストリームで送られる。つまりファイルとして保存することもできる。 この場合復元はcatによって行うことになるだろう。差分だとどうなるのかはわからない。

recieveはsendによって出力されるストリームを元にファイルへと書き出す。

ネットワーク越しのバックアップ手段として行う場合は、権限的な難しさがある。send/receiveはroot権限が必要なためだ。 rsyncであればユーザー権限でのバックアップが可能だが、send/receiveを使う場合はそうはいかない。 受け渡しをssh経由で行おうとすると、rootでのsshアクセスを許していなかったりして結構なネックになる。 別に鍵を作れば良い話ではあるが。

sudoを用いたコマンドラインで行うのであれば、socatを使用する方法がある。receive側は

$ socat tcp-listen:30000 | sudo btrfs receive /path/to/volume

send側は

$ sudo btrfs subvolume snapshot -r /path/to/volume /path/to/snapshot
$ sudo btrfs send /path/to/snapshot | socat stdin tcp-connect:receiverhost:30000

経路安全性を問題にするのであれば、openssl-listenを使うか、ssh -Lを使うなどの方法がある。 インターフェイスを限定できないのが問題なら、rubyワンライナーを使う方法や、もうひとつプログラムを増やし、ソケットで読んで書き込む仕様として、ssh経由でソケットに出力するプログラムを起動するという方法もある。

例えば次のようなスクリプトである。

#!/usr/bin/zsh
zmodload zsh/net/socket

zsocket -l /tmp/btrfs-syncer
sock=$REPLY
while zsocket -a $sock
do
  (
    btrfs receive /path/to/volume <&$REPLY
  ) &
done

こちらはrootで実行する。対してsshで実行すべきは

#!/usr/bin/zsh
zmodload zsh/net/socket
zsocket /tmp/btrfs-syncer
cat >&$REPLY
exec {REPLY}>&-

あとは

$ sudo btrfs subvolume snapshot -r /path/to/volume /path/to/snapshot
$ sudo btrfs send /path/to/snapshot | ssh receivinghost.locadomain btrfs-syncer-client

注意点として、例えばsubvolというサブボリュームに書き戻したい場合、subvolというサブボリューム上にreceiveすると結構困ってしまう。 その場合はそのペアレントボリュームにreceiveした上で(snapshotの名前になってしまうので)mvするのが正しい。

なお、mvしてもreadonlyのままになるため、ロールバックするためには属性変更が必要。

$ sudo btrfs property set -ts subvol ro false

ro trueでreadonly。 IDによるマウントを使っているのであれば、set-default

追加のクリティカルヒット

しかし、またも/dev/sdgの大量エラーという結果になった。一見すると正常だが、ログを見ると1.77TiBの書き込みの間に71456ものblk_update_requestが発行されている。

2度続けてsdgだったため、ケーブル、ラック、ボードのいずれかが問題である可能性も考えられたため、ディスクを入れ替えて(/dev/sdfの位置にあるディスクと入れ替えた)試したところエラーはなく、ディスクの不良と断定。初期不良を申請し、翌日回答、翌々日到着となった(NTT-Xの対応は早い)。

ちなみに、NTT-Xにディスクの初期不良対応を申請したところ、

  • メーカーに確認→メーカー回答を得て応答(交換or返金)→交換の回答を得て発送→到着時に集荷として引き渡し

という手順だった。

交換ディスクは

# dd if=/dev/zero of=/dev/sdf bs=512M

として全域書き込みを行い、

# journalctl --no-pager | grep -F blk_ | tail

としてblk_update_requestが発生していないことを確認した。

そして前述の通り、新規ファイルシステム作成、マスターボリュームのマウント、socatと連動したsend/receive、リネーム、プロパティ変更と行い、エラーがないことを確認した。 なぜか全域でデータ量が5.30TiBまで減少していた。

しかしtopで、kworker/3:2及びksoftirqd/3が張り付いたままになっている。 kworkerはおそらくbtrfs絡みだろうし、ksoftirqdが固まっているということはディスク処理だろうから、しばらく待つことにする。特にkworkerはずっとS欄がR(Running)であり、明らかに忙しくしているので安全を考えて待つ。

結局kworderがしずまらなかったので、syncしてunmountし、それが問題なくunmountされたことを確認してからreboot。

エラーが何もないことを確認して、おそらくこれで完了。 とてもとてもとても大変だった。