日記ブログ、または雑多なメモ
2010年3月25日

zfsでslogデバイスをぶっ飛ばしたプールを無理矢理import (missing slog device) もしくはuberblockの修理





 ゼェハァゼェハァ……
なんとかslog missingなプールをimportできた。

 とりあえず、メモ。


状況としては、
・logを別デバイスにしたzfsプールを作った
・logデバイスを物理的にぶっとばした(ディスクを外す、フォーマットしてしまう等)
・importできていた頃の記憶を含んだキャッシュファイル(zpool.cache)もない
・以上の結果「device missing」でプールがimportできない

 という状態からの復帰。 (キャッシュファイルがある場合はlogfix、もしくはzpool -Fオプションで回復可能。 かも)
この状況でなぜimportできないかというと、

「プールの基本構成に必要なtop-level のデバイスが足りないので端からimportできません」

 つまりslogデバイス(分離されたlogデバイス)はプールに必要不可欠な「top-level device」扱いされているということ。 しかし、実際はzfsとしてはslogデバイスがなくても問題なく動く仕組みのはず。

「そもそもslogデバイスがtop levelデバイス扱いになるなんて聞いとらんわっ!!」

 などと今さら怒っても仕方がないので調べてみると、kernel内でプールの全デバイス(vdev)のguidの合計値(guid sum)のチェックを行い、必要なデバイスが足りているかどうかの合否を出しているらしいのがわかった。 slogデバイスのguid1個分が足りないのでimportしないわけである。 できないわけではなく、その他暗号関係の都合の絡みで現状「しない」だけらしいので、無理矢理無効化して読み込ませればいいんでないかということで、やってみる。


disabling vdev guid sum in kernel/ vdev guid sumの無効化
 kernelソースツリーの「usr/src/uts/common/fs/zfs/spa.c」のspa_load_implの中の、
   1758     /*
   1759      * If the vdev guid sum doesn't match the uberblock, we have an
   1760      * incomplete configuration.
   1761      */
   1762     if (mosconfig && type != SPA_IMPORT_ASSEMBLE &&
   1763         rvd->vdev_guid_sum != ub->ub_guid_sum)
   1764         return (spa_vdev_err(rvd, VDEV_AUX_BAD_GUID_SUM, ENXIO));


 の部分で「guid sum」をチェックしている模様。
ここをコメントするなり削除するなりして「guid sum」チェックを無効にしてみる。 いじったzfsドライバをビルド・インストールして再起動。 念のためその際ZILは無効にしておく。
 このままだと「guid sum」チェックはすり抜けられるものの、「存在しない存在するはずのデバイス」にアクセスしにいこうとするためか、今度は「corrupted」が返ってくる。 それを抑制するために、rootデバイスのzfsラベル内の「Name/Value Pairs」の中の「vdev_children」の値「2」を「1」に変更してみる(詳細は後述)。

 この時点で、わざとlogをぶっとばした実験用のjunkpoolでは、問題なくimportできた。

 が、しかし。
肝心の本番のraidz2プールの方がヘッダを飛ばしたか何かで(実はkernel改造実験中に一度、作業用zfsルートドライブを読み込み不能->破壊したことがある)、importしようとするとkernel panic。 「zdb -e -uuu #id」をかけるとuberblock情報を表示するはずが何も表示してくれない。 ここでかなーーり悩み凹みつつ、zfsのヘッダブロックについてもう少しつっこんで調べてみた。


ZFS Label
0kB
 L0
   ブランク
  8kB
   ブートヘッダ
  16kB
   Name/Value Pairs
  128kB
   uberblockアレイ(各1kBx128)
256kB
 L1
512kB
 ブートブロック
4MB
…中身

デバイスの尻-512kB
 L2
デバイスの尻-256kB
 L3
デバイスの尻
デバイスにおけるzfsのラベルマップ

 zfsのヘッダブロック、正確には「ラベル」はドライブの先頭部分にL0/L1(512kB)、後部ににL2/L3(512kB)とあって、それぞれ4個同じもの(複製)である。 5ドライブでRAIDしてる場合は山の様に同じラベルがあることになるが、それだけあるうちどれが優先されるのかといえば、まず全体の情報を得るのにL0が読まれるらしい。
 その次に、ラベル内のuberblockが読み込まれないといけないはずなのだが、各ドライブのラベル内で「同じtxgを持った正常なuberblock」が読み込まれることによって次のimport動作に移るっぽい。 uberblockはラベルの128kB以降に1kBのコピーが128個ラウンドロビンで配置され、その中の「一番数字の大きいtxg」を持ったブロックが「active」扱いになる。 その「active扱いになってるuberblock」が死亡しているとimportできなくなる。 らしい。

 実際にラベルL0の中身を見てみると、とりたてて破壊されてる風もないし、となるとuberblockのrootbpの参照先が消えているか、もしくは参照そのものが壊れているかのどっちかと思われるが、前者だとお手上げ、後者ならラウンドロビンの過去のuberblockからなんとか戻せるかもしれない。
 そんなこんなでラベルの中身をhexeditでいじるはめになる。


make backup ZFS Label image / ラベルいじり

 まずオフィシャルの ドキュメントがとても参考になるので目を通す。

 いじる前に各ドライブのL0/L1/L2/L3をddでバックアップ。
L0をちゃんと設定すれば他のラベルはあんまり関係ないっぽいのでL0だけバックアップすればいいと思われるが、何はともあれ念のために全部バックアップしておくのが得策。 ちなみに各ラベルは、他のドライブには使いまわせないようなので(試しに違うドライブのラベルを無理矢理ぶっこんでみたらところ、そのドライブはFaulted/Corruptになった)ちゃんとぞれぞれでバックアップしないといけない。
# dd if=/dev/dsk/c5t0d0p0 bs=1k count=512 of=c5t0d0p0-l0l1.img #先頭部分512kB
# dd if=/dev/dsk/c5t0d0p0 bs=1k count=512 skip=244197888 of=c5t0d0p0-l2l3.img
#後ろ部分512kB
# dd if=/dev/dsk/c5t1d0p0 bs=1k count=512 of=c5t1d0p0-l0l1.img #2個めのドライブ…
# dd if=/dev/dsk/c5t1d0p0 bs=1k count=512 skip=244197888 of=c5t1d0p0-l2l3.img



 後ろ部分のラベルの見つけ方であるが、prtvtocでジオメトリを見て、
# dd if=/dev/dsk/c5t0d0p0 bs=1k skip=240000000 |od -x|grep "b10c 00ba"
 ub magic numberで無理矢理あたりをつけて探した。 512kBを切り出しhexeditで開き、0x4000番地から一つめの「Name/Value Pairs」が始まって、0x20000番地からuberblock、0x44000番地から二つ目の「Name/Value Pairs」、0x60000番地から二つ目のuberblockがあれば正解。 ちなみに今回のドライブは日立の2.5inch 250GBドライブが5台で、ジオメトリもラベルの配置も全部同じであった。

* Dimensions:
*     512 bytes/sector
*      63 sectors/track
*     255 tracks/cylinder
*   16065 sectors/cylinder
*   30401 cylinders
*   30399 accessible cylinders* Unallocated space:
*       First     Sector    Last
*       Sector     Count    Sector
*       16065 488343870 488359934


 prtvtocによるとジオメトリは上のような状態。 実際に後ろのラベルがあったのはbs=512で488395776。 ということはzfsから見るドライブの末端は

488395776+(512*2)=488396800?

ジオメトリよりもはみ出してるんだけど、いまひとつ規則性がわからん。

 

 以上、バックアップしたイメージをさらにコピーして、そのコピーしたイメージで作業を行う。


change vdev_children to '1' in Name/Value Pairs / vdev_childrenの値変更
 なんでこれをいじるかといえば、slogデバイスとrootのプールとを合わせて「top-levelのvdevが2個」ということで「2」になっているのだが、そのslogデバイスはもうない。 ので「1」にすればエラーなく読み込んでくれるんじゃないかという話。
 hexeditでName/Value Pairsの該当部分を単純に「2」から「1」に変更する。

00004000   01 01 00 00  00 00 00 00  00 00 00 01  00 00 00 24  00 00 00 20  00 00 00 07  76 65 72 73  69 6F 6E 00  ...............$... ....version.
00004020   00 00 00 08  00 00 00 01  00 00 00 00  00 00 00 16  00 00 00 20  00 00 00 20  00 00 00 04  6E 61 6D 65  ................... ... ....name
00004040   00 00 00 09  00 00 00 01  00 00 00 04  74 61 6E 6B  00 00 00 24  00 00 00 20  00 00 00 05  73 74 61 74  ............tank...$... ....stat
00004060   65 00 00 00  00 00 00 08  00 00 00 01  00 00 00 00  00 00 00 00  00 00 00 20  00 00 00 20  00 00 00 03  e...................... ... ....
00004080   74 78 67 00  00 00 00 08  00 00 00 01  00 00 00 00  00 16 75 04  00 00 00 28  00 00 00 28  00 00 00 09  txg...............u....(...(....
000040A0   70 6F 6F 6C  5F 67 75 69  64 00 00 00  00 00 00 08  00 00 00 01  63 99 46 17  B4 CF 0A 7A  00 00 00 24  pool_guid...........c.F....z...$
000040C0   00 00 00 20  00 00 00 06  68 6F 73 74  69 64 00 00  00 00 00 08  00 00 00 01  00 00 00 00  00 81 E0 68  ... ....hostid.................h
000040E0   00 00 00 2C  00 00 00 30  00 00 00 08  68 6F 73 74  6E 61 6D 65  00 00 00 09  00 00 00 01  00 00 00 0A  ...,...0....hostname............
00004100   66 69 6C 65  73 65 72 76  65 72 00 00  00 00 00 24  00 00 00 28  00 00 00 08  74 6F 70 5F  67 75 69 64  fileserver.....$...(....top_guid
00004120   00 00 00 08  00 00 00 01  F0 C6 9B 3E  D4 E1 69 37  00 00 00 20  00 00 00 20  00 00 00 04  67 75 69 64  ...........>..i7... ... ....guid
00004140   00 00 00 08  00 00 00 01  DB A7 E9 C1  5C F7 40 42  00 00 00 2C  00 00 00 28  00 00 00 0D  76 64 65 76  ............\.@B...,...(....vdev
00004160   5F 63 68 69  6C 64 72 65  6E 00 00 00  00 00 00 08  00 00 00 01  00 00 00 00  00 00 00 01  00 00 0A 44  _children......................D
00004180   00 00 00 38  00 00 00 09  76 64 65 76  5F 74 72 65  65 00 00 00  00 00 00 13  00 00 00 01  00 00 00 00  ...8....vdev_tree...............
000041A0   00 00 00 01  00 00 00 24  00 00 00 20  00 00 00 04  74 79 70 65  00 00 00 09  00 00 00 01  00 00 00 05  .......$... ....type............
000041C0   72 61 69 64  7A 00 00 00  00 00 00 20  00 00 00 20  00 00 00 02  69 64 00 00  00 00 00 08  00 00 00 01  raidz...... ... ....id..........

Name/Value Pairsの先頭部分。 赤字部分、上がtxg、下がvdev_children


reset active uberblock / active uberblockの変更

 「zdb -e」でuberblockが表示されなかったりimportするとフリーズしたりする場合、「active uberblock」が壊れているとみられる。 ラウンドロビン128kBの中のどのブロックがactiveなのかといえば、txgの値が一番大きいものが「active扱い」になるらしい。 というわけで「txgの一番大きいuberblock」近辺をクリアして、いくつか遡ったブロック(ならば壊れていないだろうという期待/推定で)を残しておけばいいんぢゃね?ということで、該当部分をddなり手動なりで削除してから「Name/Value Pairs」の中のtxgをそれに合わせてみる。
 ちなみに「Name/Value Pairs」の部分では「16 75 04(16進数 0x167504/10進数 1471748)」という表記だが、uberblockの中では「04 75 16」とオーダーが違うので注意。

00021000   0C B1 BA 00  00 00 00 00  16 00 00 00  00 00 00 00  04 75 16 00  00 00 00 00  FC 18 7F 2C  12 E5 B5 19  .................u.........,....
00021020   23 0C 9D 4B  00 00 00 00  03 00 00 00  00 00 00 00  7F 02 00 36  00 00 00 00  03 00 00 00  00 00 00 00  #..K...............6............
00021040   7F 02 00 52  00 00 00 00  03 00 00 00  00 00 00 00  BE 02 00 69  00 00 00 00  03 00 00 00  03 07 0B 80  ...R...............i............
00021060   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  04 75 16 00  00 00 00 00  .........................u......
00021080   77 00 00 00  00 00 00 00  5E 24 31 FF  0B 00 00 00  D3 38 5C 88  C8 04 00 00  C7 96 97 F8  DB F9 00 00  w.......^$1......8\.............
000210A0   48 42 7D FC  45 C9 22 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  HB}.E.".........................
000210C0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................................

uberblock先頭部分。 赤字がtxg。

# dd if=/dev/zero bs=1k seek=128 count=1 conv=notrunc of=c5t0d0p0-l0l1-copy.img #L0の先頭のuberblockを1個消去する場合
# dd if=/dev/zero bs=1k seek=131 count=125 conv=notrunc of=c5t0d0p0-l0l1-copy.img #L0の先頭の3個のuberblockを残してあとは消去する場合
# dd if=/dev/zero bs=1k seek=129 count=127 conv=notrunc of=c5t0d0p0-l0l1-copy.img #L0の先頭以外のuberblockを全部消去する場合

 ddで消去する場合は↑こんな感じ。

 以上の作業を今回はraidz2なので最初の3ドライブ(3ドライブ以上生きてる必要があるので)についてやってみた。 根拠は無し。 zfs的には「best」なconfigを「自動的に採択」するらしいので、それに期待。 それぞれtxgが同じ(active uberblock)になるようにする。


write modified image back to device / いじったラベルを書き戻す
 作業が終わったら、いじったラベルを各ドライブへ書き戻す。

# dd of=/dev/dsk/c5t0d0p0 bs=1k count=512 conv=notrunc if=c5t0d0p0-l0l1-copy.img
#先頭部分512kB
# dd of=/dev/dsk/c5t0d0p0 bs=1k count=512 seek=244197888 conv=notrunc if=c5t0d0p0-l2l3-copy.img
# 後ろ部分512kB(L2/L3も更新したい場合

 その後「zdb -e -uuu #id」でuberblock内容が表示されれば復帰完了。 のはず。
ダメな場合はバックアップを書き戻してオフィシャルの対応を待つか、あきらめる。

# zdb -e -uuu 7176844548823255674

Uberblock:
        magic = 0000000000bab10c
        version = 22
        txg = 1470344
        guid_sum = 9498754025217956703
        timestamp = 1269524163 UTC = Thu Mar 25 22:36:03 2010
        rootbp = DVA[0]=<0:6c00094e00:1200> DVA[1]=<0:a400094e00:1200>
DVA[2]=<0:d200090000:1200> [L0 DMU objset] fletcher4 uncompressed LE
contiguous unique triple size=800L/800P birth=1470344L/1470344P fill=119
cksum=3c12b0e52:6f8bc100d82:67d36ae5f5124:409755d167af002

 txgが1470344以降のuberblockを削除した結果、1470344まで巻き戻って復帰できた模様

# zpool import
  pool: junkpool
    id: 15441945694171481467
 state: ONLINE
action: The pool can be imported using its name or numeric identifier.
config:

        junkpool    ONLINE
          c8t0d0p1  ONLINE

  pool: tank
    id: 7176844548823255674
 state: ONLINE
status: The pool was last accessed by another system.
action: The pool can be imported using its name or numeric identifier and
        the '-f' flag.
   see: http://www.sun.com/msg/ZFS-8000-EY
config:

        tank          ONLINE
          raidz2-0    ONLINE
            c5t0d0p0  ONLINE
            c5t1d0p0  ONLINE
            c5t2d0p0  ONLINE
            c5t3d0p0  ONLINE
            c5t4d0p0  ONLINE
 junkpoolは実験用のUSBスティック。 tankが要救出のプール。 どちらもlogデバイスはなかったことになって見えるが、「zdb -e」で見るとちゃんとログデバイス名が残ってたりする


 以上、復帰完了。
ところで、このようにして無理矢理importしたプールはslogデバイス抜きのguid sumが新たにコンフィグに保存されるのか、改造kernelでなくてもimportできるようになり、かつ普通に今まで通りそのまま使えるようである。 自分はなんだか気持ち悪いのでまたまたデータをバックアップしてプールを作り直した。

 まあ、とりあえず……ファイルサーバを作っておきながら、バックアップデータを自分で消すというおバカな事態をなんとか回避できたらしい。
ものすごくアホなことでプールが読めなくなったzfsだが、中身を見てみると冗長性の鬼というかなんというか。 おかげで復帰できたっちゅうもんではある。 にしても、この「slogデバイスを飛ばすと問答無用で読めなくなる仕様」はなんとかしないと、次々とトラップにかかる人が出てくると思うんだが、どうなんだろう。


おまけ・ラベルの複製
 ラベルの一部が壊れた場合、同じドライブの他のラベルをそのままコピーすればよし。
健康なラベルをddで抜きだし、壊れたラベルに上書きする。 うっかりifとofを逆にしたり、イメージへ書き込むときに「conv=notrunc」を忘れたりしないように注意。

# dd of=label.img bs=1k count=256 if=c5t0d0p0-l0l1-copy.img #L0を抜きだす
# dd of=label.img bs=1k count=256 skip=256 if=c5t0d0p0-l0l1-copy.img #L1を抜きだす

# dd if=label.img bs=1k count=256 conv=notrunc of=c5t0d0p0-l0l1-copy.img #L0に上書き
# dd if=label.img bs=1k count=256 seek=256 conv=notrunc of=c5t0d0p0-l0l1-copy.img #L1に上書き


おまけ2・後部ラベルの位置
 ラベル情報の中の「asize」からデバイス最終端の位置がわかるんでないか?というこちらの記事を見て、計算してみた。

asize = 1250272215040
1250272215040 / 5 = 250054443008(5台分なので)
250054443008 / 512 = 488387584(1セクタ512バイト)
488396800 - 488387584 = 9216 (実際に見つけたデバイス最終端 - 上の計算結果)

記事と同じ数字が出た。
記事の「8192 + (asize/512)」という式をRAIDプールで考えると、

「8192 + (asize/512/台数)」になると思われるので当てはめてみると、

8192 + ( 1250272215040 / 512 / 5 ) = 488395776 = L2開始位置(+(512*2)=488396800 でデバイス最終端)

 になる、と。 たまたま5ドライブのジオメトリが全く同じだから「なるほど」と思うが、そうでない場合どうなるのか。 たぶん、一番小さいジオメトリに合わせて全ドライブ同じボリュームサイズが自動的に構成されるはずなので、やっぱり同じ計算方法でいける……のかねぇ?


コメント欄
(投稿なし)

コメントなどありましたらこちらからどうぞ
名前
内容
 ※名前、内容ともに入力必須です
- C'sGallery Blogっぽく見えるシステム3.2 -
小武 (管理人) eta2@tim.hi-ho.ne.jp