CentOS7 の btrfs 領域でチェックサムエラーが出始めたので、ファイルを復旧して暫定対策した話

https://ttandai.info/archives/1894に書いた通り、btrfsではfsckしなくていいよ、ということだが、1か月ほど使ってから btrfs scrub したら、checksum error が /var/log/messages の中にいくつも出てきたので、とりあえず不良ファイルをバックアップから戻したあと、 logwatch の導入と btrfs scrub の定期実行で対策した話。

事態の発覚

btrfsのsnapshotをとり始めてから1か月近くたち、適当に btrfs scrub をしてみたところ、起動ドライブにしていたUSBメディアから、100を超える、修正不能なチェックサムエラーが検出された。

/var/log/messages には下記のようなエラーメッセージがずらずらとでてくる。

 kernel: BTRFS: checksum error at logical 4598022144 on dev /dev/sdb3, sector 11094048, root 343, inode 283, offset 28569600, length 4096, links 1 (path: var/lib/rpm/Packages)
 kernel: BTRFS: checksum error at logical 4598022144 on dev /dev/sdb3, sector 11094048, root 339, inode 283, offset 28569600, length 4096, links 1 (path: var/lib/rpm/Packages)
 kernel: BTRFS: checksum error at logical 4598022144 on dev /dev/sdb3, sector 11094048, root 335, inode 283, offset 28569600, length 4096, links 1 (path: var/lib/rpm/Packages)
 kernel: BTRFS: unable to fixup (regular) error at logical 4598022144 on dev /dev/sdb3
....

全部同じinode番号の、 var/lib/rpm/Packages ファイルが、各スナップショット(rootで示されるObjectIDが異なるものたち)でそれぞれチェックサムエラーを出している。実態は1ファイルなのに、たくさんエラーが見える。(これはうっとおしい)

たとえば、 btrfs inspect-internal subvolid-resolve 335 / などとすると、サブボリュームの名前がわかるので、そこからたどっていくことも、一応は可能。(これは正直パス名の指定がよくわからない。今は、/ に同じデバイスのbtrfsファイルシステムをマウントしているからOKなのかも。)

バックアップからのファイルの復旧

スナップショットは毎日作成していたので、チェックサムエラーがあった場合でもそのままスナップショットが作成されてしまっていた。とりあえずは、本番環境でマウントしているサブボリュームのファイルでチェックサムエラーがあるものをチェックするために、下記コマンドを実行した。(${ObjID//[[:cntrl:]]/}で制御文字を消すところに苦労した、、、。)

 # rootで作業。今は、/ と /home にbtrfsの本番サブボリュームがマウントされている。
btrfs scrab start -B <PATH_TO_DEVICE_FILE>
for i in / /home; do
  ObjID=$(btrfs subvolume show $i | grep "Object ID" | awk -F: '{print $2}')
  dmesg | grep "root${ObjID//[[:cntrl:]]/}" | awk -F"path: " '{print $2}' | sed 's/)$//'
done

これで、本番サブボリュームに含まれる、チェックサムエラーなファイル名がだらだらと表示される。試しに、どれかのファイルをstatしてみると、inode番号もエラーで表示されたものと一致するので、バックアップからの復元を行う。今、別ファイルシステムの /mnt/backup 以下に、週1回rsyncでコピーしてバックアップしているので、ここからコピーしなおすことでOKということにする。

こんな感じ。

for i in / /home; do
  ObjID=$(btrfs subvolume show $i | grep "Object ID" | awk -F: '{print $2}' )
  FILES=$(dmesg | grep "root${ObjID//[[:cntrl:]]/}" | awk -F"path: " '{print $2}' | sed 's/)$//' |sort -u)
  for j in $FILES; do cp --preserve=all /mnt/backup$i/$j $i/$j; done
done

これで、本番サブボリュームの修正はOK。 必要に応じて、過去のスナップショットも削除しておく。(今の環境では、スナップショットの寿命はだいたい1か月くらいなので、ほとんどはそのまま放置した。)

暫定対策

上記のようにして手当てすることはできるので、まぁチェックサムエラーが発覚してからその都度対処することにする。(そろそろ寿命じゃなかろうかという気もするが、メディアを交換するのはマウントできなくなってからにするつもり。)

とりあえずは、

  • btrfs scrubを定期実行
  • 詳細な結果が /var/log/messages に出るので、logwatchでメール送信

ということで対応することにする。(rsyslogでは、onmailモジュールがあり、必要分だけを抽出してメールできそうにも思ったが、手元のCentOS7でやってみてもうまくいかないのであきらめた(yumでそれらしいrsyslogのモジュールも見つけられず、、、))

logwatchの導入

# yum install logwatch 

したあとで、 /etc/logwatch/conf/logwatch.conf にメール送信関係を記載。
今は、まともなメールサーバは入っておらず、 mailx コマンドで送信するのでちょっとした工夫が必要。
(STMPサーバ等の設定は、 ~/.mailrc と /root/.mailrc に記述してあります。https://ttandai.info/archives/1913参照)。

MailTo = to@example.com
MailFrom = from@example.com
mailer = "/usr/bin/mailx -t"

と書いておくことで毎日1回送信される。

btrfs scrub の定期実行

定期実行は、いつもの通り、/etc/cron.weekly 以下にスクリプトを置いて、anacronで週1回実行することにする。
スクリプトはこんな感じ。

#!/bin/bash -e
for i in <PATH_TO_DEVICE_FILE>; do
  btrfs scrub start -B $i
done

ポイントは、Bオプションでバックグラウンドでの起動を抑止している点。これがないと1度に複数デバイスを対象に btrfs scrub してしまいロードアベレージが上がるので、注意が必要。

これを実行し、万が一異常があった場合は、 /var/log/messages に結果が出力されるので、これを見て対策をとればよいということになる。

所感とか

基本的には、起動ドライブの交換が面倒で、場当たり的な対策で延命を図ろうとしているだけなので、あまりたいしたことをしているわけではない。

本当は、起動ドライブをUSBドライブにしておくなんていうことはやめてもう少し構成を考え直すべき(たとえば、USBドライブはバックアップだけにしておくとか)と思っているのだけれども、なかなかそこまでは面倒。

USBドライブに bad sector ができているような気がするが、 btrfs ではそれを検知してフラグを立てるようなことはしていないのかなぁ、、、。手で badblocks コマンドを叩いたとしても、その結果をファイルシステムに知らせる手段がないような気がする(試してないけど)。そのあたりはbtrfsでうまくやってくれていると思うのだけれど、確証がないので何とも言えない。

コメントを残す