FreeBSD7.1になったのが良かったのか、amd64+メモリ増量が効いたのか、以前再起動を繰り返していたうちのZFSもすこぶる安定して動作しております。ということで、UFSの時は dump で作っていたバックアップも ZFSなり の流儀で再構築です。
ZFSのバックアップはとても簡単です。ある時点でのファイルシステムを後から参照できるように置いておくには
# zfs snapshot -r tank@`date '+%Y%m%d-%H%M'`
でOK。たった一行です(笑)。たとえばこれを cron で一時間ごとに回せば MacOS のタイムマシーンのごとく過去を振り返ることができます。もっとも、バックアップは同一ディスク上に作られるわけですからバックアップにはならないという意見もあるでしょうが、
# rm -r /とか
% rm *>o
のような不測の事態(笑)には絶大な効果を発揮するはずです。
作成したバックアップ(スナップショット)は ファイルシステムがマウントされているディレクトリ下の .zfs/snapshot/スナップショット名 にて参照できます。たとえば "/usr/src/.zfs/snapshot/090114-1130" という感じ。FreeBSD-STABLEで csup したのは良いけれどいったいどこが変わったのだろう? という時には
diff -ur /usr/src/.zfs/snapshot/090114-1130 /usr/src/ | less
てな感じで変更箇所を見ることができます。いやはや便利ですね。ただ、こんな感じで参照するとスナップショットがマウントされてしまうので、dfやmountで表示されてしまいます。tcshなんかで ls /usr/src/.zfs/snapshot と打ったあと一覧を見るために ^d を押したりすると、すべてのスナップショットがマウントされてしまうのでもう大変(^_^)。
何か良い方法があるのかもしれませんが、よくわかんないのでとりあえず下のようなシェルスクリプトでスナップショットを umount しています。
#!/bin/sh
# スナップショットを umount する
snapshot=`mount -t zfs | grep "@" | awk '{print $3}'`
for i in $snapshot
do
umount -v $i
done
で、スナップショットを1時間ごとに作っていると数が際限なく増えていくので、適当に消すスクリプト。当日のスナップショット以外について、1日に1個だけいちばん古いものを残してあとは消します。当日の判断は単純に日付の桁しか見ていないので、動かす時刻は23時ぐらいでないと期待通りに動作しません(笑)。
#!/bin/sh
# スナップショットを1日1個にする
DATE=`date '+%Y%m%d-'`
FS=`zfs list -t filesystem,volume -o name -H -r tank`
for i in $FS
do
list=`zfs list -t snapshot -Ho name | egrep "$i@[0-9]{8}-[0-9]{4}" | grep -v "$i@$DATE"`
prev="hoge"
for j in $list
do
if [ "$prev" != "${j%-*}" ]; then
echo "remain $j"
prev="${j%-*}"
else
echo "delete $j"
zfs destroy $j
fi
done
done
で、最後にこっちは本当のバックアップ。ZFSで同名のプールを持ったホスト(rootでパスフレーズ無しでsshログインできる必要がある)にスナップショットを全部 zfs send~receive します。ちょっと長いので別ファイルで。実際には前後に Wake On Lan とリモートシャットダウンの処理が入っています。
リモートホストに zfs send でバックアップ
→rmtbackup.sh
UFSでは軽く1時間はかかっていたフルダンプですが、ZFSの差分バックアップなら途中のスナップショットが多くても数分で終ります。しかもスナップショットにより極めて参照が容易な世代バックアップが実現できます。(ハノイの塔アルゴリズムでdumpされたデータから特定日時のファイルを取り出すなんて考えただけでぞっとします ^_^)
ZFSは今のところ試験段階の実装なのでFreeBSDのインストーラでは扱えないなど導入に関しては敷居が高いのですが、いざ使い始めるとホントに運用は楽ちんです。ちゃんと動いている限り UFS にはもう戻れないです。
(おまけ)上のシェルスクリプトを作成中に悩んだことなのですが、「二つのファイルの共通行を抽出する」という処理は ruby だと
% ruby -e "print File.readlines('fileA') & File.readlines('FileB')"
てな感じで簡単にできちゃうのですな。共通行の抽出だと comm(1) で出来そうなのですが、この場合は辞書順でソートされている必要があります。上の処理は辞書順ではなくスナップショットの作成順に並んでいる必要があるのでこの方法は使えませんでした。rubyはこれまで使ったことがなかったのですが、上のようなエレガントな記述ができるのは魅力的です。タイムマシーンのように古いスナップショットを残容量にあわせて消していく処理はシェルスクリプトだけでは無理(結局 awk なり grep なりを何度も呼び出さなくちゃいけない)なので、ぜんぶ ruby で書いてしまえばすっきりするかもですね。その前に ruby についてお勉強しなくちゃいけませんが(^_^)。