課題:午前2時14分のストレージ障害ポストモーテム
午前2時14分、アラート通知が鳴り響きました。メインのハイパーバイザー上で自動更新スクリプトが暴走し、データセットのパーミッションを破壊した挙句、VaultwardenとHome Assistantのバックエンドである4GBのSQLiteデータベースを破損させてしまったのです。端末画面を見つめる私に残された選択肢は2つでした。昨日のrsyncダンプから4時間もかけて苦痛を伴いながらファイルを復元するか、14分前に取得されたクリーンなブロックレベルのスナップショットへロールバックするかです。
私はスナップショットを選択しました。30秒後にはすべてのサービスが正常に復帰していました。
いまだに多くのHomeLabユーザーが、cron内でzfs snapshotを実行するだけの脆弱なシェルスクリプトに頼っています。このような手作りの構成は、決まって同じような問題を引き起こします。プールのメタデータを圧迫する数千もの孤立したスナップショットが放置され、データセットをリネームすると何も通知されずにサイレントに失敗します。さらに最悪なのは、きめ細かな保持階層(時間単位、日単位、週単位、月単位)を適用するのが困難な点です。
それらのスナップショットをrsyncでレプリケーションするのも良い方法とは言えません。rsyncはディレクトリツリー全体を巡回し、VFS層を介してファイルをバイト単位で読み込みます。これによってディスクI/Oが激しく消費され、500GBもの仮想ディスクイメージで処理が停滞し、データベースのオープンロックファイルで失敗します。必要なのは、アトミックなブロックレベルの差分同期です。
基本概念:なぜSanoidとSyncoidなのか?
ZFSは、Copy-on-Write(CoW)スナップショットとzfs sendおよびzfs receiveによるシリアライズされたデータストリームをネイティブで処理できます。しかし、スナップショットのスケジューリングや古いデータのプルーニング(削除)、ネットワーク転送を統合管理するオーケストレーターが欠けています。そこで登場するのが、Jim Salter氏が開発したSanoidスイートです。
Sanoid:ポリシー駆動型のスナップショットライフサイクル
Sanoidは、自作のbashスクリプトを必要とせずに作成と世代保持を自動化します。単一の設定ファイル(/etc/sanoid/sanoid.conf)で保持ポリシーを定義し、systemdタイマーによってSanoidを毎分または毎時トリガーします。Sanoidはタイムスタンプ付きのスナップショット(例: autosnap_2026-10-06_02:00:00_hourly)を作成し、設定した保持期間(例えば毎時24世代、日次7世代、週次4世代、月次3世代など)に従って期限切れのスナップショットを自動的にパージします。
Syncoid:堅牢なストリームレプリケーション
Syncoidはネットワーク転送を担当します。SSH経由でzfs sendとzfs receiveをインテリジェントな検出ロジックでラップして実行します。
リモートホストを指定すると、Syncoidは送信元と送信先のデータセット間で最後に共有された共通のスナップショットを特定します。そして差分を計算し、(LZ4またはzstdで)圧縮した上で、mbufferでストリームをバッファリングしてネットワークのスパイクを吸収しながら宛先プールに適用します。不安定な1 Gbps Wi-FiやWAN回線越しであっても、Syncoidはサービスを停止させることなく、変更された50 GBのデータベースブロックをスムーズに転送できます。
実践:自動化パイプラインの構築
1. 依存パッケージのインストール
プライマリノード(送信元)とバックアップストレージサーバー(送信先)の両方で以下のコマンドを実行します。Debian、Ubuntu、Proxmox VEホストの場合:
sudo apt update
sudo apt install -y sanoid lzop mbuffer pv openssh-client openssh-server
2. Sanoidスナップショットポリシーの設定
プライマリサーバーで、設定ディレクトリと設定ファイル/etc/sanoid/sanoid.confを作成します:
sudo mkdir -p /etc/sanoid
sudo nano /etc/sanoid/sanoid.conf
スナップショットの保持プロファイルを定義し、データセットに割り当てます:
# 重要な本番データ用テンプレート
[template_production]
frequently = 0
hourly = 24
daily = 7
weekly = 4
monthly = 3
yearly = 0
autosnap = yes
autoprune = yes
# 短期間の一時ストレージ用テンプレート
[template_ephemeral]
frequently = 0
hourly = 6
daily = 1
weekly = 0
monthly = 0
yearly = 0
autosnap = yes
autoprune = yes
# データセットへの割り当て
[tank/appdata]
use_template = template_production
recursive = yes
[tank/vms]
use_template = template_production
recursive = yes
[tank/scratch]
use_template = template_ephemeral
recursive = no
3. Systemd経由でのSanoidのテストと自動化
自動削除(プルーニング)を有効にする前に、必ずドライランで設定を検証してください:
# 設定ファイルの構文を確認し、実行予定のアクションをドライランで検証
sudo sanoid --cron --debug --dry-run
# ネイティブのsystemdタイマーを有効化して即座に起動
sudo systemctl enable --now sanoid.timer
# タイマーが有効化されスケジュールされているか確認
sudo systemctl list-timers | grep sanoid
4. Syncoid専用のSSH認証の設定
Syncoidがスナップショットをストリーミング転送するにはSSHアクセスが必要です。プライマリノード上でパスフレーズなしの専用Ed25519キーペアを生成します:
# プライマリホストでSSHキーペアを生成
sudo ssh-keygen -t ed25519 -f /root/.ssh/id_syncoid -N "" -C "syncoid-backup"
# バックアップ対象(例: 192.168.10.50)に公開鍵をコピー
sudo ssh-copy-id -i /root/.ssh/id_syncoid.pub [email protected]
# 公開鍵認証が正しく動作することを確認
sudo ssh -i /root/.ssh/id_syncoid [email protected] "zfs list"
5. 初回同期の実行
まずは手動でベースラインとなるレプリケーションを実行します。Syncoidは宛先データセットを自動的に作成し、初回データセット全体をストリーミング転送します:
sudo syncoid --sshkey=/root/.ssh/id_syncoid \
--no-sync-snap \
--compress=lz4 \
--recursive \
tank/appdata [email protected]:backup-pool/appdata
--no-sync-snapフラグを指定することで、一時的な同期用スナップショットを新規生成する代わりに、Sanoidによって作成済みの自動スナップショットを再利用するようSyncoidに指示します。
6. Syncoidレプリケーションの自動化
Sanoidの毎時スナップショットの実行完了直後にレプリケーションが開始されるようスケジュールを設定します。プライマリサーバー上で/etc/cron.d/syncoid-replicationを作成します:
sudo nano /etc/cron.d/syncoid-replication
cronの定義を追加します(毎時15分に実行するようオフセットを設定):
# 毎時15分にtank/appdataをバックアップノードへレプリケーション
15 * * * * root /usr/sbin/syncoid --sshkey=/root/.ssh/id_syncoid --quiet --no-sync-snap --compress=lz4 --recursive tank/appdata [email protected]:backup-pool/appdata >> /var/log/syncoid.log 2>&1
7. リカバリ訓練:データの復元
バックアップの信頼性は、直近のリストアテストの成功によってのみ証明されます。障害発生時に復旧するための2つの方法を以下に示します:
# 選択肢A: 即時のローカルロールバック(所要時間2秒未満)
sudo zfs rollback tank/appdata@autosnap_2026-10-06_02:00:00_hourly
# 選択肢B: ローカルプール喪失時にバックアップ先からデータをリストア
sudo syncoid --sshkey=/root/.ssh/id_syncoid \
[email protected]:backup-pool/appdata tank/appdata_recovered
まとめ:ストレージ障害を日常的な復旧作業に変える
ストレージ障害や更新スクリプトの不具合は避けられないものです。Sanoidによるスケジュールされた世代保持ポリシーと、Syncoidによる高速な差分レプリケーションを組み合わせることで、人為的ミスを排除できます。ローカル環境ではきめ細かなロールバックによってデータセットを保護し、オフサイトへは帯域効率の良いブロックストリームでバックアップすることで、HomeLab環境に本番レベルの真の耐障害性をもたらすことができます。

