Syncronisation von virtuellen Maschinen zwischen 2 Proxmox Clustern via pve-zsync
Installation pve-zsync
apt-get install pve-zsync
Syncronisation
source = IP des PVE Node + die ID der VM
pve-zsync create --source 192.168.1.1:100 --dest zfsstoragepoolname --verbose --maxsnap 2 --name test1
VM Config Datei kopieren und anpassen
cp /var/lib/pve-zsync/<VMID>.conf.rep_<JOB_NAME><VMID>_<TIMESTAMP> /etc/pve/qemu-server/<VMID>.conf
in der Config muss noch der Name des Storage korrigiert werden.
Sync Job löschen
pve-zsync destroy --source 192.168.1.1:100 --name test1
Gesamt-Script
#!/bin/bash
# Script zur Synchronisation von VMs über ZFS Snapshots und Entfernen alter Snapshots.
# ACHTUNG: Der Ziel-Pool wird komplett geleert! Dieser darf nur für die Synchronisation genutzt werden.
set -euo pipefail # Strict error handling
# --- Einstellungen ---
# Primary Proxmox Node zur Stammdatenabfrage
primary_pve_node="pve01"
# IP zu Hostname Zuordnung des Source Proxmox Clusters
declare -A IP_TO_HOST=(
["pve01"]="10.2.1.1"
["pve02"]="10.2.1.2"
["pve03"]="10.2.1.3"
)
# Einstellungen für den Sync
# Der Pool, dessen VMs synchronisiert werden sollen
pool_name_to_sync="Test-Vms"
# Name des Sync Jobs (wird auch beim ZFS Snapshot verwendet)
sync_job_name="RemoteZFSSync"
source_storage_name="Storage01"
# Achtung dieser Storage wird immer komplett geleert!
destination_storage_name="Storage"
# Achtung alle VMs in diesem Pool werden gelöscht!
destination_pool_name="RemoteSyncedVMs"
lookup_ip_for_hostname() {
local ip="$1"
# Prüfen, ob IP angegeben ist
if [[ -z "$ip" ]]; then
echo "Fehler: keine IP-Adresse angegeben!" >&2
return 1
fi
# Lookup durchführen
local hostname="${IP_TO_HOST[$ip]}"
# Ausgabe oder Fehlermeldung
if [[ -n "$hostname" ]]; then
echo "$hostname"
else
echo "Unbekannte IP-Adresse: $ip" >&2
return 2
fi
}
remove_all_disks_from_storage() {
local search_term="$1"
# --- Prüfung der Eingabe ---
if [[ -z "$search_term" ]]; then
echo "Verwendung: $0 <teil-des-snapshot-namens>"
echo "Beispiel: $0 autobackup"
exit 1
fi
# --- Snapshots finden ---
local snapshots
snapshots=$(zfs list -t snapshot -o name -H | grep -i "$search_term")
if [[ -z "$snapshots" ]]; then
echo "Keine Snapshots gefunden, die '${search_term}' enthalten."
exit 0
fi
# --- Snapshots löschen ---
while IFS= read -r snap; do
zfs destroy -r "$snap"
done <<< "$snapshots"
}
remove_synced_vms() {
local pool="$1"
if [[ -z "$pool" ]]; then
echo "Fehler: Kein Poolname angegeben."
echo "Verwendung: delete_vms_in_pool <poolname>"
return 1
fi
echo "Hole VM-IDs aus Pool: $pool"
local vms
vms=$(pvesh get /pools/$pool --output-format json | jq -r '.members[].vmid' 2>/dev/null)
if [[ -z "$vms" ]]; then
echo "Keine VMs im Pool '$pool' gefunden oder Pool existiert nicht."
return 0
fi
echo "Stoppe alle VMs/LXCs im Pool '$pool'..."
for vmid in $vms; do
if qm status "$vmid" &>/dev/null; then
echo "Stoppe VM $vmid..."
qm stop "$vmid" --skiplock --timeout 30 2>/dev/null
fi
done
echo "Warte 5 Sekunden, um sicherzustellen, dass alle Maschinen gestoppt sind..."
sleep 5
echo "Lösche folgende VMs/LXCs aus Pool '$pool': $vms"
for vmid in $vms; do
if qm config "$vmid" &>/dev/null; then
echo "Lösche VM $vmid..."
qm destroy "$vmid" --purge --skiplock 2>/dev/null
fi
done
echo "Alle VMs aus Pool '$pool' wurden gestoppt und gelöscht."
echo "Lösche alle Disks im Ziel Storage: $destination_storage_name"
remove_all_disks_from_storage "$destination_storage_name"
}
rename_storage_names_in_config() {
if [[ $# -ne 3 ]]; then
echo "Verwendung: $0 <datei> <alter_storage_name> <neuer_storage_name>"
echo "Beispiel: $0 vm-101.conf Storage01 Storage"
exit 1
fi
local datei="$1"
local alt="$2"
local neu="$3"
if [[ ! -f "$datei" ]]; then
echo "Fehler: Datei '$datei' nicht gefunden."
exit 1
fi
cp "$datei" "${datei}.bak"
sed -i "s/\b${alt}\b:/${neu}:/g" "$datei"
}
get_latest_replica_file() {
local dir="$1"
local vmid="$2"
if [[ -z "$dir" || -z "$vmid" ]]; then
echo "Verwendung: get_latest_replica_file <verzeichnis> <vmid>"
return 1
fi
# Dateien suchen, die mit "<vmid>.conf.qemu.rep_RemoteZFSSync_" beginnen
local pattern="${vmid}.conf.qemu.rep_RemoteZFSSync_"
local latest_file
latest_file=$(find "$dir" -type f -name "${pattern}*" -printf '%T@ %p\n' 2>/dev/null \
| sort -nr | head -n 1 | awk '{print $2}')
if [[ -n "$latest_file" ]]; then
echo "$latest_file"
cp "$latest_file" "/etc/pve/qemu-server/9${vmid}.conf"
rename_storage_names_in_config "/etc/pve/qemu-server/9${vmid}.conf" "Storage01" "Storage"
# VM zu einem Pool Hinzufügen
pvesh set /pools/$destination_pool_name -vms "9${vmid}"
else
echo "Keine Datei gefunden, die mit '${pattern}' beginnt." >&2
return 1
fi
}
remove_synced_vms "$destination_pool_name"
mapfile -t vms < <(
ssh "$(lookup_ip_for_hostname $primary_pve_node)" pvesh get /cluster/resources --type vm --output json |
jq -r '.[] | "\(.vmid) \(.name) \(.node) \(.pool)"'
)
for vm in "${vms[@]}"; do
# Remove Remote Snapshots:
read -r vmid name node pool <<< "$vm"
ssh "$(lookup_ip_for_hostname "$node")" 'bash -s' -- < ./removeSourceSnapshots.sh "$sync_job_name"
echo "VMID: $vmid | Name: $name | Node: $node | Pool: $pool"
if [[ "$pool" == "$pool_name_to_sync" ]]; then
pve-zsync create --source "$(lookup_ip_for_hostname "$node"):$vmid" --dest "$destination_storage_name" --verbose --name "$sync_job_name"
pve-zsync destroy --source "$(lookup_ip_for_hostname "$node"):$vmid" --name "$sync_job_name"
get_latest_replica_file "/var/lib/pve-zsync" "$vmid"
else
echo "Überspringe VMID $vmid (Pool: $pool passt nicht zu $pool_name_to_sync)"
fi
done