Einführung
In virtuellen Umgebungen mit Proxmox VE ist Cloud-Init ein mächtiges Werkzeug zur automatisierten Konfiguration von virtuellen Maschinen. Eine häufige Anforderung ist die Einrichtung benutzerdefinierter Netzwerkrouten für VMs. In diesem Blogbeitrag zeige ich, wie Sie statische Routen zu Ihrer Cloud-Init-Konfiguration hinzufügen können – sowohl manuell als auch mit einem praktischen Skript, das die Arbeit für Sie erledigt.
Das Problem
Bei der Arbeit mit Proxmox möchte man oft, dass virtuelle Maschinen beim Start bestimmte Netzwerkrouten einrichten. Dies kann besonders nützlich sein für:
- VPN-Verbindungen
- Zugriff auf isolierte Netzwerksegmente
- Multi-Site-Umgebungen
- Komplexe Netzwerktopologien
Die Standardkonfiguration von Cloud-Init im Proxmox Web-Interface bietet jedoch keine direkte Möglichkeit, statische Routen hinzuzufügen.
Die Lösung
Die Lösung besteht darin, eine benutzerdefinierte Cloud-Init-Netzwerkkonfiguration zu erstellen und diese auf die VM anzuwenden. Dazu gibt es zwei Hauptansätze:
- Manuelle Konfiguration: Erstellung einer benutzerdefinierten YAML-Datei
- Automatisierte Konfiguration: Verwenden eines Skripts, das die bestehende Konfiguration anpasst
In diesem Beitrag konzentriere ich mich auf die zweite Methode: Ein Bash-Skript, das automatisch statische Routen zu einer bestehenden Cloud-Init-VM hinzufügt.
Das Skript
Hier ist das vollständige Skript, das eine statische Route zu einer Cloud-Init-VM hinzufügt:
#!/bin/bash
# Script to add static routes to Cloud-Init configuration for a Proxmox VM
# Usage: ./add_static_route.sh VMID NETWORK GATEWAY [DESCRIPTION]
# Example: ./add_static_route.sh 100 192.168.10.0/24 10.0.0.1 "Route to internal network"
set -e
# Check if required parameters are provided
if [ $# -lt 3 ]; then
echo "Usage: $0 VMID NETWORK GATEWAY [DESCRIPTION]"
echo "Example: $0 100 192.168.10.0/24 10.0.0.1 \"Route to internal network\""
exit 1
fi
VMID=$1
NETWORK=$2
GATEWAY=$3
DESCRIPTION=${4:-"Custom static route"}
# Verify that the VM exists
if ! qm status $VMID &>/dev/null; then
echo "Error: VM with ID $VMID does not exist"
exit 1
fi
# Check if the VM has cloud-init configured (on any IDE/SCSI device)
if ! qm config $VMID | grep -E "(ide|scsi)[0-9].*cloudinit" > /dev/null; then
echo "Error: VM $VMID does not have Cloud-Init configured (no cloudinit drive found)"
exit 1
fi
# Create a temporary directory for our work
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
# Get current network cloud-init config
echo "Dumping current Cloud-Init network configuration..."
if ! qm cloudinit dump $VMID network > "$TEMP_DIR/network_original.yaml" 2>/dev/null; then
echo "Info: No existing network configuration found, creating new one"
# Create a basic network config structure if none exists
cat > "$TEMP_DIR/network_original.yaml" << EOF
version: 2
ethernets:
eth0:
dhcp4: true
EOF
fi
# Make a backup of the original configuration
cp "$TEMP_DIR/network_original.yaml" "$TEMP_DIR/network_original.yaml.bak"
# Create a modified network configuration
echo "Creating modified configuration with static route..."
cp "$TEMP_DIR/network_original.yaml" "$TEMP_DIR/network_modified.yaml"
# Determine network configuration version
VERSION=$(grep -m 1 "version:" "$TEMP_DIR/network_original.yaml" | awk '{print $2}')
echo "Detected network configuration version: $VERSION"
if [ "$VERSION" = "1" ]; then
# For version 1, we need to carefully modify the config to maintain proper indentation
# First, let's create a new file with the correct structure
cp "$TEMP_DIR/network_modified.yaml" "$TEMP_DIR/network_modified.yaml.tmp"
# Find the physical section with eth0
ETH0_SECTION=$(grep -n "name: eth0" "$TEMP_DIR/network_modified.yaml.tmp" | cut -d: -f1)
if [ -z "$ETH0_SECTION" ]; then
echo "Error: Could not find eth0 section"
exit 1
fi
# Create a new file with the corrected format
echo "version: 1" > "$TEMP_DIR/network_modified.yaml"
echo "config:" >> "$TEMP_DIR/network_modified.yaml"
# Process the file line by line with correct indentation
FOUND_ETH0=false
ADDED_ROUTES=false
SUBNETS_SECTION=false
while IFS= read -r line; do
# Skip the version and config lines as we've already added them
if [[ "$line" == "version: 1" || "$line" == "config:" ]]; then
continue
fi
# Check if we're in the eth0 section
if [[ "$line" == *"name: eth0"* ]]; then
FOUND_ETH0=true
fi
# Check if we're in the subnets section
if [[ "$FOUND_ETH0" == true && "$line" == *"subnets:"* ]]; then
SUBNETS_SECTION=true
fi
# Add the routes after the last subnet entry
if [[ "$SUBNETS_SECTION" == true && "$line" == *"gateway:"* && "$ADDED_ROUTES" == false ]]; then
echo "$line" >> "$TEMP_DIR/network_modified.yaml"
echo " routes:" >> "$TEMP_DIR/network_modified.yaml"
echo " - network: $NETWORK" >> "$TEMP_DIR/network_modified.yaml"
echo " gateway: $GATEWAY" >> "$TEMP_DIR/network_modified.yaml"
ADDED_ROUTES=true
continue
fi
# Add the line to the new file
echo "$line" >> "$TEMP_DIR/network_modified.yaml"
done < "$TEMP_DIR/network_modified.yaml.tmp"
# If we didn't find a place to add routes, report an error
if [[ "$ADDED_ROUTES" == false ]]; then
echo "Error: Could not find an appropriate place to add routes"
echo "Please manually modify the cloud-init configuration"
exit 1
fi
elif [ "$VERSION" = "2" ]; then
# Check if the eth0 interface exists
if ! grep -q "eth0:" "$TEMP_DIR/network_modified.yaml"; then
echo "Error: Version 2 network configuration doesn't contain eth0 section"
cat "$TEMP_DIR/network_original.yaml"
exit 1
fi
# Check if routes section already exists
if grep -q "routes:" "$TEMP_DIR/network_modified.yaml"; then
# Add route to existing routes section
sed -i "/routes:/a\\ - to: $NETWORK\\n via: $GATEWAY\\n # $DESCRIPTION" "$TEMP_DIR/network_modified.yaml"
else
# Add routes section after eth0
sed -i "/eth0:/a\\ routes:\\n - to: $NETWORK\\n via: $GATEWAY\\n # $DESCRIPTION" "$TEMP_DIR/network_modified.yaml"
fi
else
echo "Error: Unsupported network configuration version: $VERSION"
cat "$TEMP_DIR/network_original.yaml"
exit 1
fi
# Save the modified configuration to a snippets directory
SNIPPET_NAME="vm-${VMID}-custom-network-$(date +%s).yaml"
SNIPPETS_PATH="/var/lib/vz/snippets"
# Ensure snippets directory exists
if [ ! -d "$SNIPPETS_PATH" ]; then
echo "Creating snippets directory at $SNIPPETS_PATH"
mkdir -p "$SNIPPETS_PATH"
fi
# Create backup of original config
BACKUP_NAME="vm-${VMID}-network-backup-$(date +%s).yaml"
cp "$TEMP_DIR/network_original.yaml" "$SNIPPETS_PATH/$BACKUP_NAME"
echo "Original configuration backed up to $SNIPPETS_PATH/$BACKUP_NAME"
# Show differences between original and modified configuration
echo -e "\nConfiguration changes:"
diff -u "$TEMP_DIR/network_original.yaml" "$TEMP_DIR/network_modified.yaml" || true
# Ask for confirmation before applying changes
echo -e "\nReady to apply the new network configuration to VM $VMID."
echo "Do you want to continue? (y/N)"
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Operation cancelled by user."
exit 0
fi
# Copy the modified configuration to snippets
cp "$TEMP_DIR/network_modified.yaml" "$SNIPPETS_PATH/$SNIPPET_NAME"
echo "Saved modified network configuration to $SNIPPETS_PATH/$SNIPPET_NAME"
# Set the custom network configuration for the VM
echo "Applying custom network configuration to VM $VMID..."
qm set $VMID --cicustom "network=local:snippets/$SNIPPET_NAME"
# Show the final configuration
echo -e "\nFinal Cloud-Init network configuration for VM $VMID:"
qm cloudinit dump $VMID network
echo -e "\nStatic route added successfully to VM $VMID"
echo "Network: $NETWORK"
echo "Gateway: $GATEWAY"
echo "Description: $DESCRIPTION"
echo -e "\nCustom configuration stored in: $SNIPPETS_PATH/$SNIPPET_NAME"
echo -e "Backup stored in: $SNIPPETS_PATH/$BACKUP_NAME"
echo -e "\nTo restore the original configuration if needed, run:"
echo "qm set $VMID --cicustom \"network=local:snippets/$BACKUP_NAME\""
Wie das Skript funktioniert
Das Skript führt folgende Schritte aus:
- Parameter prüfen: Überprüft, ob die notwendigen Parameter (VM-ID, Netzwerk, Gateway) angegeben wurden
- VM validieren: Stellt sicher, dass die VM existiert und mit Cloud-Init konfiguriert ist
- Konfiguration exportieren: Ruft die aktuelle Cloud-Init Netzwerkkonfiguration ab
- Konfigurationsformat erkennen: Unterscheidet zwischen Version 1 und Version 2 Konfigurationen
- Route hinzufügen: Fügt die statische Route im richtigen Format hinzu
- Backup erstellen: Sichert die ursprüngliche Konfiguration
- Änderungen anzeigen: Zeigt die Unterschiede zur Überprüfung an
- Bestätigung einholen: Fragt nach Bestätigung, bevor Änderungen angewendet werden
- Konfiguration anwenden: Wendet die neue Konfiguration auf die VM an
Unterschiedliche Cloud-Init Versionen
Ein wichtiger Aspekt ist die Unterstützung verschiedener Cloud-Init Konfigurationsformate:
Version 1 (Legacy-Format)
Das ältere Format verwendet diese Struktur für Routen:
version: 1
config:
- type: physical
name: eth0
subnets:
- type: static
address: '10.0.0.2'
netmask: '255.255.255.0'
gateway: '10.0.0.1'
routes:
- network: '192.168.0.0/24'
gateway: '10.0.0.254'
Version 2 (Netplan-Format)
Das neuere Format verwendet diese Struktur:
version: 2
ethernets:
eth0:
addresses: ['10.0.0.2/24']
gateway4: '10.0.0.1'
routes:
- to: '192.168.0.0/24'
via: '10.0.0.254'
Das Skript erkennt automatisch das verwendete Format und passt die Konfiguration entsprechend an.
Verwendung des Skripts
Um das Skript zu verwenden, speichern Sie es in einer Datei (z.B. add_static_route.sh
), machen Sie es ausführbar und führen Sie es mit den erforderlichen Parametern aus:
chmod +x add_static_route.sh
./add_static_route.sh 100 192.168.10.0/24 10.0.0.1 "Route zu internem Netzwerk"
Die Parameter sind:
- VMID: Die ID der virtuellen Maschine
- NETWORK: Das Zielnetzwerk im CIDR-Format
- GATEWAY: Die Gateway-IP-Adresse
- DESCRIPTION (optional): Eine Beschreibung der Route
Sicherheitsfeatures
Das Skript enthält mehrere Sicherheitsfeatures, um Probleme zu vermeiden:
- Automatisches Backup: Die ursprüngliche Konfiguration wird gesichert
- Differenzanzeige: Änderungen werden vor dem Anwenden angezeigt
- Bestätigungsabfrage: Bestätigung wird vor dem Anwenden eingeholt
- Wiederherstellungsanleitung: Befehl zur Wiederherstellung wird angezeigt
Anwendungsfälle
Dieses Skript ist besonders nützlich für:
- Automatisierte Bereitstellung: Hinzufügen von Routen zu neu erstellten VMs
- Batch-Updates: Aktualisieren mehrerer VMs mit denselben Routingregeln
- VPN-Konfiguration: Einrichten von Routen zu entfernten Netzwerken über VPN
- Netzwerksegmentierung: Zugriff auf isolierte Netzwerksegmente
Erweiterungsmöglichkeiten
Das Skript kann auf verschiedene Weise erweitert werden:
- Mehrere Routen: Unterstützung für mehrere Routen in einem Durchlauf
- Template-Integration: Automatisches Anwenden auf alle aus einem Template erstellten VMs
- Metriken und Prioritäten: Hinzufügen von Routing-Metriken für fortgeschrittene Szenarien
- DNS-Konfiguration: Gleichzeitige Anpassung der DNS-Einstellungen
Fazit
Die Möglichkeit, statische Routen über Cloud-Init zu konfigurieren, ist ein mächtiges Feature für komplexere Netzwerkumgebungen in Proxmox. Mit dem vorgestellten Skript wird dieser Prozess automatisiert und fehlerresistent.
Dieses Werkzeug spart nicht nur Zeit bei der Konfiguration, sondern stellt auch sicher, dass VMs nach Neustarts oder Migration konsistent mit den richtigen Netzwerkrouten konfiguriert werden.
Ich hoffe, dieser Beitrag und das Skript sind für eure Proxmox-Umgebungen hilfreich. Viel Spaß beim Experimentieren und Optimieren eurer Cloud-Init-Konfigurationen!