ZFS SnapshotをGCPのColdline Storageに上げてみた
この記事はCAMPHOR- Advent Calendar 2016第6日目の記事です。
ZFSにはsnapshotを送受信する機能があり、バックアップを取るのに使用できる。安価にオフサイトバックアップを取るべく、Google Compute PlatformのColdline Storageにそのスナップショットを保存してみた。
理想と現実…
ZFSを使用しているシステム間であるsnapshotを送るとき、
# zfs send zroot/dataset/to/backup@snap | ssh remote-host "zfs recv zroot/backup
とする。続けて差分のsnapshotを送るときは
# zfs send -i zroot/dataset/to/backup@snap zroot/dataset/to/backup@snap2 | ssh remote-host "zfs recv zroot/backup"
とすればよい。recv側に指定したdatasetが受信したsnapshotで置き換えられる。
上の2コマンドで送信したsnapshotをローカルに受信して復元するときは
# ssh remote-host "zfs send zroot/backup@snap2" | zfs recv zroot/dataset/to/restore
で済む。これらを自動化するスクリプトは存在するし、簡単に書くこともできる。
しかし、Coldline Storageにはファイル単位でデータをアップロードしなければならないので、snapshotを送る際のパイプの後半と、snapshotから復元するときのパイプの前半はファイルへの読み書きでなければならない。Coldline Storageにファイルに書きだすのは標準出力のリダイレクションで済む。問題は、zfs send
でファイルに書きだしたsnapshotの差分をどうやってzfs recv
で読み込むかだ。以下では
- zroot/dataset/to/backup@snap1
- zroot/dataset/to/backup@snap2
- zroot/dataset/to/backup@snap3
の3つのsnapshotがあるとする。
ダメな方針: snapshotの差分ファイルをすべてcatで結合してrecvする
zfs send zroot/dataset/to/backup@snap1 > snap1
zfs send -i zroot/dataset/to/backup@snap1 zroot/dataset/to/backup@snap2 > snap2
zfs send -i zroot/dataset/to/backup@snap2 zroot/dataset/to/backup@snap3 > snap3
とsnapshotを書き出し、snap1
、snap2
、snap3
の3つのファイルを得たとき、
cat snap1 snap2 snap3 | zfs recv -F zroot/dataset/to/restore
で読み込みを試した。しかし、@snap1
のみが復元され、@snap2
、@snap3
は無視された。これではダメだ…
差分ファイルを作成した順にrecvする
仕方がないのでそれぞれのsnapshotファイルを順にrecvすることに。
cat snap1 | zfs recv zroot/dataset/to/restore
cat snap2 | zfs recv zroot/dataset/to/restore
cat snap3 | zfs recv zroot/dataset/to/restore
これだとちゃんと復元できるが、snapshotの数が増えると全て手でやるのが面倒になる。スクリプトを書いて自動化したい。
というわけで作ってみた
Coldline Storage (に限らずgsutil
で読み書きできるストレージ全般) にsnapshotを保存するzfs2gcp
というスクリプトを作ってみた。なんとPOSIXシェルで動く! ソースはこちら。スクリプトからgsutil
を呼んでいるので、動作にはgsutil
がパスに通っている必要がある。ZFS2GCP_BUCKET
環境変数にCloud Storageのバケットのurlをセットしたうえで次のように使用する。
snapshotを取って送信するとき
# zfs2gcp backup zroot/dataset/to/backup
バックアップするdatasetを指定するだけで、勝手にローカルにsnapshotを作成してこのスクリプトで作成した直前のsnapshotとの差分をColdline Storageに保存してくれる。
バックアップされたsnapshotの一覧を表示するとき
# zfs2gcp list
バケット内のsnapshotファイルの一覧を表示。特定のdatasetに関して表示したいときは
# zfs2gcp list <dataset>
で出力を絞れる。
出力はzroot/dataset/to/backup@zfs2gcp_1480840292
のようにsnapshot名がzfs2gcp_
snapshotから復元するとき
# zfs2gcp restore zroot/backup@zfs2gcp_timestamp zroot/dataset/to/restore
復元したいsnapshotの名前をdataset@zfs2gcp_<timestamp>
の形式で指定すると、指定したsnapshotのファイルとその読み込みに必要なすべてのsnapshotのファイルを、Coldline Storageから落として来て正しい順に復元先にrecvしてくれる。
今後の課題
- エラー処理: ファイルが存在しないなどのエラー時に、綺麗に終了するようにする。
- snapshotファイルの削除
- 既存のsnapshotをアップロードするモードの追加 (現在は新規に専用のsnapshotを作成している)
- ホストごとに一意的なidを振る: 現状では別のホストで同名のdatasetに対して同時刻にバックアップを実行すると、Coldline Storage側でファイル名が衝突する。
zfs2gcp list
の出力には別ホストのsnapshotも含めて一緒くたに表示される。 - 暗号化: 開発されている途中であるZFSでの暗号化が完成すれば、スクリプトで実装する必要性はなくなるかもしれない。
- snapshotファイルのチェックサム: ZFSのrecvは受信時にファイルの誤りを訂正してくれないから、こちらで何らかの手段で整合性を担保しなければならない。
…時間があるときにやろう。