ZFSを使ったProxmox VE Replicationの設定:SANなしでHigh AvailabilityとDisaster Recoveryを実現

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

午前2時。スマホが鳴った。Proxmoxクラスターのノード1が死んだ——ディスク障害。12台のVMがそこで動いていて、shared storageなし、SANなし、何もない。

自分もその状況を経験した。本番環境ではなく、幸いにもhomelab——でも全VMが一斉にオフラインになる光景は変わらない。Proxmox VEで12台のVMとコンテナを管理するhomelabを運用しており、productionに投入する前に何でもテストするplaygroundとして使っている。あの夜が、Proxmox VE Replicationを真剣に考え始めたきっかけだった。

SANなしでHAを実現できる理由

Shared Storage(SAN/NFS)は従来のHA手法で、すべてのノードが共通のストレージに読み書きする。あるノードでVMが落ちても、別のノードですぐ再起動できる——ゼロダウンタイム。しかしSANはコストが高く複雑で、予算のないhomelabやSMEにはほぼ現実的ではない。

Proxmox VE ReplicationはZFS send/receiveを使い、設定可能な間隔(デフォルト15分)でノード間のVMディスクを同期する。動作の仕組みは以下の通り:

  • ソースノードでZFSスナップショットを作成
  • 暗号化されたSSHトンネルを通じてインクリメンタルスナップショットを宛先ノードへ送信
  • 宛先ノードは常に最新のVMディスクのコピーを保持(最大でも設定したインターバル分の遅延)
  • ソースノードが停止したとき→宛先ノードですぐにVMを起動、失われるデータは最大1同期サイクル分

これはlive migrationでも、zero-downtime HAでもない。しかしRPO 15分、RTOも時間単位でなく分単位、さらにコストゼロという条件なら、SANが選択肢にならない大半のユースケースで十分現実的な解決策だ。

始める前の要件確認

以下の条件をすべて満たす必要がある。一つでも欠けるとReplicationは機能しない:

  • Proxmox Clusterが構築済み——最低2ノードがクラスターに参加していること
  • 各ノードにZFS Pool——VMディスクはZFSストレージ上に置く必要がある。LVMやdirectoryストレージは不可
  • ノード間のSSH鍵認証——Proxmoxはクラスター作成時に自動設定するため、通常は手動設定不要
  • 十分な内部帯域幅——初回同期はディスク全体をコピーするため、事前に見積もりが必要

作業前にZFS poolとcluster statusを確認:

# 現在のZFS poolを確認
zpool list
zpool status rpool

# クラスターノードを確認
pvecm status
pvecm nodes

VMディスクがZFSを使用しているか確認(例:VM ID 100):

qm config 100 | grep -E "scsi|virtio|ide|sata"
# 正しい出力には先頭に "local-zfs:" が表示される:
# scsi0: local-zfs:vm-100-disk-0,size=32G

# "local-lvm:" や "local:" が表示された場合は、先にディスクをZFSへ移行する必要がある

Replicationのステップバイステップ設定

Web UIでReplication Jobを作成

最も手軽な始め方:

  1. Datacenter → Replication → Addに移動
  2. レプリケートするVMを選択(VM IDで選択)
  3. Targetを選択——クラスター内の宛先ノード
  4. Scheduleを選択——デフォルトは*/15(15分ごと)、変更可能
  5. Createをクリック

作成後、Proxmoxはすぐに同期を開始する。初回はディスク全体を送信するため時間がかかるが、その後はインクリメンタルな差分のみを送るため大幅に速くなる。

CLIで複数VMを一括設定

一括設定やスクリプト化が必要な場合はpvesrコマンドを使う:

# Replication jobを追加:VM 100、宛先pve-node2、15分ごと
pvesr create 100-0 --vmid 100 --target pve-node2 --schedule "*/15"

# 3番目のノードへのReplication(同じVMの2番目のjob)
pvesr create 100-1 --vmid 100 --target pve-node3 --schedule "*/30"

# 現在のjob一覧を確認
pvesr list

# スケジュールを待たずに即座に同期を実行
pvesr run 100-0

job IDのフォーマットは<vmid>-<number>。1台のVMに対して複数のノードへのreplication jobを設定できる——ノード2とノード3の両方にレプリケートしたい場合に便利だ。

Replicationの帯域幅制限

この手順は重要なのに見落とされがちだ。制限しないと、初回同期(フルコピー)が内部ネットワークの帯域を占有し、稼働中のproduction VMに直接影響する。

# ストレージの帯域幅制限(Web UI:Datacenter → Storage → local-zfs → Edit)
# または /etc/pve/storage.cfg を直接編集:
# bwlimit: 102400   # KB/s、つまり100 MB/s

# 別の方法:tcでネットワークインターフェース単位で制限
tc qdisc add dev eth0 root tbf rate 200mbit burst 32kbit latency 400ms

Replicationの確認と監視

リアルタイムステータス確認

# 現在のノードの全jobのステータスを確認
pvesr status

# 出力例:
# VMID  STATE     SOURCE     TARGET     DURATION  FAIL
# 100   ok        pve-node1  pve-node2  0:00:23   0
# 101   ok        pve-node1  pve-node2  0:01:45   0
# 102   syncing   pve-node1  pve-node2  -         0

# 特定のjobの詳細ログを確認
journalctl -u pvesr@100-0 --since "2 hours ago" -f

宛先ノードのZFSスナップショットを確認

これがデータが本当に宛先ノードに届いているかを確認する方法だ——UIだけを信用してはいけない:

# 宛先ノードにSSH接続
ssh root@pve-node2

# レプリケートされたVMのスナップショットを確認
zfs list -t snapshot | grep vm-100

# 期待する出力——複数のインクリメンタルスナップショットが表示される:
# rpool/data/vm-100-disk-0@__replicate_100-0__1700000000  1.2G
# rpool/data/vm-100-disk-0@__replicate_100-0__1700000900  45M
# rpool/data/vm-100-disk-0@__replicate_100-0__1700001800  38M

実際のフェイルオーバーテスト——最重要ステップ

フェイルオーバーをテストしていないReplicationは、ないも同然だ。あの午前2時の体験から得た最も痛い教訓がこれだ——バックアップは完璧に設定していたのに、一度もリストアを試したことがなく、いざというとき設定ミスが発覚した。

フェイルオーバーのシミュレーション:VMを宛先ノードへマイグレート、事前にレプリケートしたZFSデータセットを使用:

# 方法1:Web UIから
# VMを選択 → More → Migrate → ノードを選択 → "Allow Replication Overwrite"にチェック

# 方法2:CLI——VM 100をpve-node2へマイグレート、既存のローカルディスクを使用
qm migrate 100 pve-node2 --with-local-disks 1

# VMは宛先ノードの最新ZFSスナップショットを使用するため、
# 最初からコピーし直す必要がなく、マイグレーションは数秒で完了する

node2でVMが正常に起動したら、元のノードに戻すため逆方向にマイグレートする:

qm migrate 100 pve-node1 --with-local-disks 1

自動監視スクリプト

定期的に確認してjob失敗時にアラートを送るスクリプトを作成:

#!/bin/bash
# /usr/local/bin/check-replication.sh

FAILED=$(pvesr status 2>/dev/null | awk 'NR>1 && $5 > 0 {print $1, "fail_count:", $5}')

if [ -n "$FAILED" ]; then
  MSG="[REPLICATION ALERT] $(hostname): $FAILED"
  # Telegramアラートを送信——YOUR_BOT_TOKENとCHAT_IDを置き換えること
  curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
    -d "chat_id=${CHAT_ID}&text=${MSG}" > /dev/null
  exit 1
fi

echo "$(date): All replication jobs OK"
exit 0
chmod +x /usr/local/bin/check-replication.sh

# 30分ごとに実行
echo "*/30 * * * * root /usr/local/bin/check-replication.sh >> /var/log/pve-replication.log 2>&1" \
  > /etc/cron.d/proxmox-replication

よくあるエラーの対処法

エラー:”Could not connect to host”

# ソースノードから宛先ノードへのSSH接続を確認
ssh root@pve-node2 "echo OK"

# 失敗した場合、クラスターの証明書とSSH鍵を更新
pvecm updatecerts

# クラスターのauthorized keysを確認
cat /etc/pve/priv/authorized_keys

エラー:”target storage does not exist”

ノード間でZFS poolの名前またはストレージIDが異なる場合に発生する。宛先ノードのストレージを確認:

pvesh get /nodes/pve-node2/storage
# storage nameがreplication jobの設定と一致していることを確認

“syncing”状態でjobがスタックした場合

# ロックを解除して再実行
pvesr finalize 100-0 --lock-timeout 1
pvesr run 100-0

# まだエラーが続く場合は詳細ログを確認
journalctl -u pvesr@100-0 -n 50

このreplication設定を入れてから、shared storageなしのベアメタルProxmox上でproductionワークロードを動かすことへの自信が格段に増した。vSphere HAやshared storageありのProxmox HAのような完全なHAではないが、ライセンスとハードウェアのコストがゼロで、RPO/RTO 15分という条件は十分に許容できるトレードオフだ——特に事前にフェイルオーバーをテストして確実に動作することを確認済みならば。

Share: