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