午前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を作成
最も手軽な始め方:
- Datacenter → Replication → Addに移動
- レプリケートするVMを選択(VM IDで選択)
- Targetを選択——クラスター内の宛先ノード
- Scheduleを選択——デフォルトは
*/15(15分ごと)、変更可能 - 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分という条件は十分に許容できるトレードオフだ——特に事前にフェイルオーバーをテストして確実に動作することを確認済みならば。

