Eintrag

Proxmox VE im Unternehmensnetz: Cluster, HA, SDN & Backup ohne Internet

Proxmox VE im Unternehmensnetz: Cluster, HA, SDN & Backup ohne Internet

Ein einzelner Proxmox-Host ist schnell installiert. Ein Cluster, der in einem grossen Unternehmensnetz produktionsreif laeuft – mit echter Hochverfuegbarkeit, sauberer Netz-Segmentierung, verteiltem Storage, einer internen PKI und einem Backup, das im Ernstfall auch wirklich zurueckspielt – ist eine andere Hausnummer. Und wenn das Ganze in einem geschlossenen Netz ohne Internet-Anbindung stehen soll, faellt jede bequeme Abkuerzung weg: kein apt update aus dem Netz, kein Let’s-Encrypt, kein Cloud-NTP, kein schneller Blick ins offizielle Repo.

Dieser Post baut einen solchen Cluster Schritt fuer Schritt auf und erklaert bei jedem Aspekt das Warum – so, dass du die Entscheidungen nachvollziehen und im eigenen Netz reproduzieren kannst. Die Beispiele nutzen bewusst Unternehmens-Adressbereiche (mehrere 10.0.0.0/8-Segmente, VLAN-Trennung) statt eines typischen 192.168.1.0/24-Heimnetzes, und der rote Faden ist durchgehend der Air-Gap-Betrieb.

Dieser Post ist als Schritt-für-Schritt-Anleitung aufgebaut: Jedes Kapitel ist ein Schritt und liefert copy&paste-faehige Befehle, Configs und Skripte plus einen Verifikations-Block am Ende. Die Platzhalter (corp.local, 10.10.0.x, Interface-Namen, Disks) musst du an dein Netz anpassen – die Struktur der Schritte bleibt gleich.

So arbeitest du diesen Post durch

Die Schritte bauen aufeinander auf – arbeite sie in dieser Reihenfolge ab und pruefe nach jedem die Verifikation, bevor du weitergehst:

  1. Schritt 1 – Planung, Netzdesign, Adresskonzept (Papier vor Blech)
  2. Schritt 2 – Air-Gap-Basisdienste (Mirror, NTP, DNS, CA)
  3. Schritt 3 – Nodes installieren + Post-Install-Skript
  4. Schritt 4 – Cluster mit redundantem Corosync
  5. Schritt 5 – Ceph-Storage
  6. Schritt 6 – HA + Fencing
  7. Schritt 7–10 – SDN, Zertifikate, User/Rechte, Backup
  8. Schritt 11 – Betrieb, Monitoring, Updates

Voraussetzungen, die vor Schritt 1 stehen sollten:

  • Hardware: mindestens 3 Nodes mit je 4–6 NICs, getrennte OS- und Daten-Disks, Hardware-Watchdog (IPMI/iDRAC/iLO).
  • Netz: Switches mit VLAN-Trunking und Jumbo-Frames (MTU 9000) fuers Storage-Netz.
  • Ein Einschleus-Host mit Internet, um Pakete/ISO einmalig zu spiegeln.
  • Bestehende Firmen-Dienste erreichbar: AD/LDAP und eine interne CA.
  • Konventionen: FQDNs (pve-01.corp.local), festes IP-Schema (siehe Schritt 1).

Alle Befehle laufen als root (oder mit sudo). Wo ein Befehl auf jedem Node oder nur auf pve-01 gehoert, steht es dabei. Copy&paste-Bloecke sind so gehalten, dass du nur die Platzhalter ersetzt.


Zielbild: Was bauen wir eigentlich?

Bevor eine einzige ISO gebrannt wird, muss das Zielbild stehen. Wir bauen einen 3-Node-Cluster (das Minimum fuer echtes Quorum und HA) mit verteiltem Ceph-Storage, angebunden an eine bestehende Unternehmens-Infrastruktur (AD, interne CA, interner Paket-Mirror, PBS-Backup). Alles ohne Weg nach draussen.

flowchart TB
    subgraph MGMT["Management-Segment (VLAN 10 - 10.10.0.0/24)"]
        A1["pve-01"]
        A2["pve-02"]
        A3["pve-03"]
    end
    subgraph CORO["Corosync-Ring 0/1 (VLAN 20/21 - dediziert)"]
        A1 -. "totem" .- A2
        A2 -. "totem" .- A3
        A3 -. "totem" .- A1
    end
    subgraph CEPH["Ceph Cluster+Public (VLAN 30 - 10.30.0.0/24, MTU 9000)"]
        A1 --- OSD["OSDs / MON / MGR"]
        A2 --- OSD
        A3 --- OSD
    end
    subgraph SVC["Interne Dienste"]
        MIR["Paket-Mirror + APT-Repo"]
        CA["Interne CA / step-ca (ACME)"]
        AD["AD / LDAP"]
        NTP["NTP (chrony)"]
        PBS["Proxmox Backup Server"]
    end
    MGMT --> SVC
    A1 & A2 & A3 --> PBS

Die vier tragenden Saeulen:

Saeule Umsetzung Warum so
Quorum 3 Nodes + optional QDevice Ungerade Knotenzahl, damit bei Ausfall eines Nodes die Mehrheit steht
Storage Ceph (RBD) Shared Storage ist Voraussetzung fuer Live-Migration und HA-Recovery
Netz mehrere getrennte NICs/VLANs Corosync, Storage, VM-Traffic und Backup duerfen sich nicht gegenseitig aushungern
Backup Proxmox Backup Server, getrennt Ein Backup auf demselben Cluster ist kein Backup

Schritt 1: Planung, Netzdesign und Adresskonzept

Node-Dimensionierung

Fuer einen produktiven Enterprise-Cluster gelten andere Massstaebe als im Heim-Setup. Als belastbare Ausgangswerte pro Node:

  • CPU: 2 Sockel, genug Kerne fuer deine VM-Last + Reserve fuer den Ausfall eines Nodes (bei 3 Nodes muss jeder ~50 % Zusatzlast tragen koennen, wenn einer wegfaellt).
  • RAM: VM-Bedarf + Ceph (Faustregel ~4 GB je OSD) + ZFS-ARC (falls lokales ZFS) + Reserve. RAM ist bei HA der limitierende Faktor, nicht CPU.
  • Disks: getrennt fuer OS (2× SSD im ZFS-Mirror) und OSDs (Enterprise-SSD/NVMe mit Power-Loss-Protection). Ceph auf Consumer-SSDs ohne PLP ist quaelend langsam.
  • NICs: mindestens 4 physische Ports, besser 6 (siehe unten).

Rechne bei HA immer mit n-1: Der Cluster muss den Verlust eines kompletten Nodes verkraften, ohne dass die verbleibenden ins Schwitzen kommen. Ein 3-Node-Cluster, der zu 80 % ausgelastet ist, hat kein HA – er hat nur noch nicht bemerkt, dass er keins hat.

Hardware-Spezifikationen im Ueberblick

Konkrete Anhaltswerte in drei Stufen. Mindestens = laeuft ueberhaupt; Best Practice = solide Produktion; Empfehlung = die Zielgroesse fuer den in diesem Guide beschriebenen Cluster.

Proxmox-VE-Node (je Node):

Komponente Mindestens Best Practice Empfehlung (dieser Guide)
Nodes im Cluster 3 (oder 2 + QDevice) 3–5, ungerade 3+ ungerade, mit n-1-Reserve
CPU 8 Kerne, 1 Sockel 16+ Kerne, 2 Sockel, AVX2 2× 16 Kerne (32C/64T), Reserve fuer n-1
RAM 32 GB ECC 128 GB ECC 256 GB ECC (VM-Last + ~4 GB je OSD + ARC + Reserve)
Boot-/OS-Disks 1× SSD 2× SSD (ZFS-Mirror) 2× 240 GB Enterprise-SSD, ZFS RAID1
Ceph-OSD-Disks 1× SSD 3–4× SSD mit PLP 4–8× Enterprise-NVMe mit Power-Loss-Protection
Netzwerk 2× 1 GbE 4 Ports (Corosync getrennt) 6 Ports: 2×1G Corosync + 2×10G Mgmt/VM + 2×25G Ceph
Out-of-Band IPMI/iDRAC/iLO dediziertes OOB-Netz (Fencing + Remote Hands)

Proxmox Backup Server (PBS):

Komponente Mindestens Best Practice Empfehlung (dieser Guide)
CPU 4 Kerne 8+ Kerne, AES-NI 8–16 Kerne mit AES-NI (Dedup + Verify sind CPU-lastig)
RAM 8 GB 32 GB ECC 64 GB ECC (skaliert mit Datastore-Groesse / Dedup-Index)
Boot-/OS-Disk 1× SSD 2× SSD (ZFS-Mirror) 2× SSD, ZFS RAID1
Datastore HDD, einzeln ZFS RAIDZ2 (HDD) RAIDZ2 aus HDDs + NVMe-Special-Device fuer den Chunk-Index
Netzwerk 1 GbE 10 GbE 10/25 GbE (kurzes Backup-Fenster)
Standort anderer Brandabschnitt getrennter Standort, dazu Offsite/Tape

PBS-Besonderheiten: Der Dedup-Index lebt im RAM – mehr Datastore = mehr RAM. Und ein NVMe-Special-Device fuer die ZFS-Metadaten (bzw. den Chunk-Index) beschleunigt Verify und Garbage Collection auf HDD-Stores um Groessenordnungen. AES-NI in der CPU macht die client-seitige Verschluesselung praktisch kostenlos.

Warum mehrere NICs? (Das Netz-Design ist die halbe Miete)

Der haeufigste und teuerste Fehler beim Cluster-Bau: allen Traffic ueber eine einzige NIC (oder einen einzigen Bond) schicken. Im Labor mit drei Test-VMs faellt das nie auf – in Produktion unter Last kippt genau daran der Cluster. Um zu verstehen warum, muss man zwei grundverschiedene Anforderungen auseinanderhalten, die gern in einen Topf geworfen werden: Bandbreite und Latenz.

Bandbreite ist nicht Latenz

  • Storage und Backup brauchen Bandbreite. Sie schaufeln grosse Datenmengen; ob ein einzelnes Paket 1 ms oder 5 ms braucht, ist egal – Hauptsache der Durchsatz stimmt.
  • Corosync braucht Latenz. Es verschickt winzige Heartbeat-Pakete, aber die muessen konstant und schnell ankommen. Der Cluster erwartet Antworten im niedrigen Millisekunden-Bereich; wird die Leitung voll, staut sich der Puffer (Bufferbloat) und die Latenz springt von Bruchteilen einer Millisekunde auf zweistellige Werte.

Das ist der Kern des Problems: Ein voll ausgelastetes 10-GbE-Storage-Netz hat prima Bandbreite und trotzdem miserable Latenz. Legt man Corosync mit auf diese Leitung, sabotiert der Storage-Traffic den Cluster-Heartbeat – obwohl “genug Bandbreite” da waere. Bandbreite kann man kaufen; die Latenz eines gesaettigten Links nicht.

Der Kaskaden-Ausfall: was ohne Trennung wirklich passiert

Ein realistisches Szenario, wenn Corosync, Storage und Backup sich einen Bond teilen:

sequenceDiagram
    participant BK as Backup-Job (nachts)
    participant NIC as gemeinsame NIC / Bond
    participant CO as Corosync
    participant HA as HA-Manager
    BK->>NIC: sichert 2 TB, NIC 100% ausgelastet
    Note over NIC,CO: Latenz steigt 0.3ms auf 40ms (Bufferbloat)
    Note over CO: Heartbeat-Timeout ueberschritten
    CO->>HA: Node gilt als tot, Quorum-Neuwahl
    HA->>HA: Watchdog fenced Node, VMs migrieren
    Note over HA,NIC: Migrations-Traffic saettigt NIC zusaetzlich, naechster Node kippt

Ein einzelner Backup-Job loest also eine Kettenreaktion aus: Heartbeat-Timeout, Fencing, VM-Migration, dadurch noch mehr Last auf derselben Leitung, naechster Node kippt. Das ist kein hypothetisches Risiko, sondern der klassische “warum ist nachts der halbe Cluster neu gestartet?”-Vorfall – und er ist mit getrennten Netzen komplett vermeidbar.

Die Rollen im Detail

Deshalb trennen wir den Traffic physisch nach Rolle. Jede Zeile hat einen konkreten Grund, warum sie nicht mit den anderen geteilt werden darf:

NIC-Rolle Traffic Bandbreite Warum getrennt
Management / Web-UI SSH, API, GUI 1–10 GbE Muss erreichbar bleiben, gerade wenn das Storage-Netz haengt – sonst kommst du im Stoerfall nicht mehr an den Cluster
Corosync Ring 0 Cluster-Heartbeat 1 GbE reicht Latenz schlaegt Bandbreite; darf nie gesaettigt werden, sonst faelscht Bufferbloat den Cluster-Zustand
Corosync Ring 1 Redundanter Heartbeat 1 GbE Zweiter, physisch getrennter Pfad, damit ein Switch-Ausfall den Cluster nicht kippt
Ceph Public Client ↔ Ceph 10/25 GbE Storage-I/O saettigt schnell alles andere – gehoert weg von Corosync und Management
Ceph Cluster OSD-Replikation 10/25 GbE Recovery nach einem Disk-/Node-Ausfall kann 10 GbE stundenlang dauerhaft fuellen
VM-Traffic Gast-Netzwerke 10 GbE, VLAN-Trunk Mandanten-/Produktionstrennung; ein Gast darf die Infrastruktur-Netze nicht sehen

Warum Ceph zwei eigene Netze (Public und Cluster)? Das Public-Netz traegt den Verkehr zwischen VMs und Ceph (Lesen/Schreiben). Das Cluster-Netz traegt die Replikation zwischen den OSDs. Faellt ein Node aus, muss Ceph tausende Objekte neu verteilen – dieser Recovery-Traffic ist massiv. Lage er auf dem Public-Netz, wuerde jedes Ceph-Recovery gleichzeitig die laufenden VMs ausbremsen. Getrennt merkt der Gast vom Recovery nichts.

Buendelung: Bonds, VLANs und Ringe

In der Praxis buendelt man diese Rollen auf 4–6 physische NICs. Das Beispiel unten zeigt einen Node mit 6 physischen NICs: zwei 10-GbE-Ports (eno3+eno4) werden per LACP zu bond0 (Management + VM-Trunk), zwei 25-GbE-Ports (ens1f0+ens1f1) zu bond1 (Ceph, Jumbo Frames), und zwei 1-GbE-Ports (eno1, eno2) gehen ungebuendelt direkt an die beiden Corosync-Ringe. Lies das Diagramm von links (physischer NIC) ueber die Buendelung (Mitte) nach rechts (Netz/Rolle):

flowchart LR
    NIC3["eno3<br/>10GbE"] --> B0["bond0 · LACP"]
    NIC4["eno4<br/>10GbE"] --> B0
    NIC5["ens1f0<br/>25GbE"] --> B1["bond1 · LACP<br/>MTU 9000"]
    NIC6["ens1f1<br/>25GbE"] --> B1
    B0 -->|"Trunk, VLANs 2-4094"| VMBR0["vmbr0<br/>Management + VM-VLANs"]
    B1 -->|"VLAN 30/31 · MTU 9000"| CEPHNET["Ceph<br/>Public + Cluster"]
    NIC1["eno1<br/>1GbE"] -->|"VLAN 20"| RING0["Corosync<br/>Ring 0"]
    NIC2["eno2<br/>1GbE"] -->|"VLAN 21"| RING1["Corosync<br/>Ring 1"]

    classDef nic fill:#1f2a37,stroke:#e57000,stroke-width:2px,color:#fff;
    classDef bond fill:#2a2140,stroke:#b07cff,stroke-width:2px,color:#fff;
    classDef net fill:#0d1117,stroke:#8aa0b6,stroke-width:1.5px,color:#fff;
    class NIC1,NIC2,NIC3,NIC4,NIC5,NIC6 nic;
    class B0,B1 bond;
    class VMBR0,CEPHNET,RING0,RING1 net;

Die sechs orangefarbenen Kaesten sind die tatsaechlich verbauten NICs, die zwei violetten die LACP-Bonds (je 2 NICs), rechts die Netze/Rollen. eno1/eno2 haben bewusst keinen Bond – warum, steht direkt darunter.

So sieht das konkret in /etc/network/interfaces aus (gekuerzt): ein LACP-Bond fuer Management + VM-Trunk, ein separater Bond mit Jumbo-Frames fuer Ceph, und Corosync bewusst ohne Bond auf einem dedizierten Port:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Management + VM-Trunk (LACP-Bond)
auto bond0
iface bond0 inet manual
    bond-slaves eno3 eno4
    bond-mode 802.3ad
    bond-miimon 100
    bond-xmit-hash-policy layer3+4

auto vmbr0
iface vmbr0 inet static
    address 10.10.0.11/24
    gateway 10.10.0.1
    bridge-ports bond0
    bridge-vlan-aware yes
    bridge-vids 2-4094

# Corosync Ring 0 - eigener Port, eigenes VLAN, KEIN Bond
auto eno1.20
iface eno1.20 inet static
    address 10.20.0.11/24
    mtu 1500

# Ceph - eigener Bond, Jumbo Frames durchgaengig
auto bond1
iface bond1 inet manual
    bond-slaves ens1f0 ens1f1
    bond-mode 802.3ad
    mtu 9000

auto vmbr1
iface vmbr1 inet static
    address 10.30.0.11/24
    bridge-ports bond1
    mtu 9000

Bridge-Modell: eine Trunk-Bridge statt eine Bridge je VLAN

Beim Anlegen der VM-Netze gibt es zwei Wege – und die Wahl entscheidet, wie gut das Setup im Cluster skaliert.

Variante A – Bridge-per-VLAN (klassisch): Pro VLAN eine eigene Bridge mit getaggtem Sub-Interface als Port, der Bridge-Name kodiert die VLAN-ID:

1
2
3
4
5
6
7
8
auto eno1.30
iface eno1.30 inet manual

auto vmbr30
iface vmbr30 inet manual
    bridge-ports eno1.30      # vmbr30 -> VLAN 30, vmbr45 -> VLAN 45, ...
    bridge-stp off
    bridge-fd 0

Das ist selbsterklaerend (vmbr30 = VLAN 30) und im Homelab beliebt. Nachteil im Cluster: Jede neue VLAN heisst interfaces editieren und ifreload -a auf jedem Node, jede Bridge muss auf allen Nodes namensgleich existieren (sonst bricht die Live-Migration), und der Host bekommt schnell eine IP in jedem Segment – unnoetige Angriffsflaeche.

Variante B – VLAN-aware Bridge (Enterprise-Standard): Eine Trunk-Bridge, der VLAN-Tag steht am VM-NIC (Feld VLAN Tag in der GUI, net0: ...,tag=30). Neue VLAN = neuer Tag, keine Host-Aenderung:

1
2
3
4
5
6
7
auto vmbr0
iface vmbr0 inet static
    address 10.10.0.11/24
    gateway 10.10.0.1
    bridge-ports bond0
    bridge-vlan-aware yes
    bridge-vids 2-4094
Kriterium A: Bridge-per-VLAN B: VLAN-aware / SDN
Neue VLAN interfaces + ifreload auf jedem Node Tag/VNet setzen, keine Host-Aenderung
Cluster-Konsistenz Bridge muss ueberall namensgleich sein VNet clusterweit identisch (SDN verteilt es)
Live-Migration / HA Ziel-Node braucht gleichnamige Bridge Tag/VNet reist mit der VM-Config
Host-Exposure Host-IP in jedem VLAN Mgmt bleibt in einem Segment
SDN nutzbar nein ja (baut genau darauf auf)

Namenskonvention behalten, nur verlagern: Die Idee, die VLAN-ID in den Namen zu ziehen (vmbr30), ist auch im Enterprise-Umfeld goldwert – nur haeng sie nicht an die Bridge, sondern an die SDN-VNets (vnet30, vnet45, …). So behaeltst du die Lesbarkeit und bekommst clusterweite Konsistenz, Live-Migration und automatische Verteilung. Genau das macht Schritt 7 (SDN).

Fuer einen einzelnen Node ist Variante A voellig in Ordnung; sobald Cluster mit HA/Migration im Spiel ist, ist Variante B (bzw. SDN) der Standard.

Warum zwei Corosync-Ringe statt eines Bonds

Ein naheliegender Gedanke: “Ich packe Corosync doch einfach auf einen redundanten LACP-Bond, dann ist es auch ausfallsicher.” Das ist ein Trugschluss. Ein Bond schuetzt nur gegen den Ausfall eines NIC-Ports oder Kabels – beide Bond-Mitglieder haengen aber typischerweise am selben Switch (oder an einem MLAG-Paar mit gemeinsamer Konfiguration). Ein Switch-Absturz, ein fehlerhaftes Firmware-Update oder ein STP-Sturm nimmt dir den kompletten Bond auf einen Schlag.

Corosync loest das anders und besser: Es kennt nativ mehrere unabhaengige Ringe (link0, link1). Du legst Ring 0 und Ring 1 auf zwei physisch getrennte Ports, zwei getrennte VLANs und zwei getrennte Switches. Faellt ein kompletter Pfad (Switch, VLAN, Verkabelung) aus, laeuft der Heartbeat ueber den anderen weiter – ohne dass ein einziger Node das Quorum verliert. Das ist echte Pfad-Redundanz, die ein Bond prinzipbedingt nicht liefern kann.

Merksatz: Bonding = Redundanz gegen Port-/Kabel-Ausfall (fuer Storage/Management sinnvoll). Zwei Corosync-Ringe = Redundanz gegen Pfad-/Switch-Ausfall (fuer den Cluster-Heartbeat Pflicht). Beides ergaenzt sich, das eine ersetzt das andere nicht.

Wie viele Ports brauchst du wirklich?

Die Portzahl skaliert mit dem Anspruch – eine ehrliche Staffelung:

Ports Aufteilung Geeignet fuer
2 1 Bond, alles gemeinsam Nur Lab / PoC – nicht fuer Produktion
4 1 Bond (Mgmt + VM) + 2× 1 GbE Corosync (Ring 0/1) Einstiegs-Produktion, moderate I/O
6 + eigener Ceph-Bond (2× 10/25 GbE) Empfohlener Standard fuer HA + Ceph
8 + Ceph Public und Cluster auf getrennten Bonds I/O-intensive oder grosse Cluster

Die 4-Port-Variante ist das realistische Minimum fuer einen ernsthaften Cluster; ab 6 Ports wird es entspannt. Wichtiger als “moeglichst viele” ist die richtige Zuordnung: Corosync zuerst absichern, dann Storage trennen.

Out-of-Band: das Netz, das man vergisst

Zusaetzlich zu den Daten-NICs gehoert jeder Enterprise-Node an ein Out-of-Band-Management (IPMI / iDRAC / iLO) in einem eigenen, gesicherten Segment. Zwei Gruende: Erstens braucht das Fencing im Idealfall genau diesen Kanal (der Cluster kann einen haengenden Node ueber IPMI hart abschalten, selbst wenn dessen OS voellig weg ist). Zweitens ist es im Air-Gap deine einzige “Remote Hands” – es gibt keinen Cloud-KVM, mit dem du dich auf die Konsole eines Nodes schaltest. Ohne IPMI stehst du bei einem Boot-Problem im Serverraum.

Jumbo Frames fuer Storage

Das Ceph-Netz laeuft mit MTU 9000 (Jumbo Frames). Bei 10/25 GbE und grossen Bloecken reduziert das die Paket-Anzahl (und damit CPU-Overhead) deutlich. Wichtig: durchgaengig – NIC, Bond, Bridge/VLAN und die Switches muessen 9000 koennen, sonst zerschiesst du dir die Verbindung mit fragmentierten Paketen. Management- und Corosync-Netze bleiben bei MTU 1500.

Adresskonzept fuer ein grosses Netz

Statt eines flachen /24 planen wir sauber getrennte Segmente. Beispielhaftes Schema (an dein Netz anpassen):

Segment VLAN Netz Zweck
Management 10 10.10.0.0/24 PVE-Web-UI, SSH, API
Corosync Ring 0 20 10.20.0.0/24 Cluster-Heartbeat
Corosync Ring 1 21 10.21.0.0/24 Redundanter Heartbeat
Ceph Public 30 10.30.0.0/24 Storage-Zugriff
Ceph Cluster 31 10.31.0.0/24 OSD-Replikation
VM-Prod 100–199 172.16.0.0/16 Produktions-VMs (per SDN)
VM-Mgmt-Services 40 10.40.0.0/24 Mirror, CA, PBS, NTP, AD

Andere Blöcke sind Konvention, nicht Pflicht. Dass hier die Infrastruktur in 10.x und die Workload-VMs in 172.16.x liegen, dient allein der optischen Trennung von Underlay (die Plattform) und Overlay (die VMs). Technisch sind 10.0.0.0/8, 172.16.0.0/12 und 192.168.0.0/16 gleichwertig – es sind RFC-1918-Blöcke, keine “Klassen” (modernes Networking ist klassenlos, CIDR). Genauso valide waere alles in einem 10.0.0.0/8 (z. B. VM-Prod in 10.100.0.0/16). Was im grossen Netz wirklich zaehlt: (1) keine Ueberschneidung mit bereits belegten Bereichen (Routing, VPN-Kopplung, Firmen-Fusionen) und (2) der IPAM-Policy deines Netzes folgen. In vielen Unternehmen ist 10/8 bereits flaechendeckend vergeben – dann weicht man fuer neue SDN-/VM-Segmente bewusst auf 172.16/12 aus.

Feste Node-Adressen (Beispiel Management-Segment):

1
2
3
pve-01  10.10.0.11    Corosync0 10.20.0.11   Corosync1 10.21.0.11   Ceph 10.30.0.11 / 10.31.0.11
pve-02  10.10.0.12    Corosync0 10.20.0.12   Corosync1 10.21.0.12   Ceph 10.30.0.12 / 10.31.0.12
pve-03  10.10.0.13    Corosync0 10.20.0.13   Corosync1 10.21.0.13   Ceph 10.30.0.13 / 10.31.0.13

Schritt 2: Air-Gap-Basisdienste bereitstellen

Ein geschlossenes Netz aendert nicht was du tust, aber woher die Bausteine kommen. Vier Dienste musst du vorher intern bereitstellen, sonst kommst du beim Cluster-Aufbau nicht weit:

flowchart LR
    subgraph EXT["Einschleus-Zone (temporaer / getrennt)"]
        DL["Download-Host mit Internet"]
    end
    subgraph INT["Geschlossenes Netz"]
        MIR["APT-Mirror<br/>Debian + PVE + Ceph"]
        NTP["NTP-Server<br/>chrony"]
        DNS["Interner DNS<br/>Forward + Reverse"]
        CA["Interne CA<br/>step-ca (ACME)"]
    end
    DL -->|"Datentraeger / Diode"| MIR
    MIR --> PVE["Proxmox-Nodes"]
    NTP --> PVE
    DNS --> PVE
    CA --> PVE

Arbeite die folgenden vier Teilschritte der Reihe nach ab. Jeder endet mit einem Verifikations-Befehl, mit dem du pruefst, ob es geklappt hat, bevor du weitergehst.

1.1 Interner Paket-Mirror

Ziel: Debian-, PVE- und Ceph-Pakete ohne Internet auf allen Nodes verfuegbar machen.

a) In der Einschleus-Zone (ein Host mit Internet) apt-mirror einrichten:

1
apt-get install -y apt-mirror
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/apt/mirror.list  (Einschleus-Host)
set base_path    /srv/mirror
set nthreads     10
set _tilde 0

# Debian Base (Bookworm) - main + security
deb http://deb.debian.org/debian bookworm main contrib
deb http://security.debian.org/debian-security bookworm-security main contrib

# Proxmox VE - No-Subscription (Umgebungen ohne Subscription)
deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription
# Proxmox Ceph (Reef)
deb http://download.proxmox.com/debian/ceph-reef bookworm no-subscription

clean http://deb.debian.org/debian

b) Spiegel ziehen und ausschleusen – als wiederholbares Skript (auch fuer spaetere Updates):

1
2
3
4
5
6
#!/usr/bin/env bash
# sync-mirror.sh - auf dem Einschleus-Host ausfuehren
set -euo pipefail
apt-mirror                                          # zieht/aktualisiert /srv/mirror
rsync -a --delete /srv/mirror/mirror/ /media/transfer/pve-mirror/   # auf Wechseldatentraeger/Diode
echo "Fertig - Datentraeger ins geschlossene Netz bringen und dort nach /srv/mirror einspielen."

c) Im geschlossenen Netz den Spiegel per Webserver bereitstellen (nginx-Beispiel):

1
2
3
4
5
6
7
8
# /etc/nginx/sites-available/mirror
server {
    listen 80;
    server_name mirror.corp.local;
    root /srv/mirror;          # hierhin wurde der Spiegel eingespielt
    autoindex on;
    location / { try_files $uri $uri/ =404; }
}
1
2
ln -s /etc/nginx/sites-available/mirror /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

d) Auf jedem Node die Paketquellen auf den internen Host umbiegen:

1
2
3
4
5
# /etc/apt/sources.list.d/pve-internal.list  (jeder Node)
deb http://mirror.corp.local/debian bookworm main contrib
deb http://mirror.corp.local/debian-security bookworm-security main contrib
deb http://mirror.corp.local/pve bookworm pve-no-subscription
deb http://mirror.corp.local/ceph-reef bookworm no-subscription

Verifikation (auf einem Node):

1
2
curl -sI http://mirror.corp.local/pve/dists/bookworm/Release | head -1   # -> HTTP/1.1 200 OK
apt update                                                               # -> keine "Could not resolve"-Fehler

Der Mirror ist auch dein Update-Kanal. Sicherheitsupdates spielst du ein, indem du sync-mirror.sh neu laufen laesst, den Delta-Stand einschleust und auf den Nodes apt update && apt full-upgrade faehrst. Plane das als wiederkehrenden Prozess – ein Air-Gap ist keine Ausrede fuer veraltete Pakete.

1.2 Interne Zeit (NTP)

Ziel: Alle Nodes laufen auf exakt gleicher Zeit – Corosync, Ceph und TLS reagieren allergisch auf Drift.

Auf dem internen NTP-Server (idealerweise an DCF77/GPS oder AD-Zeit):

1
2
3
4
5
# /etc/chrony/chrony.conf  (ntp.corp.local)
# externe Quelle ODER lokale Referenzuhr; hier: als Stratum-Quelle fuers Netz
allow 10.0.0.0/8
local stratum 8
makestep 1.0 3

Auf jedem Node:

1
2
3
4
5
6
7
apt-get install -y chrony
cat > /etc/chrony/chrony.conf <<'EOF'
server ntp.corp.local iburst
makestep 1.0 3
rtcsync
EOF
systemctl restart chrony

Verifikation (auf einem Node):

1
2
chronyc sources -v        # -> ntp.corp.local mit "^*" (synchronisiert)
chronyc tracking          # -> Leap status: Normal, kleiner Offset

1.3 Interner DNS (Forward + Reverse)

Ziel: Jeder Node loest sich selbst und alle anderen in beide Richtungen auf (A + PTR). Fehlt das Reverse, bricht der Cluster-Join gern an unerwarteter Stelle ab.

Neben den DNS-Eintraegen (A + PTR fuer jede Node-IP im Management-Segment) traegst du zur Sicherheit auf jedem Node einen /etc/hosts-Fallback ein:

1
2
3
4
5
# /etc/hosts  (jeder Node)
127.0.0.1        localhost
10.10.0.11       pve-01.corp.local pve-01
10.10.0.12       pve-02.corp.local pve-02
10.10.0.13       pve-03.corp.local pve-03

Verifikation (auf jedem Node):

1
2
3
hostname -f                       # -> pve-01.corp.local (FQDN korrekt)
dig +short pve-02.corp.local      # -> 10.10.0.12  (Forward)
dig +short -x 10.10.0.12          # -> pve-02.corp.local.  (Reverse/PTR)

1.4 Interne CA im Trust-Store

Ziel: Das Root-Zertifikat deiner internen CA ist auf jedem Node (und spaeter jedem Client) vertraut – Voraussetzung fuer die spaeteren PVE-Zertifikate (Details im Zertifikate-Kapitel).

Root-CA-Zertifikat einspielen (copy&paste, auf jedem Node):

1
2
3
# corp-root-ca.crt vom internen Mirror/Share holen und als vertrauenswuerdig markieren
cp corp-root-ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt
update-ca-certificates            # -> "1 added"

Verifikation:

1
openssl verify -CApath /etc/ssl/certs /usr/local/share/ca-certificates/corp-root-ca.crt  # -> OK

Schritt 3: Nodes installieren

3.1 ISO einschleusen und booten

Die Proxmox-VE-ISO wird einmalig eingeschleust und intern verteilt – per USB oder, fuer viele Nodes eleganter, per PXE/HTTP-Boot vom internen Mirror. Fuer identische Nodes lohnt die Proxmox Automated Installation mit Answer-File.

3.2 Installer durchklicken (je Node)

  1. Vom Installer booten und als Ziel ZFS (RAID1) ueber die zwei OS-SSDs waehlen – ein gespiegeltes Root ueberlebt den Ausfall einer OS-Disk.
  2. Zeitzone, Tastatur, ein starkes root-Passwort und eine gueltige Admin-Mail (fuer lokale Notifications) setzen.
  3. Management-Netz konfigurieren: FQDN pve-01.corp.local, IP 10.10.0.11/24, Gateway, interner DNS.
  4. Nach dem ersten Boot per SSH/Konsole anmelden und mit dem Post-Install-Skript (unten) fertig konfigurieren.

3.3 Automatisierte Installation mit Answer-File (viele/identische Nodes)

Das manuelle Durchklicken (3.2) je Server per IPMI-KVM ist bei mehreren Nodes muehsam und fehleranfaellig. Seit PVE 8.2 gibt es die Automated Installation: Eine Datei answer.toml beschreibt die komplette Installation, das Werkzeug proxmox-auto-install-assistant backt sie in die offizielle ISO. Jeder Node wird damit bit-identisch und ohne Interaktion aufgesetzt – im Air-Gap Gold wert, weil du dich sonst per Remote-Konsole durch jeden Installer klicken muesstest.

flowchart LR
    A["answer.toml<br/>+ offizielle ISO"] --> B["prepare-iso<br/>(Answer eingebettet)"]
    B --> C["pve-XX-auto.iso"]
    C --> D["Boot per USB/IPMI<br/>installiert automatisch"]
    D --> E["Reboot<br/>dann pve-postinstall.sh"]

a) Werkzeug installieren (auf dem Einschleus-Host):

1
apt-get install -y proxmox-auto-install-assistant

b) answer.toml schreiben (Beispiel: ZFS-Mirror, feste IP, dt. Tastatur):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[global]
keyboard = "de"
country  = "de"
fqdn     = "pve-01.corp.local"
mailto   = "[email protected]"
timezone = "Europe/Berlin"
# Passwort-Hash erzeugen mit:  openssl passwd -6
root_password_hashed = "$6$SALT$HASH..."
reboot_on_error = false

[network]
source  = "from-answer"
cidr    = "10.10.0.11/24"
gateway = "10.10.0.1"
dns     = "10.40.0.53"
# Management-NIC per MAC auswaehlen (robuster als der Interface-Name):
filter.ID_NET_NAME_MAC = "*aabbccddee01"

[disk-setup]
filesystem = "zfs"
zfs.raid   = "raid1"
disk_list  = ["nvme0n1", "nvme1n1"]

c) Validieren und ISO backen:

1
2
3
4
5
6
7
# Syntax/Vollstaendigkeit pruefen
proxmox-auto-install-assistant validate-answer answer.toml

# Answer in die offizielle ISO einbetten
proxmox-auto-install-assistant prepare-iso proxmox-ve_8.x.iso \
  --fetch-from iso --answer-file answer.toml \
  --output pve-01-auto.iso

d) Mehrere Nodes – je Node ein Answer-File (Unterschiede: fqdn, cidr, NIC-MAC), dann alle ISOs in einem Rutsch:

1
2
3
4
5
for n in 01 02 03; do
  proxmox-auto-install-assistant prepare-iso proxmox-ve_8.x.iso \
    --fetch-from iso --answer-file answer-${n}.toml \
    --output pve-${n}-auto.iso
done

Noch weniger Handarbeit – HTTP-Fetch: Mit --fetch-from http holt der Installer das Answer-File zur Laufzeit von einer URL (z. B. vom internen Mirror). Ein kleiner Endpoint liefert pro Node das richtige File anhand von Seriennummer/MAC – dann brauchst du nur eine ISO fuer die ganze Flotte. Im Air-Gap zeigt die URL auf einen internen Host.

e) Verteilen und booten: Die ISO per USB oder IPMI Virtual Media mounten und booten – die Installation laeuft vollautomatisch durch, der Node rebootet und ist bereit fuer das Post-Install-Skript (3.4).

3.4 Post-Install-Skript (auf jedem Node)

Statt jeden Node haendisch nachzuziehen, laeuft ein einziges Skript durch: Repos umbiegen, Enterprise-Repo deaktivieren, NTP setzen, ZFS-ARC begrenzen, Kernel-Tuning fuer Cluster/Storage, Update. Oben die drei Variablen anpassen, dann copy&paste:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env bash
# pve-postinstall.sh - auf JEDEM Node nach der Installation ausfuehren (als root)
set -euo pipefail

MIRROR="mirror.corp.local"     # interner APT-Mirror
NTP="ntp.corp.local"           # interner Zeitserver
ARC_MAX_GB=16                  # ZFS-ARC-Obergrenze (an RAM/Ceph anpassen)

echo ">> 1/6  Enterprise-Repos deaktivieren"
rm -f /etc/apt/sources.list.d/pve-enterprise.list /etc/apt/sources.list.d/ceph.list

echo ">> 2/6  interne Paketquellen setzen"
cat > /etc/apt/sources.list.d/pve-internal.list <<EOF
deb http://${MIRROR}/debian bookworm main contrib
deb http://${MIRROR}/debian-security bookworm-security main contrib
deb http://${MIRROR}/pve bookworm pve-no-subscription
deb http://${MIRROR}/ceph-reef bookworm no-subscription
EOF

echo ">> 3/6  Zeitserver (chrony)"
apt-get install -y chrony
cat > /etc/chrony/chrony.conf <<EOF
server ${NTP} iburst
makestep 1.0 3
rtcsync
EOF
systemctl restart chrony

echo ">> 4/6  ZFS-ARC begrenzen (${ARC_MAX_GB} GB)"
echo "options zfs zfs_arc_max=$((ARC_MAX_GB*1024*1024*1024))" > /etc/modprobe.d/zfs.conf
update-initramfs -u -k all

echo ">> 5/6  Kernel-Tuning fuer Cluster/Storage"
cat > /etc/sysctl.d/99-pve-cluster.conf <<'EOF'
# groessere Netzpuffer fuer Ceph/Migration
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# mehr ARP-Eintraege in grossen L2-Segmenten
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
# Kernel-Panic-Reboot als letzte Fencing-Reserve
kernel.panic = 10
EOF
sysctl --system >/dev/null

echo ">> 6/6  System aktualisieren"
apt-get update && apt-get -y full-upgrade

echo ">> fertig - bitte Node einmal neu starten (reboot)"

Ohne Subscription blendet die GUI beim Login einen Hinweis ein – rein kosmetisch, ohne Funktionsbezug. Er laesst sich patchen, aber da das offizielle Verhalten sich mit Updates aendern kann, hier bewusst kein Snippet. Ignorieren oder ueber ein separat gepflegtes Post-Update-Skript entfernen.

3.5 Verifikation (je Node)

1
2
3
4
pveversion                       # -> pve-manager/8.x, kein Fehler
apt-get update                   # -> zieht sauber vom internen Mirror
chronyc tracking | grep Stratum  # -> synchronisiert
arcstat 1 1 2>/dev/null || cat /sys/module/zfs/parameters/zfs_arc_max   # -> Limit gesetzt

Schritt 4: Cluster aufbauen (redundantes Corosync)

Jetzt kommt der Kern. Voraussetzung: Schritt 1–3 sind auf allen drei Nodes durch, die Ring-Netze (VLAN 20/21) sind konfiguriert und die Nodes sehen sich (ping auf den Ring-Adressen).

4.1 Firewall fuer Corosync freigeben

Corosync spricht UDP 5405–5412 auf den Ring-Netzen. Sind Host-Firewalls aktiv, muss das offen sein, sonst findet der Cluster nicht zusammen:

1
2
# auf jedem Node (Beispiel nftables/pve-firewall: Ring-Netze erlauben)
# Cluster-Netze 10.20.0.0/24 und 10.21.0.0/24 duerfen UDP 5405-5412

4.2 Cluster auf pve-01 erzeugen

Auf dem ersten Node den Cluster mit zwei Corosync-Links (redundante Ringe) anlegen:

1
2
3
4
# NUR auf pve-01
pvecm create DC-CLUSTER \
  --link0 address=10.20.0.11 \
  --link1 address=10.21.0.11

4.3 pve-02 und pve-03 beitreten

Der Join laeuft vom beitretenden Node aus (nicht von pve-01), jeweils mit dessen eigenen Ring-Adressen:

1
2
3
4
5
6
7
8
9
# Auf pve-02
pvecm add 10.20.0.11 \
  --link0 address=10.20.0.12 \
  --link1 address=10.21.0.12

# Auf pve-03
pvecm add 10.20.0.11 \
  --link0 address=10.20.0.13 \
  --link1 address=10.21.0.13

4.4 Verifikation

1
2
3
4
5
6
7
8
pvecm status
#   -> Quorate: Yes | Nodes: 3 | Expected votes: 3

corosync-cfgtool -s
#   -> LINK ID 0 ... enabled connected  UND  LINK ID 1 ... enabled connected

pvecm nodes
#   -> pve-01, pve-02, pve-03 alle mit Status "online"

Die resultierende corosync.conf hat pro Node zwei Ringadressen. Editiere Corosync nach dem Setup nie direkt – und wenn, dann nur ueber /etc/pve/corosync.conf (die vom Cluster synchronisierte Version), mit erhoehtem config_version, und nie bei fehlendem Quorum (sonst zerlegst du den Cluster).

Quorum: Warum ungerade Zahlen zaehlen

Ein Cluster trifft Entscheidungen per Mehrheit. Bei 3 Nodes ist die Mehrheit 2 – faellt ein Node aus, bleiben 2 von 3, Quorum steht, HA laeuft weiter. Bei 2 Nodes ist die Mehrheit 2 – faellt einer aus, ist Quorum weg und beide verweigern den Dienst (bzw. riskieren Split-Brain). Deshalb ist 3 das praktische Minimum.

flowchart LR
    subgraph OK["3 Nodes - 1 faellt aus"]
        direction LR
        Q1["pve-01 OK"]
        Q2["pve-02 OK"]
        Q3["pve-03 DOWN"]
        QV["2/3 = Quorum -> Cluster laeuft"]
    end

Kannst du (etwa aus Kostengruenden) nur 2 vollwertige Nodes stellen, ergaenzt du ein QDevice: ein kleiner externer Host (z. B. eine VM auf einem separaten Standort/Server) mit corosync-qnetd, der als Tie-Breaker eine dritte Stimme liefert:

1
2
3
4
5
# Auf dem QDevice-Host (nicht Teil des Clusters)
apt install corosync-qnetd
# Auf einem Cluster-Node
apt install corosync-qdevice
pvecm qdevice setup 10.40.0.9   # IP des QDevice-Hosts

Schritt 5: Ceph-Storage einrichten

HA bedeutet, dass eine VM nach dem Ausfall ihres Nodes auf einem anderen Node wieder startet. Das geht nur, wenn ihre Disk nicht am ausgefallenen Node haengt. Also brauchst du Shared Storage. Drei enterprise-taugliche Optionen:

Option Wann sinnvoll Air-Gap
Ceph (RBD) Storage soll im Cluster leben, keine externe SAN Voll offline moeglich
iSCSI / FC-SAN Es gibt bereits ein Enterprise-SAN Offline, SAN-abhaengig
NFS Einfacher shared Storage, geringere Anforderungen Offline, NAS-abhaengig

Fuer einen selbsttragenden Cluster ohne externe Abhaengigkeit ist Ceph die Standard-Wahl – es ist in Proxmox integriert und braucht keine Internet-Verbindung.

5.1 Ceph installieren und Netze trennen

Ceph bekommt zwei eigene Netze (Public + Cluster) aus Schritt 1. Das Cluster-Netz traegt die OSD-Replikation (Recovery-Traffic!) und muss vom Public-Netz getrennt sein.

1
2
3
4
5
# a) auf JEDEM Node installieren
pveceph install --repository no-subscription --version reef

# b) NUR auf pve-01 initialisieren, mit getrennten Netzen
pveceph init --network 10.30.0.0/24 --cluster-network 10.31.0.0/24

5.2 Monitore und Manager (auf jedem Node)

MONs bilden ihr eigenes Quorum – deshalb drei (ungerade). MGRs liefern Statistiken/Dashboard; mindestens zwei fuer Redundanz.

1
2
3
# auf pve-01, pve-02, pve-03 jeweils:
pveceph mon create
pveceph mgr create

Verifikation:

1
2
ceph mon stat     # -> 3 mons, quorum pve-01,pve-02,pve-03
ceph -s | grep mgr   # -> active + standbys

5.3 OSDs anlegen (je Datendisk)

Jede Enterprise-SSD/NVMe wird ein OSD. Die Disk muss leer sein (ggf. vorher ceph-volume lvm zap --destroy /dev/nvmeXn1):

1
2
3
# je Datendisk auf JEDEM Node (Beispiel: zwei NVMe pro Node)
pveceph osd create /dev/nvme1n1
pveceph osd create /dev/nvme2n1

Verifikation:

1
2
ceph osd tree
#   -> je Host ein Block mit seinen OSDs, alle "up"

5.4 Pool anlegen und in Proxmox einbinden

1
2
3
4
5
# NUR einmal (auf pve-01): RBD-Pool mit dreifacher Redundanz
pveceph pool create vm-pool --size 3 --min_size 2 --pg_autoscale_mode on

# als Storage fuer VM-Disks/Container registrieren
pvesm add rbd ceph-vm --pool vm-pool --content images,rootdir

size=3 / min_size=2 ist die Standard-Sicherheit: Jeder Block liegt auf 3 OSDs (auf 3 verschiedenen Nodes, dank Default-CRUSH-Regel), und Schreibzugriffe funktionieren noch, solange 2 Kopien erreichbar sind. Faellt ein Node aus, bleiben 2 – die Daten sind sicher und der Cluster schreibt weiter.

5.5 Verifikation (Gesamtzustand)

1
2
3
4
ceph -s
#   -> health: HEALTH_OK | mon: 3 | mgr: active | osd: N up, N in
pvesm status
#   -> ceph-vm  rbd  active
flowchart TB
    VM["VM-Disk (RBD-Image)"] --> P["Pool vm-pool (size=3)"]
    P --> O1["OSD @ pve-01"]
    P --> O2["OSD @ pve-02"]
    P --> O3["OSD @ pve-03"]
    O3 -. "faellt aus" .- X["Recovery re-repliziert<br/>auf verbleibende OSDs"]

CRUSH-Failure-Domain: Die Default-Regel verteilt Kopien pro Host – genau richtig fuer einen 3-Node-Cluster. In groesseren Setups mit mehreren Racks/Reihen setzt du die Failure-Domain auf rack, damit ein kompletter Rack-Ausfall keine Daten kostet. Das ist der Punkt, an dem Ceph im Enterprise glaenzt.


Schritt 6: Hochverfuegbarkeit (HA) konfigurieren

Mit Quorum (Corosync) und Shared Storage (Ceph) sind die Voraussetzungen erfuellt. HA in Proxmox besteht aus zwei Bausteinen: HA-Groups (wo darf eine VM laufen) und Fencing (wie stellt der Cluster sicher, dass ein “toter” Node wirklich tot ist, bevor die VM woanders startet).

Mit Quorum (Schritt 4) und Shared Storage (Schritt 5) sind die Voraussetzungen fuer HA erfuellt.

6.1 Warum Fencing zuerst kommt

Bevor der Cluster eine VM auf einem anderen Node neu startet, muss er sicher sein, dass sie auf dem alten Node nicht mehr laeuft – sonst schreiben zwei Instanzen gleichzeitig auf dieselbe RBD-Disk (Datenkorruption). Proxmox nutzt dafuer einen Watchdog: Verliert ein Node das Quorum, tritt der Watchdog nach ~60 s in Kraft und rebootet den Node hart. Erst danach uebernehmen die anderen.

Ohne funktionierendes Fencing ist HA gefaehrlich, nicht hilfreich. Der Software-softdog ist der Default und funktioniert; ein echter IPMI-/iTCO-Hardware-Watchdog ist im Enterprise aber Pflicht (er greift auch, wenn der Kernel selbst haengt). Und: Teste das Fencing bewusst, nicht erst im echten Ausfall.

6.2 Hardware-Watchdog aktivieren (auf jedem Node)

Statt des Software-softdog den IPMI-Watchdog verwenden:

1
2
3
4
5
6
7
8
9
10
11
12
13
# a) IPMI-Watchdog-Modul laden und persistent machen
echo "ipmi_watchdog" >> /etc/modules
modprobe ipmi_watchdog

# b) Proxmox anweisen, diesen Watchdog zu nutzen
if grep -q '^WATCHDOG_MODULE=' /etc/default/pve-ha-manager; then
  sed -i 's/^WATCHDOG_MODULE=.*/WATCHDOG_MODULE=ipmi_watchdog/' /etc/default/pve-ha-manager
else
  echo 'WATCHDOG_MODULE=ipmi_watchdog' >> /etc/default/pve-ha-manager
fi

# c) Watchdog-Multiplexer neu starten
systemctl restart watchdog-mux

Verifikation:

1
journalctl -u watchdog-mux -b | grep -i watchdog   # -> "Using watchdog 'ipmi_watchdog'"

6.3 HA-Group und Ressource anlegen

Eine HA-Group legt fest, auf welchen Nodes eine VM bevorzugt laeuft (mit Prioritaeten). Beispiel: prod-vms bevorzugt pve-01/02, faellt auf pve-03 zurueck.

1
2
3
4
5
6
7
# HA-Group mit Node-Prioritaeten (hoehere Zahl = bevorzugt)
ha-manager groupadd prod-vms --nodes "pve-01:2,pve-02:2,pve-03:1"

# VM 1001 unter HA stellen
ha-manager add vm:1001 --group prod-vms --state started --max_restart 2 --max_relocate 2

ha-manager status   # -> service vm:1001 (pve-01, started)

6.4 Failover bewusst testen

Der einzige Weg, HA zu vertrauen, ist es auszuloesen. VM 1001 laeuft auf pve-01 – jetzt den Node hart simulieren (echter Test, kein sauberes Shutdown):

1
2
# auf pve-01 einen Kernel-Panic ausloesen (harter Ausfall, Watchdog muss fencen)
echo 1 > /proc/sys/kernel/sysrq && echo c > /proc/sysrq-trigger
1
2
3
# auf pve-02 beobachten:
watch -n2 ha-manager status
#   nach ~1-2 min:  service vm:1001 (pve-02, started)  -> Failover erfolgreich

Erwartung: pve-01 wird gefenced (rebootet), vm:1001 startet automatisch auf pve-02, die RBD-Disk wird dort gemountet. Der Ablauf im Detail:

sequenceDiagram
    participant N1 as pve-01 (VM 1001)
    participant CL as Cluster (Quorum)
    participant WD as Watchdog
    participant N2 as pve-02
    N1->>CL: Heartbeat...
    Note over N1: Node crasht
    N1--xCL: Kein Heartbeat
    CL->>WD: Node ohne Quorum -> fence
    WD->>N1: Hard-Reset (Watchdog)
    CL->>N2: VM 1001 neu starten
    N2->>N2: RBD-Disk mounten, VM boot

Schritt 7: SDN einrichten

In einem grossen Netz willst du VM-Netze zentral im Cluster verwalten, statt auf jedem Node haendisch Bridges und VLANs zu pflegen (genau der Nachteil des Bridge-per-VLAN-Modells aus Schritt 1). Proxmox SDN (“Software Defined Networking”) ist die Antwort: Du beschreibst das Netz einmal deklarativ, der Cluster rollt es auf alle Nodes identisch aus. Ein neuer Node bekommt beim Beitritt automatisch dieselben Netze – ohne dass du eine einzige Zeile interfaces anfasst.

Das mentale Modell: Zone -> VNet -> Subnet (-> Controller)

SDN stapelt vier Objekte. Wer die Rollen einmal sauber trennt, versteht den Rest von selbst:

  • Zone = der Transport: wie kommen Frames von Node zu Node? Per VLAN-Tag auf dem Trunk (vlan), per VXLAN-Overlay (vxlan) oder per EVPN mit BGP-Routing (evpn). Die Zone bindet an das physische Fundament (Trunk-Bridge bzw. Underlay-Peers).
  • VNet = das eigentliche Netz. Es wird auf jedem Node zu einer echten Bridge – der clusterweite Ersatz fuer deine vmbr30/vmbr45. Hier haengst du die VM-NICs ein.
  • Subnet = die IP-Ebene: CIDR, Gateway, IPAM (Adressvergabe) und optional DHCP. Ohne Subnet ist ein VNet reines L2.
  • Controller = nur bei EVPN: der BGP-Sprecher (FRR), der Routen und MAC-Adressen zwischen den Nodes verteilt.
flowchart TB
    Z["Zone corpvlan<br/>Typ VLAN · Trunk vmbr0"] --> V["VNet prod100<br/>Tag 100 = Bridge je Node"]
    V --> S["Subnet 172.16.100.0/24<br/>Gateway .1 · IPAM pve"]
    S --> APPLY(["pvesh set /cluster/sdn<br/>= Apply"])
    APPLY --> N1["pve-01<br/>Bridge prod100"]
    APPLY --> N2["pve-02<br/>Bridge prod100"]
    APPLY --> N3["pve-03<br/>Bridge prod100"]

    classDef def fill:#1f2a37,stroke:#e57000,stroke-width:2px,color:#fff;
    classDef node fill:#0d1117,stroke:#8aa0b6,stroke-width:1.5px,color:#fff;
    class Z,V,S def;
    class N1,N2,N3 node;

Wo das landet: Alle Definitionen liegen als Textdateien unter /etc/pve/sdn/*.cfg (zones.cfg, vnets.cfg, subnets.cfg, controllers.cfg). Weil /etc/pve das Cluster-Dateisystem (pmxcfs) ist, sind sie sofort auf jedem Node vorhanden – du editierst nie eine Node-lokale Datei.

Pending vs. Applied: Neue/geaenderte Objekte sind zunaechst pending (nur vorgemerkt). Erst pvesh set /cluster/sdn (GUI: Datacenter -> SDN -> Apply) generiert daraus die echte Node-Konfiguration und laedt das Netzwerk neu. So aenderst du gefahrlos mehrere Objekte und rollst sie atomar aus.

Zone-Typen und wann welcher

Zone-Typ Prinzip Braucht Wann
VLAN 802.1Q-Tags auf dem vorhandenen Trunk VLAN-aware Trunk-Bridge Es gibt bereits eine VLAN-Fabric (haeufigster Enterprise-Fall)
VXLAN L2-Overlay ueber ein beliebiges L3-Underlay Erreichbarkeit der Node-IPs (Peers) Viele isolierte Netze, mandantenfaehig, ohne die Fabric anzufassen
EVPN VXLAN + BGP-Controller, integriertes L3-Routing Controller (FRR) + Exit-Nodes Grosse, dynamische Umgebungen mit Routing/Anycast-Gateway im Cluster

Faustregel: An eine bestehende Unternehmens-VLAN-Fabric koppelst du mit einer VLAN-Zone. Willst du Netze im Cluster erzeugen, ohne jedes Mal Switch-Ports/VLANs zu beantragen, nimmst du VXLAN; brauchst du zusaetzlich geroutete Segmente mit lokalem Gateway auf jedem Node, EVPN.

7.1 VLAN-Zone (an bestehende Fabric andocken)

Der Standardfall. Voraussetzung: vmbr0 ist eine VLAN-aware Trunk-Bridge (Schritt 1, Variante B). Alle Befehle laufen einmal auf einem beliebigen Node:

1
2
3
4
5
6
7
8
9
10
11
12
# a) Zone an den Trunk binden
pvesh create /cluster/sdn/zones --zone corpvlan --type vlan --bridge vmbr0

# b) VNet = VLAN 100 (wird auf jedem Node zur Bridge "prod100")
pvesh create /cluster/sdn/vnets --vnet prod100 --zone corpvlan --tag 100

# c) Subnet mit integriertem IPAM (kein Cloud-Dienst noetig)
pvesh create /cluster/sdn/subnets --subnet 172.16.100.0/24 \
  --vnet prod100 --gateway 172.16.100.1 --snat 0

# d) Ausrollen
pvesh set /cluster/sdn

GUI-Weg: Datacenter -> SDN -> Zones -> Add -> VLAN, dann VNets -> Add und Subnets -> Add, zum Schluss oben Apply. CLI und GUI schreiben dieselben .cfg-Dateien – du kannst sie beliebig mischen.

7.2 VXLAN-Zone (Overlay ohne Fabric-Eingriff)

Ein VXLAN-Overlay spannt L2-Netze ueber ein beliebiges geroutetes Underlay – die physische Fabric muss die VM-VLANs gar nicht kennen, sie transportiert nur die Node-zu-Node-IP-Pakete. Als “Peers” gibst du die (Underlay-)IPs aller Cluster-Nodes an:

1
2
3
4
5
6
7
8
# Zone kennt alle Node-IPs des Underlays
pvesh create /cluster/sdn/zones --zone overlay --type vxlan \
  --peers 10.10.0.11,10.10.0.12,10.10.0.13

# VNet: die Zahl ist hier die VXLAN-VNI, nicht ein VLAN-Tag
pvesh create /cluster/sdn/vnets --vnet tenant-a --zone overlay --tag 100000

pvesh set /cluster/sdn

MTU-Falle: VXLAN kostet 50 Byte Overhead. Das Underlay muss also mindestens VNet-MTU + 50 koennen. Praxis: Underlay auf MTU 9000 (Jumbo, wie das Ceph-Netz), VNet auf 1500 – dann hast du reichlich Luft. Vergisst du das, brechen genau die grossen Pakete weg (SSH tippt noch, scp haengt).

7.3 EVPN-Zone (geroutetes Overlay mit lokalem Gateway)

EVPN ergaenzt VXLAN um einen BGP-Controller (FRR auf den Nodes) und um integriertes Routing: Das Subnet-Gateway existiert als Anycast-Adresse auf jedem Node – eine VM nutzt immer ihr lokales Gateway, egal auf welchem Node sie gerade laeuft (ideal fuer Live-Migration). Zwei Objekte mehr als bei VLAN:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1) Controller: der BGP-Sprecher (eigene private ASN)
pvesh create /cluster/sdn/controllers --controller evpnctl --type evpn \
  --asn 65000 --peers 10.10.0.11,10.10.0.12,10.10.0.13

# 2) EVPN-Zone: nutzt den Controller, eigene VRF-VNI, Exit-Nodes fuers Raus-Routing
pvesh create /cluster/sdn/zones --zone evpn0 --type evpn \
  --controller evpnctl --vrf-vxlan 10000 \
  --exitnodes pve-01,pve-02 --mtu 8950

# 3) VNet + Subnet (Gateway wird zum Anycast-GW auf allen Nodes)
pvesh create /cluster/sdn/vnets --vnet app-net --zone evpn0 --tag 11000
pvesh create /cluster/sdn/subnets --subnet 10.100.0.0/24 \
  --vnet app-net --gateway 10.100.0.1

pvesh set /cluster/sdn

Die Exit-Nodes sind die Nodes, ueber die Traffic aus dem Overlay in den Rest des (Air-Gap-)Netzes geroutet wird – zwei fuer Redundanz. Die BGP-Ebene verifizierst du auf einem Exit-Node ueber FRR:

1
vtysh -c "show bgp l2vpn evpn summary"   # sind die Nachbarn "Established"?

7.4 Subnets, IPAM und optional DHCP

Das Subnet macht aus dem reinen L2-VNet ein IP-Netz:

  • Gateway – bei VLAN/VXLAN dein vorhandener Fabric-Router, bei EVPN das Anycast-GW auf jedem Node.
  • IPAM – Proxmox bringt einen eingebauten IP-Address-Manager mit (pve), der belegte Adressen clusterweit verwaltet. Im Air-Gap reicht das voellig; externe IPAM (NetBox, phpIPAM) sind optional anbindbar.
  • SNAT--snat 1 NATtet das Subnet hinter der Node-IP (fuer Overlays ohne eigene Route sinnvoll); an einer normalen VLAN-Fabric bleibt es 0.
  • DHCP (optional) – SDN kann per dnsmasq automatisch Adressen an VMs vergeben. Dazu die Zone auf DHCP stellen und dem Subnet einen Bereich geben:
1
2
3
4
pvesh set /cluster/sdn/zones/corpvlan --dhcp dnsmasq
pvesh set /cluster/sdn/subnets/corpvlan-172.16.100.0-24 \
  --dhcp-range start-address=172.16.100.100,end-address=172.16.100.200
pvesh set /cluster/sdn

Der Subnet-Bezeichner in der API ist <zone>-<cidr-mit-bindestrichen> (siehe pvesh get /cluster/sdn/subnets). Unsicher bei einem Flag? pvesh help /cluster/sdn/subnets create -v listet alle Optionen der jeweiligen Version.

7.5 Verifizieren und Fehlersuche

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1) Pending-Config sauber angewandt? (idempotent, zeigt Fehler sofort)
pvesh set /cluster/sdn

# 2) VNet ist auf JEDEM Node als Bridge da?
for n in pve-01 pve-02 pve-03; do
  echo "== $n =="; ssh $n "ip -br link show app-net"
done

# 3) Objekte im Ueberblick
pvesh get /cluster/sdn/vnets
pvesh get /cluster/sdn/zones

# 4) DHCP laeuft? (eine dnsmasq-Instanz je Zone)
systemctl status dnsmasq@corpvlan

Taucht ein VNet auf einem Node nicht als Bridge auf, hat dort meist der Reload geklemmt – ifreload -a auf dem betroffenen Node ausfuehren und die Logs mit journalctl -u pvedaemon -e pruefen.

7.6 Migration: von Bridge-per-VLAN zu SDN-VNets

Genau der Umstieg, den viele aus dem Homelab mitbringen (vmbr30, vmbr45, …). Er geht unterbrechungsarm, weil das VNet dieselben VLAN-Tags nutzt wie deine alten Bridges:

  1. VLAN-Zone auf dem Trunk anlegen (7.1).
  2. Pro altem vmbrXX ein VNet mit gleichem Tag – und, wenn du magst, gleichem sprechenden Namen: vmbr30 wird zu VNet vnet30 --tag 30. So bleibt deine Nomenklatur erhalten, jetzt aber clusterweit.
  3. pvesh set /cluster/sdn – die VNets erscheinen als Bridges auf allen Nodes.
  4. VMs einzeln nacheinander von vmbrXX auf das passende vnetXX umhaengen (NIC editieren; kein Neustart noetig, nur ein kurzer Link-Flap).
  5. Sind alle VMs umgezogen, die alten vmbrXX/enoX.XX-Eintraege aus /etc/network/interfaces entfernen.

Ergebnis: Deine lesbare …30/…45-Konvention ueberlebt – nur ist der Name jetzt ein VNet, das auf jedem Node automatisch existiert, bei der Live-Migration mitreist und sich zentral pflegen laesst. Genau der Punkt aus Schritt 1.

Air-Gap-Hinweis: Der integrierte IPAM reicht im geschlossenen Netz voellig; kein externer Cloud-IPAM noetig. Auch der EVPN-Controller (FRR) laeuft komplett offline. SDN ist einer der Bereiche, in denen ein Air-Gap gar keine Einschraenkung ist.


Schritt 8: Zertifikate mit interner PKI

Die Proxmox-Weboberflaeche laeuft per Default mit einem selbstsignierten Zertifikat – jeder Browser meckert. In Produktion willst du Zertifikate deiner internen CA, denen alle Firmen-Clients vertrauen. Ohne Internet faellt ACME/Let’s-Encrypt weg – aber Proxmox’ ACME-Client kann gegen jeden ACME-Server sprechen, auch einen internen.

Voraussetzung: Die Root-CA ist bereits im Trust-Store (Schritt 2.4).

8.1 Variante A: Manuell mit interner CA

Fuer eine klassische interne CA (AD Certificate Services oder OpenSSL-CA). Moderne Browser brauchen Subject Alternative Names (SAN) – deshalb eine CSR-Config statt nur -subj:

1
2
3
4
5
6
7
8
9
10
11
12
13
# pve-01-csr.cnf
[req]
distinguished_name = dn
req_extensions     = v3_req
prompt             = no
[dn]
CN = pve-01.corp.local
O  = Corp
[v3_req]
subjectAltName = @san
[san]
DNS.1 = pve-01.corp.local
IP.1  = 10.10.0.11
1
2
3
4
5
6
7
8
9
# a) Key + CSR erzeugen
openssl req -new -newkey rsa:2048 -nodes \
  -keyout pve-01.key -out pve-01.csr -config pve-01-csr.cnf

# b) pve-01.csr von der internen CA signieren lassen -> pve-01.crt

# c) Zertifikat + Key einspielen und pveproxy neu starten
pvenode cert set pve-01.crt pve-01.key --force
systemctl restart pveproxy

Verifikation:

1
2
3
echo | openssl s_client -connect pve-01.corp.local:8006 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates
#   -> issuer = deine interne CA, subject = CN=pve-01.corp.local, gueltiges Ablaufdatum

8.2 Variante B: Automatisiert mit internem ACME-Server (empfohlen)

Manuelles Signieren skaliert nicht und laeuft ab. Eleganter: ein interner ACME-Server wie step-ca (smallstep) – Proxmox erneuert die Zertifikate dann selbst, wie bei Let’s-Encrypt, nur ohne Internet.

1
2
3
4
5
6
7
8
9
# a) Internen ACME-Account gegen step-ca registrieren (auf jedem Node)
pvenode acme account register default [email protected] \
  --directory https://step-ca.corp.local/acme/acme/directory

# b) Domain am Node hinterlegen und Zertifikat anfordern
pvenode config set --acme domains=pve-01.corp.local
pvenode acme cert order

# c) automatische Erneuerung ist per systemd-Timer bereits aktiv (pve-daily-update)

Verifikation:

1
pvenode acme cert order --force   # erzwingt eine Erneuerung als Test -> "Success"
flowchart LR
    N["pve-01 (ACME-Client)"] -->|"order + challenge"| S["step-ca<br/>(interner ACME-Server)"]
    S -->|"validiert HTTP-01/DNS-01"| N
    S -->|"signiertes Cert"| N
    ROOT["step-ca Root-CA"] -->|"im Trust-Store"| CLIENTS["alle Firmen-Clients"]

Best Practice: Root-CA offline und gut geschuetzt halten, nur eine Intermediate-CA signiert im Betrieb. Kurze Zertifikats-Laufzeiten (z. B. 90 Tage) sind mit automatischer Erneuerung kein Problem – und genau deshalb ist der interne ACME-Weg dem manuellen vorzuziehen.


Schritt 9: User, Rollen und Rechte

Im Unternehmen meldet sich niemand mit dem root-Account an. Proxmox bringt ein feingranulares Rechtemodell mit: Realms (woher kommen die User), Roles (was darf man), ACLs (worauf, mit welcher Rolle) und Pools (Gruppen von VMs/Storage).

9.1 AD-Realm anbinden

Statt lokaler PVE-User bindest du das Active Directory an – zentrale Identitaeten, zentrale Sperrung:

1
2
3
pveum realm add corp-ad --type ad --domain corp.local \
  --server1 dc1.corp.local --server2 dc2.corp.local --secure 1 --default 0
pveum realm sync corp-ad

Verifikation: pveum user list | grep @corp-ad zeigt die synchronisierten User.

9.2 Rollen – least privilege

Vergib nie mehr als noetig. Praxisnahe Zuschnitte:

Rolle Rechte Fuer wen
PVEVMUser (eingebaut) VM starten/stoppen/Konsole, kein Anlegen/Loeschen 1st-Level / App-Teams
BackupOperator (custom) VM.Backup, Datastore.AllocateSpace, VM.Audit Backup-Team
Auditor (custom) nur *.Audit Revision / Security
PVEAdmin (eingebaut) fast alles ausser Realm-/Cluster-Kern Plattform-Team

9.3 Komplettes Rechte-Setup (copy&paste)

Realm ist gesetzt – jetzt Pools, Custom-Rollen, Rechte und ein Service-Token in einem Rutsch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# a) Pools (fachliche Buendel; Rechte gibst du auf den Pool, nicht die Einzel-VM)
pveum pool add prod --comment "Produktions-VMs"
pveum pool add dev  --comment "Entwicklung"

# b) Custom-Rollen
pveum role add BackupOperator --privs "VM.Backup VM.Audit Datastore.AllocateSpace Datastore.Audit"
pveum role add Auditor        --privs "VM.Audit Datastore.Audit Sys.Audit Pool.Audit"

# c) Rechte vergeben (Gruppe -> Rolle -> Pfad; jeweils so eng wie moeglich)
pveum acl modify /pool/prod --group "app-team@corp-ad"    --role PVEVMUser
pveum acl modify /pool/prod --group "backup-team@corp-ad" --role BackupOperator
pveum acl modify /         --group "auditors@corp-ad"    --role Auditor --propagate 1

# d) API-Token fuer Automatisierung (Ansible/Backup) mit Privilege Separation
pveum user token add svc-backup@pve automation --privsep 1
#    -> gibt EINMALIG den Secret aus: sicher wegspeichern!
pveum acl modify /pool/prod --tokens "svc-backup@pve!automation" --role BackupOperator

9.4 2FA erzwingen

TOTP fuer alle administrativen Accounts aktivieren – funktioniert offline (kein SMS-Gateway noetig). Entweder pro User (GUI: Datacenter → Permissions → Two Factor) oder realm-weit als Pflicht:

1
pveum realm modify corp-ad --tfa type=totp   # TOTP fuer diesen Realm verpflichtend

Verifikation:

1
2
pveum acl list                                   # alle Zuweisungen auf einen Blick
pveum user permissions app-team@corp-ad          # effektive Rechte eines Users pruefen
flowchart TB
    U["AD-User (corp-ad)"] --> G["AD-Gruppe backup-team"]
    G -->|"ACL: Rolle BackupOperator"| POOL["/pool/prod"]
    POOL --> V1["VM 1001"]
    POOL --> V2["VM 1002"]
    T["API-Token svc-backup (privsep)"] -->|"nur VM.Backup"| POOL

Schritt 10: Backup mit Proxmox Backup Server

Snapshots auf demselben Ceph sind kein Backup – faellt der Cluster, ist auch der Snapshot weg. Das Backup gehoert auf einen getrennten Proxmox Backup Server (PBS), idealerweise auf eigener Hardware und in einem anderen Brandabschnitt.

PBS speichert dedupliziert und inkrementell (nur geaenderte Bloecke per Dirty-Bitmap) – das macht taegliche Vollsicherungen bezahlbar. Der Datenfluss:

flowchart LR
    subgraph CL["Proxmox-Cluster"]
        V["VMs (RBD)"]
    end
    V -->|"inkrementell, dedupliziert, verschluesselt"| DS["PBS Datastore<br/>(Namespaces: prod / dev)"]
    DS -->|"Verify (Pruefsummen)"| DS
    DS -->|"Sync / Tape"| OFF["Offsite / Wechseldatentraeger"]

10.1 Datastore und Namespace (auf dem PBS)

1
2
proxmox-backup-manager datastore create main /mnt/datastore/main
proxmox-backup-manager namespace create main prod

10.2 Backup-User und Token (auf dem PBS)

Der Cluster meldet sich mit einem Token (nicht mit einem Passwort) an, mit Rechten nur auf diesen Namespace:

1
2
3
4
5
proxmox-backup-manager user create backup@pbs --password 'CHANGE-ME'
proxmox-backup-manager user generate-token backup@pbs cluster
#   -> gibt das Token-Secret EINMALIG aus: sicher wegspeichern!
proxmox-backup-manager acl update /datastore/main/prod DatastoreBackup \
  --auth-id 'backup@pbs!cluster'

10.3 PBS am Cluster einbinden (mit Verschluesselung)

1
2
3
4
5
6
7
8
# Fingerprint auf dem PBS holen:  proxmox-backup-manager cert info | grep Fingerprint
# dann auf dem Cluster (einmal):
pvesm add pbs pbs-main --server pbs.corp.local --datastore main --namespace prod \
  --fingerprint "AA:BB:CC:DD:..." --username 'backup@pbs!cluster' --password 'TOKEN-SECRET'

# Client-seitige Verschluesselung aktivieren (Key wird auf dem Cluster erzeugt)
pvesm set pbs-main --encryption-key autogen
#   -> Key unter /etc/pve/priv/storage/pbs-main.enc  -> SEPARAT sichern (Tresor/Ausdruck)!

10.4 Backup-, Prune- und Verify-Job planen

1
2
3
4
5
6
7
8
9
10
# a) taeglicher Backup-Job fuer den Pool prod (auf dem Cluster)
pvesh create /cluster/backup --storage pbs-main --pool prod \
  --schedule "02:00" --mode snapshot --enabled 1

# b) Aufbewahrung/Prune (auf dem PBS): 7 taeglich, 4 woechentlich, 6 monatlich
proxmox-backup-manager prune-job create daily-prune --store main \
  --schedule "04:00" --keep-daily 7 --keep-weekly 4 --keep-monthly 6

# c) woechentlicher Verify-Job (auf dem PBS) - prueft die Pruefsummen
proxmox-backup-manager verify-job create weekly-verify --store main --schedule "sun 05:00"

10.5 Restore testen (Pflicht, nicht Kuer)

1
2
3
4
5
# verfuegbare Snapshots auflisten (auf einem Node)
pvesm list pbs-main

# Test-Restore unter NEUER VM-ID (Original nicht ueberschreiben)
qmrestore pbs-main:backup/vm/1001/2026-07-17T02:00:00Z 9999 --storage ceph-vm

Restore testen, nicht hoffen. Ein Backup gilt erst als funktionierend, wenn du es zurueckgespielt hast – plane regelmaessige Test-Restores (ganze VM + einzelne Datei per File-Level-Restore). Im Air-Gap ist der Offsite-Weg meist ein Wechseldatentraeger oder Tape statt Cloud-Sync; auch dieser Prozess gehoert getestet. Und: Den Encryption-Key getrennt sichern – ohne ihn ist auch dein Backup wertlos.

vzdump als Fallback

Fuer einzelne Nodes oder ohne PBS gibt es weiterhin vzdump (Voll-Backups als Archiv auf NFS/lokal). Im Cluster-Kontext ist PBS aber wegen Deduplizierung, Inkrementen und Verify klar ueberlegen.


Schritt 11: Betrieb, Monitoring und Updates

11.1 Metriken an einen internen Server pushen

Proxmox schiebt Metriken an einen externen Metric-Server (InfluxDB/Graphite), Visualisierung per Grafana – alles intern, keine Cloud. Einmal auf dem Cluster konfiguriert, senden alle Nodes:

1
2
3
4
5
6
7
8
9
# InfluxDB v1 per UDP (einfachster Weg im internen Netz)
pvesh create /cluster/metrics/server/influx \
  --type influxdb --server metrics.corp.local --port 8089 \
  --influxdbproto udp --enable 1

# InfluxDB v2 per HTTPS + Token (Alternative):
# pvesh create /cluster/metrics/server/influx --type influxdb \
#   --server metrics.corp.local --port 8086 --influxdbproto https \
#   --organization corp --bucket proxmox --token 'INFLUX-TOKEN' --enable 1

Verifikation:

1
2
pvesh get /cluster/metrics/server        # -> influx, enable 1
journalctl -u pvestatd -n 20 | grep -i metric   # -> keine Sendefehler

11.2 Benachrichtigungen (internes SMTP oder Gotify)

Der Cluster meldet Backup-/HA-/Ceph-/Update-Ereignisse. Ziel und Filter (Matcher) werden zentral gesetzt – hier ein internes Mail-Relay:

1
2
3
4
5
6
7
8
9
10
11
12
# a) SMTP-Ziel (internes Mail-Relay im geschlossenen Netz)
pvesh create /cluster/notifications/endpoints/smtp/corp-mail \
  --server mail.corp.local --mode starttls \
  --from-address [email protected] --mailto [email protected]

# b) Matcher: welche Ereignisse an dieses Ziel gehen (ab Warnung)
pvesh create /cluster/notifications/matchers/warn-to-mail \
  --target corp-mail --match-severity warning,error

# Alternative Gotify (statt SMTP):
# pvesh create /cluster/notifications/endpoints/gotify/corp-gotify \
#   --server https://gotify.corp.local --token 'GOTIFY-TOKEN'

Verifikation:

1
2
pvesh get /cluster/notifications/targets   # -> corp-mail gelistet
# Test-Benachrichtigung ausloesen (Datacenter -> Notifications -> Test in der GUI)

11.3 Rolling Updates (ohne Downtime)

Updates laufen ueber den internen Mirror – ein Node nach dem anderen: VMs per Live-Migration wegschieben, updaten, rebooten, zurueck, dann der naechste:

1
2
3
4
5
# pro Node, nacheinander:
ha-manager crm-command node-maintenance enable pve-01   # VMs wandern weg
apt update && apt full-upgrade -y && reboot
# nach dem Boot:
ha-manager crm-command node-maintenance disable pve-01

Verifikation nach jedem Node:

1
2
pvecm status | grep Quorate     # -> Yes (Quorum die ganze Zeit erhalten)
ceph -s | grep health           # -> HEALTH_OK, bevor der naechste Node drankommt

Mehrere Umgebungen: Test, Staging, Produktion (und Schulung)

Sobald Proxmox nicht mehr “ein Server”, sondern eine Plattform ist, brauchst du getrennte Umgebungen. Der Grund ist immer derselbe: Du willst jede Aenderung – ein Ceph-Upgrade, einen neuen Kernel, eine geaenderte Firewall-Regel – erst woanders ausprobieren, bevor sie die Produktion trifft. Im Air-Gap wiegt das doppelt: Du kannst nicht eben schnell in einer Cloud nachbauen, um etwas zu testen.

Warum trennen? Der Blast Radius

Der entscheidende Begriff ist Blast Radius – wie weit reicht der Schaden, wenn etwas schiefgeht. In einem Cluster teilen sich alle VMs dieselbe Corosync-Domain, dasselbe Ceph und dieselbe PVE-Version. Ein misslungenes clusterweites Upgrade oder ein Ceph-Problem trifft dann alles gleichzeitig – Test wie Produktion. Getrennte Umgebungen begrenzen genau diesen Radius.

Dazu kommen drei praktische Treiber: unterschiedliche Lebenszyklen (Test wird staendig umgebaut, Prod bleibt stabil), unterschiedliche SLAs (ein Test-Ausfall stoert niemanden, ein Prod-Ausfall schon) und saubere Rechtetrennung (Entwickler duerfen in Test viel, in Prod nichts).

Zwei Wege der Trennung – mit klarem Trade-off

  Getrennte Cluster Ein Cluster + Segmentierung
Isolation Stark (eigene Quorum-/HA-/Ceph-Domain) Schwach (geteilte Failure-Domain)
Upgrade-Risiko pro Umgebung isoliert trifft potenziell alles
Hardware-Bedarf hoeher (min. 3 Nodes je Cluster) niedrig (ein Cluster)
Betriebstrennung physisch/logisch komplett per Pools, SDN-Zones, RBAC
Empfohlen fuer Prod vs. Rest Dev/Staging untereinander

Best Practice im Enterprise: Die Produktion bekommt einen eigenen Cluster – harte Trennung, eigenes Quorum, eigenes Ceph, eigener Upgrade-Zyklus. Test und Staging koennen sich einen zweiten, kleineren Cluster teilen und dort per Pool + SDN-Zone + Rechten getrennt werden. So bezahlst du nicht dreimal drei Nodes, schuetzt aber die Produktion kompromisslos.

flowchart TB
    subgraph SHARED["Zentrale Dienste - einmal fuer alle Umgebungen"]
        MIR["Paket-Mirror"]
        CA["Interne CA / ACME"]
        AD["AD / LDAP"]
        ND["NTP + DNS"]
        PBS["PBS - Namespaces je Umgebung"]
    end
    subgraph PROD["Cluster PROD (eigenes Quorum + Ceph)"]
        P1["pve-prod-01..03"]
    end
    subgraph NONPROD["Cluster NONPROD (geteilt)"]
        T1["Test - Pool/SDN/RBAC"]
        S1["Staging - Pool/SDN/RBAC"]
    end
    SHARED --> PROD
    SHARED --> NONPROD

Was geteilt wird – und was nicht

Die zentralen Air-Gap-Dienste (Mirror, CA, NTP, DNS, AD) betreibst du einmal und bedienst damit alle Cluster – alles andere waere doppelte Pflegearbeit. Getrennt bleibt dagegen konsequent alles, was den Blast Radius bestimmt:

  • Corosync/Ceph: pro Cluster eigen – und mit eigenen IP-Segmenten, sonst kollidieren Ring- und Storage-Netze zweier Cluster. Also z. B. Prod-Ring 0 auf 10.20.0.0/24, Nonprod-Ring 0 auf 10.25.0.0/24.
  • PBS: ein PBS reicht, aber je Umgebung ein Namespace (prod, staging, test) mit eigener Retention und eigenen Zugriffsrechten. Fuer harte Trennung (oder getrennte Aufbewahrungspflichten) einen separaten PBS fuer Prod.
  • Rechte: eigene AD-Gruppen und Rollen je Umgebung (prod-admins, test-users), damit niemand versehentlich in der falschen Umgebung landet.

Der Promotion-Pfad

Getrennte Umgebungen entfalten ihren Wert erst mit einem klaren Promotion-Pfad: Neues wird erst in Test verprobt, dann in Staging unter prod-aehnlichen Bedingungen bestaetigt, dann kontrolliert in Prod ausgerollt.

flowchart LR
    T["TEST<br/>PVE/Ceph-Upgrades,<br/>neue VM-Templates"] --> S["STAGING<br/>prod-nah bestaetigen"]
    S --> P["PROD<br/>kontrolliert ausrollen"]

Im Air-Gap heisst das konkret: Ein neues Paket landet zuerst im internen Mirror, wird auf dem Test-Cluster eingespielt (rolling), laeuft ein paar Tage, wandert nach Staging – und erst dann faehrst du das Prod-Update. So wird “wir haben das Ceph-Upgrade schon dreimal gemacht, bevor es Prod erreicht” vom Wunsch zum Prozess. Genau dafuer ist es sinnvoll, den Test-Cluster bewusst eine Version vor Produktion laufen zu lassen.

Schulung: die Wegwerf-Umgebung

Fuer Schulungen und Trainings willst du eine Umgebung, die man absichtlich kaputtmachen und zuruecksetzen kann, ohne dass es irgendjemanden juckt. Zwei praktikable Wege:

  • Nested Virtualization: ganze Proxmox-Nodes als VMs auf einem Trainings-Host (PVE-in-PVE). Teilnehmer bauen darin ihren eigenen “Cluster” – inklusive Corosync und Ceph im Kleinen – und nach dem Kurs wirft man die VMs einfach weg und spielt das Template neu aus. Kostet keine Extra-Hardware und ist ideal fuer ein Air-Gap-Lab.
  • Kleiner dedizierter Cluster aus ausgemusterter Hardware, der per Snapshot/Template auf einen bekannten Startzustand zurueckgesetzt wird.

Wichtig: Die Schulungs-Umgebung darf an denselben zentralen Diensten haengen (Mirror etc.), bekommt aber strikt getrennte Rechte und IP-Bereiche. Ein uebermuetiger Kursteilnehmer darf niemals aus Versehen die Produktion erreichen – genau deshalb ist die harte Netz- und Rechtetrennung hier keine Kuer, sondern Pflicht.


Best-Practice-Checkliste

Bevor der Cluster in Produktion geht – die Punkte, die im Ernstfall den Unterschied machen:

  • 3+ Nodes (ungerade) oder 2 Nodes + QDevice
  • Corosync auf dedizierten NICs, zwei Ringe auf getrennten Switches
  • Storage-Netz getrennt (Ceph Public + Cluster), MTU 9000 durchgaengig
  • Ceph size=3 / min_size=2, CRUSH-Failure-Domain passend (host/rack)
  • Hardware-Watchdog aktiv und Fencing getestet
  • Shared Storage vorhanden (sonst kein HA)
  • Interne CA vertraut auf allen Nodes + Clients, Zertifikate automatisiert erneuert
  • AD-Realm + least privilege, root nur im Notfall, 2FA fuer Admins
  • PBS getrennt, Retention + Verify + Encryption, Restore getestet
  • Interner Mirror/NTP/DNS stehen und werden gepflegt
  • Rolling-Update-Prozess dokumentiert und geuebt
  • Umgebungen getrennt: Prod als eigener Cluster, eigene IP-Segmente je Cluster, PBS-Namespace + Rechte je Umgebung
  • Out-of-Band-Management (IPMI/iDRAC/iLO) im eigenen Segment, fuers Fencing und als Air-Gap-“Remote Hands”

Fazit

Ein produktiver Proxmox-Cluster im Unternehmensnetz ist kein “grosses Homelab”, sondern eine Kette abhaengiger Entscheidungen: Ohne getrennte Netze kein stabiles Corosync, ohne Corosync kein Quorum, ohne Quorum kein HA, ohne Shared Storage kein Recovery, ohne Fencing kein sicheres Recovery, ohne getrenntes Backup keine Wiederherstellung. Jede Saeule traegt die naechste.

Der Air-Gap aendert daran erstaunlich wenig – er verschiebt nur die Quelle: interner Mirror statt Internet-Repo, interne CA statt Let’s-Encrypt, interner NTP/DNS statt Cloud, Wechseldatentraeger statt Offsite-Sync. Wer diese vier internen Dienste sauber aufsetzt, betreibt einen Cluster, der genauso aktuell, sicher und hochverfuegbar ist wie ein angebundener – nur eben unabhaengig vom Internet.

Aspekt Heim-Setup Enterprise / Air-Gap
Nodes 1–2 3+ (oder 2 + QDevice)
Netz 1 NIC, flaches /24 4–6 NICs, getrennte VLANs, Jumbo-Storage
Storage lokal (ZFS) Ceph / SAN (shared, Pflicht fuer HA)
Zertifikate selbstsigniert / Let’s-Encrypt interne CA / interner ACME
Auth lokale User AD-Realm + least privilege + 2FA
Backup vzdump auf NAS PBS getrennt, Verify + Encryption + Offsite
Updates direkt aus dem Netz interner Mirror, Rolling Update

Damit steht das Fundament. Aufsetzen kannst du darauf beliebig – von der einzelnen Test-VM bis zur mandantenfaehigen Plattform mit SDN-Overlays.

Dieser Eintrag ist vom Autor unter CC BY 4.0 lizensiert.