Die machine-id-Falle: Proxmox Cloud-Init Templates, die wirklich klonbar sind

Du hast ein Tutorial befolgt. Debian-Cloud-Image gezogen, mit virt-customize den qemu-guest-agent reingebacken, brav --truncate /etc/machine-id aufgerufen, das Image in ein Proxmox-Template konvertiert. Dann drei VMs daraus geklont — und zwei hängen sich am DHCP. Im DHCP-Log: zwei Leases mit derselben Client-ID. Im Gast: identische /etc/machine-id.

Aber du hast doch gerade ein truncate gemacht?

Das Symptom

Cloud-Init-Templates auf Proxmox sind simpel: VM-Image mit cloud-init drin, via qm template in Klon-Modus, pro Deployment qm clone. Funktioniert — bis du mehr als eine VM klonst und Lease-Konflikte siehst:

  • Zwei Klone bekommen vom DHCP-Server dieselbe IP.
  • systemd-networkd schreibt im Journal: DUID generated from machine-id.
  • cat /etc/machine-id liefert auf jedem Klon denselben Hex-String.

Alle Klone haben dieselbe machine-id geerbt. systemd-networkd leitet daraus die DHCP-Client-DUID ab. Aus DHCP-Sicht: alle Klone sind dieselbe Maschine.

Die naheliegende Lösung, die nicht funktioniert

Jedes zweite Tutorial schreibt:

virt-customize -a $IMG --truncate /etc/machine-id

Sieht plausibel aus. Läuft fehlerfrei durch. Datei ist nach dem Befehl leer. Alles gut? Nein. Beim nächsten Boot ist die Datei wieder gefüllt — und beim nächsten virt-customize-Aufruf auf demselben Image ebenfalls.

Warum: das libguestfs-Verhalten

In den Verbose-Logs steht eine unscheinbare Zeile:

[ 1.6] Setting the machine ID in /etc/machine-id

virt-customize schreibt am Anfang jeder Operation einen Zufallswert in /etc/machine-id. Hartcodiert in libguestfs — aus einem Grund, der mit Proxmox nichts zu tun hat: Fedora-Kernel-Postinstall-Scripts erwarten eine gesetzte machine-id, sonst bleibt der Kernel halb installiert. RHEL-Bug #1554546, seit 2018 WONTFIX.

Wer --truncate /etc/machine-id aufruft, gewinnt nichts — virt-customize schreibt sie unmittelbar danach wieder.

Der Fix: virt-sysprep, nicht virt-customize

virt-sysprep ist genau für diesen Zweck gebaut und respektiert das machine-id-Reset. Als letzter Schritt nach allen virt-customize-Aufrufen:

virt-sysprep -a $IMG --operations 
  machine-id,bash-history,logfiles,tmp-files,net-hostname,net-hwaddr,
ssh-hostkeys,ssh-userdir,dhcp-client-state,package-manager-cache
  • machine-id — Datei wird geleert, systemd generiert beim Boot neu.
  • ssh-hostkeys — sonst hat jeder Klon dieselbe Server-Identität.
  • net-hwaddr — leert persistente Mappings, damit systemd nicht die alte MAC zuweist.
  • dhcp-client-state — alte Leases raus.

Wichtig: erst virt-customize (Pakete installieren), dann virt-sysprep. Andersrum sinnlos.

Belt-and-suspenders zusätzlich ein Firstboot-Hook, falls jemand später nochmal virt-customize anwirft:

virt-customize -a $IMG --firstboot-command 
  'rm -f /etc/machine-id /var/lib/dbus/machine-id && 
   systemd-machine-id-setup && 
   systemctl restart systemd-networkd 2>/dev/null || true'

Bonus-Falle: PVE 9 und das fehlende dhcpcd-base

Auf Proxmox VE 9 schlägt der erste virt-customize-Aufruf mit --install fehl: die libguestfs-Appliance bekommt keinen Netzwerk-Zugriff, apt update scheitert an DNS. Fehlender Dependency-Eintrag im Debian-guestfs-tools-Paket — in PVE 8.x kam der DHCP-Client noch transitiv, in Debian 13 / PVE 9 ist die Kette gebrochen. Upstream als libguestfs/libguestfs#211, Fix in trixie-proposed-updates.

Workaround:

apt install dhcpcd-base

auf dem Proxmox-Host. Danach funktioniert virt-customize --install wieder.

Lessons Learned

Copy-paste-Tutorials sind eine eigene Korrektheitsklasse. Das --truncate /etc/machine-id-Anti-Pattern hat sich über zehn Jahre durch hunderte Blog-Posts vererbt, weil niemand es nachgeprüft hat. Das Kommando wirft keinen Fehler — das Symptom tritt erst auf, wenn man mehr als eine VM klont.

virt-customize und virt-sysprep sind unterschiedliche Tools mit unterschiedlichen Zwecken. virt-customize baut das Image, virt-sysprep macht es klonbar. Wer beide in der richtigen Reihenfolge einsetzt, hat ein deutlich saubereres Setup.

Primärquellen lesen ist nicht optional. RHEL-Bug #1554546, libguestfs Issue #211, der upstream-Commit zur machine-id — alles in zehn Minuten zu finden, während die Frustration mit dem kaputten Template Stunden frisst.