彷徨えるフジワラ

年がら年中さまよってます

仮想マシン上の ZFS 領域の復旧

要約すると『仮想化ゲスト側が ZFS 運用だと、ホスト側由来の問題があっても、復旧が楽チンだよ?』という話。

先日、音楽再生中の iTunes が、突然うんともすんとも言わなくなった。

まぁ、Windows 上の iTunes は時々アレだからなぁ、と思っていたら、どうやら仮想マシン上の OpenIndiana (旧 OpenSolaris) が停止しているっぽい。

現在、仮想マシン上で稼動する OI の ZFS 領域を CIFS 公開したものを、iTunes のリッピングデータ格納先にしているので、『OI 停止 ⇒ iTunes の再生が停止』は至極自然な流れ。

再起動して、念のために zpool scrub POOL名 で整合性チェックを走らせて見ると……うひぃー!データ不整合が検出されてる!

丁度 iTunes の再生がアレになった曲名とファイル名が一致するので、iTunes 停止の原因は、このファイルのデータ不整合に間違いないだろう。

故障した HDD の差し替えはコレまでに散々やってきたのだけれど、バックアップからの復旧は今回が初めてなので、手順を確認しつつ慎重に作業をすることに。

"zpool scrub" により、どの時点のスナップショットからデータ不整合があるかは判明しているので、手順的には:

  1. 不整合のあるスナップショット以後のスナップショットを全部破棄してから、
  2. バックアップ先から "zfs send" したデータをリストア先で "zfs receive"

という按配になる筈。
ファイルシステム FILESYSTEM において、特定のスナップショット SNAPSHOT より後に作成された全てのスナップショットを破棄するには、"zfs rollback" を使用する。

# pfexec zfs rollback -r SNAPSHOT@FILESYSTEM

これにより、FILESYSTEM 上で SNAPSHOT より後に作成されたスナップショットが全て破棄される。

破棄対象が複数に渡る場合は "-r" 指定が無いと処理が中断されるので注意が必要。まぁ、安全サイドに振ってくれているのは良いことだ。

バックアップ保存先の状況を以下のように仮定すると:

  • ホスト名: host
  • ssh ログイン名: user
  • バックアップ先ファイルシステム名: pool/path/to/filesystem
  • 最新のスナップショット: RECENT

復旧作業は:

# ssh user@host pfexec \
      zfs send -I SNAPSHOT pool/path/to/filesystem@RECENT | \
  pfexec zfs receive -F FILESYSTEM

今回は、実効転送レート 30Mbps 程度の 802.11n 上での ssh 経由による復旧データ転送だったので、実際の復旧速度は 4MB/sec 程度。

GB クラスのスナップショットだと hour 単位の時間が必要になってしまいはするものの、tar アーカイブとか rsync とかによる復旧を考えたら、ファイル名の問題とか、ファイルの改名/破棄とか、その他諸々を全然気にしなく良いので、恐ろしく楽チンな作業。っつーか基本的には終了するのを待っていれば良いだけ。

上記の復旧作業コマンド例での "zfs send" の引数指定には、"-I" を使用したが、本当のところ、最初のスナップショットの復旧は "-i" を使用して実施&挙動確認している。

例えば、ファイルシステム fs 上に、a, b, c, d というスナップショットがある場合、"-I a filesystem@d" 指定は、"-i a fs@b", "-i b fs@c", "-i c fs@d" の順序実施と等価になる。

初回分のスナップショットの復旧に成功したので、残りのスナップショットはまとめて復旧するべく、"zfs send" に "-I" 指定を使って実行すること暫し………あれ?仮想マシンが異常終了してしまった?

なんだろう?負荷が掛かると駄目なのかなぁ?などと思いつつ、とりあえず仮想マシンを再起動。

"-I" 指定でまとめて復旧した分はあてにならない可能性があるので、一括復旧分まで "zfs rollback" してから再度復旧を開始……やっぱり仮想マシンが異常終了する!

こうなると、流石に仮想マシンの再起動でお茶を濁すわけにも行かないので、本格的に調査することに。

仮想マシンの異常終了時に表示されるダイアログのメッセージは以下のような感じ。

The I/O cache encountered an error while updating data in medium "ahci-0-2" (rc=VERR_IO_CRC). Make sure there is enough free space on the disk and that the disk is working properly. Operation can be resumed afterwards

で、仮想マシンのログを漁って見ると、以下のようなエラーが大量にある。

I/O cache: Error while writing entry at offset 160321162240 (131072 bytes) to medium "ahci-0-2" (rc=VERR_IO_CRC)

VERR_IO_CRC で検索してみると、(ログメッセージから想像は付いていたけど)どうやらホスト側で、仮想 HDD ファイルへの書き出しの失敗を意味するらしい。

他の領域は問題なくアクセスできるので、状況から言えば HDD のセクタ不良なんだろうなぁ。

ZFS 側で検出したデータ不整合は、セクタ不良により仮想 HDD 領域からデータが読み出せなくなったことに起因するのだろう。

当初、仮想 HDD の読み書きエラーは、単に『ハード故障相当』と見なして放置してくれても良いのでは?とも思ったのだが、後になって異常に気付いた時には、仮想 HDD データの移行が全然できなくなってしまった!なんていう事態を極力回避するには、やっぱり仮想化ホスト側でも早期エラー検出の仕組みが欲しいんだろうなぁ。

"zfs scrub" 実施の際に、ゲスト側でだけエラーが検出されていたので:

  • 仮想 HDD からの読み出し時エラーは、『ハード故障相当』とみなしてそのままゲストに通知
  • 仮想 HDD への書き込み時エラー(チェックサム検証付き?)は、ホスト側でエラー通知 ⇒ 障害時運用フローのトリガーへ

という感じなのではないかと推測 > VirtualBox 実装

さて、HDD が駄目になったということならば、新しい HDD に差し替えるしか無い。駄目になった HDD は丸々 OI 仮想マシン用に使用していたものなので、そのまま新しい HDD に入れ替えれば良かろう、と思ったのだけれど、いざ HDD を入れ替えるとなると、意外と面倒なことに気付く。

例えば、単純に物理 HDD を入れ替える場合、仮想マシン上で以下の作業が必要になる。

  1. 既存物理 HDD 上に構築した既存仮想 HDD を切り離す
  2. 新規物理 HDD 上に構築した新規仮想 HDD を追加
  3. 新規仮想 HDD 上に ZFS プールを作成
  4. CIFS 公開とかの属性設定を再実施
  5. ZFS プールにバックアップデータを復旧

かと言って、既存物理 HDD から新規物理 HDD に、既存仮想 HDD データをコピーしようとしても、そもそも既存物理 HDD は読み書きできない領域があるので、エクスプローラからのコピーは上手く行く筈が無い。実際、駄目元で試してみたけど、やっぱり駄目だった… orz

やっぱり前者の面倒な方法しかないのか、と絶望しかけた瞬間、はたとひらめいた。

  1. 新規物理 HDD 上に構築した新規仮想 HDD を仮想マシンに追加
  2. 新規仮想 HDD を "zpool attach" でミラーリング対象に追加
  3. ミラーリングの同期が完了したら、"zpool detach" で既存仮想 HDD をZFS プールから切り離す
  4. 仮想マシンを一旦停止させ、既存仮想 HDD を仮想マシンから切り離す
  5. 仮想メディアマネージャ(『ファイル』⇒『仮想メディアマネージャ』)上で仮想 HDD の破棄
  6. PC から既存物理 HDD を切り離す

仮想マシン側では "zpool scrub" が通る状況なので、ミラーリングの同期の際に、既存物理 HDD の不良領域を触ることは無い ⇒ 不良領域を除いた必要な分だけが、新規仮想 HDD 側に転送される筈。

そして予想通り、無事に複製&不良 HDD の切り離しに成功!

仮想マシン経由とは言え、ローカルでの HDD ⇒ HDD 転送だから、WiFi + ssh 経由でのスナップショット転送による復旧 (およそ 4MB/sec) よりは断然早い (およそ 50MB/sec) し、こっちの方法を選択して正解。

後は、当初の予定通り、バックアップ先から残りのスナップショット分を復旧させて、長い道のりも何とか終着!

ゲスト側を普通のファイルシステムで構築していたら、ホスト側でもゲスト側でもにっちもさっちも行かなくなっていたに違いない

これが物理環境での問題だった場合でも、既存 HDD から騙し騙し dd コマンドでコピーしようとすると、仮想 HDD の全領域にアクセスが発生 ⇒ 不良領域にアクセス ⇒ 玉砕となってしまう: noerror + sync を指定すればとりあえずエラー無視+0パディングしてくれるのかな? > dd コマンド

その一方で、健全なデータ(= 正常に HDD アクセス可能な領域)のみを選択的に回収可能な ZFS ってすげー便利なんだなぁ、と再確認。

ちなみに、VERR_IO_CRC エラー発生時にダイアログで表示されるメッセージ中に:

Make sure there is enough free space on the disk and that the disk is working properly

とあるのだけれど、これは:

  • ホスト側ディスク領域に余裕があれば、ちゃんと動くようにしてあげられるよ
  • ゲスト側でディスク消費を減らせば、(不良セクタを踏まなくなるかもしれないので)ちゃんと動くようになるよ

どちらの意味なんだろう?

今回、駄目になった既存物理 HDD は、仮想 HDD で目一杯容量を使い切っていた+固定サイズの事前作成形式だったで、どちらのことなのか、判断材料が無かったんだよなぁ。