Proxmox PBS ZFS best practice

ZFS Pool erstellen mit special device für Metadaten

zpool create  -o ashift=12  backupstorage raidz1  \
  /dev/disk/by-id/ata-ST24000NM000C-3WD103_ZXA0JCB5 \
  /dev/disk/by-id/ata-ST24000NM000C-3WD103_ZXA0EPHT \
  /dev/disk/by-id/ata-ST24000NM000C-3WD103_ZXA0K5GS \
  /dev/disk/by-id/ata-ST24000NM000C-3WD103_ZXA0MQM7 \
  special mirror /dev/disk/by-id/nvme-eui.01000000000000008ce38ee306063c4d /dev/disk/by-id/nvme-eui.01000000000000008ce38ee30a536b19

zpool add backupstorage log mirror /dev/disk/by-id/xxx /dev/disk/by-id/xxx
zpool add backupstorage cache /dev/disk/by-id/xxx

zfs set mountpoint=/mnt/pbs backupstorage
zfs set recordsize=1M backupstorage
zfs set compression=zstd backupstorage
zfs set atime=off backupstorage
zfs set xattr=sa backupstorage
zfs set acltype=posixacl backupstorage

zfs set recordsize=128K backupstorage
zfs set special_small_blocks=128K backupstorage # 32K alternativ

Benchmark

Sequentielle Backup-Performance (fio write, 1M)

fio --rw=write --bs=1M --numjobs=1 --iodepth=4
StorageDurchsatzLatenz (avg)
1× HDD-Mirror250–300 MB/s4–6 ms
2× HDD-Mirror450–550 MB/s5–8 ms
LimitierendHDD-Sequential

Sequentielle Restore-Performance (fio read, 1M)

fio --rw=read --bs=1M --numjobs=1 --iodepth=4
StorageDurchsatz
1× HDD-Mirror280–320 MB/s
2× HDD-Mirror500–650 MB/s
Mit warmem ARC+10–20 %

Verify-ähnlicher Read (fio read, 128K, 4 Jobs)

fio --rw=read --bs=128K --numjobs=4 --iodepth=2
SzenarioDurchsatzIOPS
Ohne Special Device80–150 MB/s600–1.200
Mit NVMe Special 250–400 MB/s2.000–3.500

ZFS Checkscript für Zabbix

#!/bin/bash
# zfs_health_check.sh
# Gibt 0 zurück, wenn alle ZFS-Pools ONLINE sind, sonst 1
# Optional: Mit Parameter --verbose werden Details ausgegeben
# echo1
# exit1
VERBOSE=0
if [[ "$1" == "--verbose" ]]; then
    VERBOSE=1
fi

# Prüfen, ob zpool vorhanden ist
if ! command -v zpool &>/dev/null; then
    echo "zpool command not found"
    exit 1
fi

POOLS=$(zpool list -H -o name)
if [ -z "$POOLS" ]; then
    echo "no_pools_found"
    exit 1
fi

ERROR_FOUND=0
DETAILS=""

for POOL in $POOLS; do
    HEALTH=$(zpool list -H -o health "$POOL")

    if [[ "$HEALTH" != "ONLINE" ]]; then
        ERROR_FOUND=1
        DETAILS+="$POOL:$HEALTH "
    else
        DETAILS+="$POOL:$HEALTH "
    fi
done

if [ $VERBOSE -eq 1 ]; then
    if [ $ERROR_FOUND -eq 0 ]; then
        echo "OK: all pools ONLINE -> $DETAILS"
    else
        echo "ERROR: $DETAILS"
    fi
else
    echo $ERROR_FOUND
fi
zabbix_sender -z zbx.upits.at -s "lavupbs01.upits.at" -k "zfs.state" -o $ERROR_FOUND
exit $ERROR_FOUND

Proxmox Cluster Sync

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

Proxmox VE Helper-Scripts

Die Webseite „Proxmox VE Helper-Scripts“ ist eine community-getriebene Plattform, die über 300 Skripte zur Verfügung stellt, um die Verwaltung und Einrichtung der Proxmox Virtual Environment (VE) zu vereinfachen. Sie richtet sich sowohl an erfahrene Nutzer als auch an Einsteiger im Homelab-Bereich. Neben einer umfangreichen Sammlung von Automatisierungsskripten bietet die Seite auch eine FAQ-Sektion, die häufige Fragen zur Installation, Fehlerbehebung und Aktualisierung von Anwendungen beantwortet. Die Plattform ist offen zugänglich, der Quellcode ist auf GitHub verfügbar, und sie wird aktiv von der Community gepflegt.

Proxmox VE Helper-Scripts

Ceph performance: benchmark and optimization

Die Webseite von croit GmbH beschreibt eine umfassende Benchmark- und Optimierungsstudie eines CephFS-Clusters mit NVMe-Laufwerken. Ziel war es, die maximale Leistung eines 5-Knoten-Ceph-Clusters mithilfe des IO500-Benchmarks zu demonstrieren. Dabei wurden verschiedene Hardware- und Softwarekonfigurationen getestet, darunter BIOS-Tuning, Netzwerkoptimierungen, CPU-Stromsparmodi, MDS-Skalierung und Containerisierung der Clients. Die besten Ergebnisse wurden durch gezielte Anpassungen wie das Deaktivieren des C6-Stromsparmodus, die Erhöhung des MDS-Caches und den Einsatz mehrerer MDS-Instanzen pro Host erzielt. Trotz zahlreicher Tests und Tuning-Versuche blieb die IO500-Gesamtpunktzahl bei etwa 11, was für ein 5-Knoten-Setup bemerkenswert ist. Die Studie zeigt, dass viele Ceph-Optimierungen außerhalb der eigentlichen Ceph-Konfiguration stattfinden – insbesondere im Bereich Netzwerk, CPU und Client-Architektur.

Ceph performance: benchmark and optimization | croit

Proxmox UPS Shutdown

Installation APC Dienste

apt install apcupsd

Konfiguration Master Server (bei dem die USV via USB Kabel verbunden ist)

nano /etc/apcupsd/apcupsd.conf

UPSCABLE usb
UPSTYPE usb
NETSERVER on
NISIP 0.0.0.0

Konfiguration Slave Server

nano /etc/apcupsd/apcupsd.conf

UPSCABLE ether
UPSTYPE net
DEVICE 10.2.51.1:3551 //IP vom Master Server

Dienste installieren und starten

service apcupsd start

apt install apcupsd-cgi
apt install apache2
a2enmod cgi
systemctl restart apache2

VMs herunterfahren

Skript zur kontrollierten Abschaltung von virtuellen Maschinen in Proxmox, wenn ein Stromausfall erkannt wird. 
HA wird zuvor bei den VMs deaktiviert um ein automatisches Neustarten zu verhindern.

/etc/apcupsd/shutdownvms.sh

#!/bin/bash
# Create list of VM Ids
running_vms=$(qm list | awk '/running/ {print $1}')
# Shutdown each VM
for vmid in $running_vms; do
    echo $(date +"%Y-%m-%d_%H-%M-%S ")"Disable HA For VM: "$vmid >> /var/log/shutdownvm.log
    ha-manager set $vmid --state ignored
    echo $(date +"%Y-%m-%d_%H-%M-%S ")"Shutdown VM: "$vmid >> /var/log/shutdownvm.log
    qm shutdown $vmid --skiplock
    sleep 2
done

Einstellung in /etc/apcupsd/apccontrol

 doshutdown)
        echo "UPS ${2} initiated Shutdown Sequence" | ${WALL}
        /etc/apcupsd/shutdownvms.sh
        ${SHUTDOWN} -h now "apcupsd UPS ${2} initiated shutdown"
    ;;

Shutdown-Zeit einstellen

/etc/apcupsd/apcupsd.conf

ZFS Recovery bei Disk-Fehler

Um bei ZFS die Disks einfacher zu erkennen sollte die Seriennummer vor Einbau notiert werden. Dadurch kann bei einem Fehler die Disk einfacher erkannt und ausgetauscht werden

Status überprüfen

Via Proxmox Webinterface oder via CLI:

zpool status

Disk tauschen:

Die Fehlerhafte Disk aus dem Server ausbauen und neue einbauen. Danach die neue Disk suchen:

ls -l /dev/disk/by-id/

Neue Disk einbinden

Neue Disk in ZFS einbinden und resilver durchführen

# bestehendes Dateisystem auf neuer (falls vorhanden) Disk löschen
wipefs -a /dev/disk/by-id/<new_disk_id>

# Disk ersetzen
zpool replace rpool <failed_disk_id> /dev/disk/by-id/<new_disk_id>

Monitoring Resilvering Process

zpool status

Proxmox ZFS-Verschlüsselung aktivieren

Erstellen

# Encrypted Dataset erstellen
 
zpool create -f -o ashift=12 VMStoragePool mirror /dev/sdc /dev/sdd

zfs create -o encryption=on -o keyformat=passphrase VMStoragePool/VMStorage

pvesm add zfspool encryptedVMStorage -pool VMStoragePool/VMStorage

Nach Neustart neu Einbinden

zfs mount -l VMStoragePool/VMStorage

pvesm add zfspool encryptedVMStorage -pool VMStoragePool/VMStorage