KVM-Host mit Ubuntu einrichten

Ca. alle vier Jahre wiederholt sich das gleiche Spiel: Ich bestelle einen neuen Root-Server, installiere dort die aktuelle Version von Ubuntu Server LTS und richte das Virtualisierungssystem KVM ein. Sobald dieses läuft, migriere ich meine virtuellen Maschinen, zu denen unter anderem der Server für kofler.info zählt, auf den neuen Host. Die Down-Time beträgt in der Regel weniger als eine Stunde und ergibt sich durch den Zeitraum für die Aktualisierung der DNS-Einträge.

Mit dem Betrieb eines eigenen Virtualisierungssystem entspreche ich nicht ganz dem Cloud-Zeitgeist. Natürlich gibt es unzählige Hoster (an erster Stelle natürlich Amazon), die mir virtuelle Maschinen für den Betrieb von Linux-Installationen anbieten. Die Verwendung eines »echten« (sprich: altmodischen) Root-Servers kann allerdings eine Menge Geld sparen, bringt mehr Flexibilität mit sich und hält mein KVM-Know-how einigermaßen aktuell :-)

Dieser Artikel fasst die wichtigsten Details zur Konfiguration des KVM-Hosts (Ubuntu) sowie von virtuellen Maschinen (Ubuntu sowie RHEL/CentOS/Oracle) zusammen. Beachten Sie, dass einige Details der Netzwerkkonfiguration providerspezifisch sind und für Hetzner gelten.

Warum alle vier Jahre ein Server-Wechsel, werden Sie vielleicht fragen. Im Wesentlichen gibt es zwei Gründe:

  • Der eine ist der LTS-Zeitraum von Ubuntu (fünf Jahre). Ein LTS-Update des Servers im laufenden Betrieb ist mir suspekt. Allzu oft geht dabei etwas schief; dann muss ich sofort und unter Stress nach einer Lösung suchen, weil sich das einmal gestartete/durchgeführte Update nicht mehr rückgängig machen lässt. Bis das Problem dann gelöst ist, habe ich womöglich eine Downtime von mehreren Stunden oder gar Tagen. Deswegen lasse ich eine einmal installierte LTS-Version einfach weiterlaufen (natürlich mit allen »normalen« Updates), so lange der Server aktiv ist.

  • Der zweite Grund ist die Lebensdauer von Hardware: Nach vier Jahren ununterbrochenen Einsatz steigt nicht nur bei herkömmlichen Festplatten die Fehlerwahrscheinlichkeit, sondern auch bei SSDs und anderen Komponenten. Ich mag nicht warten, bis es wirklich Ausfälle gibt, und steige lieber rechtzeitig auf neue Hardware um.

Update 28.10.2020: systemctl disable --now dnsmasq

Server-Hardware

Ich habe mich diesmal erstmalig für einen Server mit AMD-Prozessor entschieden (Details siehe hier):

  • AMD Ryzen 5 3600
  • 2 x 512 GB SSD
  • 64 GB RAM

Für die virtuellen Maschinen habe ich zusätzlich einige IPv4-Adressen bestellt. (Für IPv6 ist ein ganzes /64-Subnetz inkludiert, das reicht aus. Aber leider ist IPv6 noch nicht so verbreitet, dass man auf IPv4 verzichten kann. Und es ist zweifelhaft, ob es je soweit kommen wird.)

Ubuntu-Host

Die Ubuntu-Installation habe ich mit dem Hetzner-Script installimage durchgeführt (Dokumentation). Dieses Werkzeug führt eine Minimalinstallation mit einigen Hetzner-spezifischen (Netzwerk-)Einstellungen durch. Das Script erlaubt eine relativ einfache Disk-Konfiguration. In meinem Fall habe ich beide SSDs mit RAID-1 verbunden. 120 GB sind für das Root-Dateisystem reserviert, der Rest per LVM für ein zweites Dateisystem mit den Images der virtuellen Maschinen. Als Dateisystem verwende ich in beiden Fällen ext4. (LVM ist mir vor allem wegen der Snapshot-Funktion wichtig. Damit kann ich in einem Backup-Script die Image-Dateien in einem Snapshot einfrieren und konsistent sichern, ohne den Betrieb der virtuellen Maschinen zu unterbrechen.)

lsblk

NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
nvme1n1       259:0    0   477G  0 disk  
├─nvme1n1p1   259:2    0   120G  0 part  
│ └─md0         9:0    0   120G  0 raid1 /
└─nvme1n1p2   259:3    0   357G  0 part  
  └─md127       9:127  0 356.8G  0 raid1 
    └─vg1-vms 253:0    0   320G  0 lvm   /var/lib/libvirt/images/lvm
nvme0n1       259:4    0   477G  0 disk  
├─nvme0n1p1   259:6    0   120G  0 part  
│ └─md0         9:0    0   120G  0 raid1 /
└─nvme0n1p2   259:7    0   357G  0 part  
  └─md127       9:127  0 356.8G  0 raid1 
    └─vg1-vms 253:0    0   320G  0 lvm   /var/lib/libvirt/images/lvm


Sobald der Ubuntu Server als solches läuft und via SSH erreichbar ist, folgt die Basisabsicherung: Neuen Benutzer mit sudo-Rechten einrichten, root-Login per Passwort sperren (installimage richtet entgegen der Ubuntu-Gepflogenheiten einen aktiven root-Account ein), unattended-upgrades installieren und konfigurieren, Kernel-Live-Patches aktivieren (canonical-livepatch), fail2ban zur SSH-Absicherung installieren etc.

Der nächste Schritt ist die Installation von QEMU/KVM samt der libvirt-Bibliotheken:

apt install qemu-kvm libvirt-daemon-system libvirt-clients qemu-utils bridge-utils dnsmasq virt-top

dnsmasq ist für die libvirt-Werkzeuge erforderlich. dnsmasq soll allerdings nur von libvirt gestartet werden, nicht als eigenständiges Service:

systemctl disable --now dnsmasq

(Wenn Sie das obige Kommando vergessen, erhalten Sie nach ein paar Wochen eine Abuse-Mail vom BSI, der zufolge Sie einen offenen DNS-Resolver betreiben, der missbräuchlich für DDoS-Reflection/Amplification-Angriffe verwendet werden könnte.)

Damit die libvirt-Werkzeuge von einem gewöhnlichen Account (nicht root) genutzt werden können, muss dieser Nutzer den Gruppen kvm und libvirt hinzugefügt werden:

usermod -a -G kvm <username>
usermod -a -G libvirt <username>

Damit sind alle Voraussetzungen erfüllt, um virtuellen Maschinen einzurichten und auszuführen. Ich verwende dazu in der Regel die grafische Benutzeroberfläche virt-manager, die auf meinem Notebook zuhause läuft. Die Verbindung zum KVM-Host erfolgt via SSH.

Die virtuellen Maschinen können aus der Ferne über den virt-manager eingerichtet und administriert werden.

Netzwerkkonfiguration am Host

Noch offen ist die Netzwerkkonfiguration am Host: Standardmäßig erhalten neue virtuellen Maschinen eine private IP-Adresse via NAT. Die virtuellen Maschinen haben damit Internetzugang, sind aber von außen nicht zugänglich. Für den Server-Betrieb nicht ideal …

Ab jetzt wird es Hetzner-spezifisch: Mit jedem Server erhalten Sie ein riesiges eigenes IPv6-Netz, aber nur eine IPv4-Adresse. Zusätzlich können Sie bis zu 6 Einzel-IPv4-Adressessen oder ein ganzes IPv4-Subnetz mieten. IPv4-Adressen sind ein knappes Gut und daher ziemlich teuer. Ich habe aktuell nur drei Einzel-IPv4-Adressen, was für meine Zwecke reicht.

Nun geht es darum, am Host entweder eine Bridge oder Routing einzurichten, um die virtuellen Maschinen mit dem Host zu verbinden. Ich habe mich für die einfachere und besser dokumentierte Bridge-Variante entschieden.

Ausgangspunkt ist die vorgegebene Konfiguration des neuen Servers, hier in der Netplan-Syntax von Ubuntu. a.b.c.d bzw. x:y:z sind Teile der von Hetzner zugewiesenen IPv4- oder IPv6-Adressen. a.b.c.e ist die Gateway-Adresse für IPv4.

# Datei /etc/netplan/01-netcfg.yaml auf dem KVM-Host
# vorgegeben durch das installimage-Script von Hetzner
network:
  version: 2
  renderer: networkd
  ethernets:
    enp35s0:
      addresses:
        # a.b.c.d: dem Server zugewiesene IPv4-Adresse
        # x.y.z: Teil des des dem Server zugewiesenen IPv6-Adressblocks
        - a.b.c.d/32
        - 2a01:x:y:z::2/64
      routes:
        - on-link: true
          to: 0.0.0.0/0
          # a.b.c.e: vorgegebene IPv4-Gateway-Adresse
          via: a.b.c.e
      gateway6: fe80::1
      nameservers:
        addresses:
          - 213.133.100.100
          - 213.133.99.99
          - 213.133.98.98
          - 2a01:4f8:0:1::add:1010
          - 2a01:4f8:0:1::add:9999
          - 2a01:4f8:0:1::add:9898

In meinem Fall gibt es außerdem drei weitere IPv4-Adressen a.b.c.f, a.b.c.g und a.b.c.h, zu denen ich über eine Bridge Punkt-zu-Punkt-Verbindungen mit den virtuellen Maschinen herstellen möchte. IPv6 soll natürlich auch an die virtuellen Maschinen weitergegeben werden, was aber wesentlich einfacher ist. (Es sind dabei weder Punkt-zu-Punkt-Verbindungen noch spezielle Routing-Regeln erforderlich; der IP-Verkehr erfolgt ja innerhalb des IPv6-Adressbereichs.)

Nun geht es darum, die obige Konfiguration für die Schnittstelle enp35s0 in eine Netzwerkbrücke umzubauen. Nahezu alle Einstellungen, die bisher für enp35s0 galten, werden für die Brücke br0 übernommen. Entscheidend ist, dass Sie der Brücke mit macaddress dieselbe MAC-Adresse zuweisen, die auch die Netzwerkschnittstelle des Servers hat (in diesem Beispiel also enp35s0). Die MAC-Adresse können Sie mit dem Kommando ip a feststellen. (Wenn macaddress fehlt, entscheidet sich Netplan für eine zufällige MAC-Adresse. Hetzner mag das gar nicht.)

Bevor Sie die Netplan-Konfigurationsdatei verändern, sollten Sie unbedingt ein Backup der ursprünglichen Datei erstellen. (Verwenden Sie dabei eine andere Dateikennung als .yaml!)

# geänderte Datei /etc/netplan/01-netcfg.yaml auf dem KVM-Host
network:
  version: 2
  renderer: networkd
  ethernets:
    enp35s0:
      dhcp4: no
      dhcp6: no
  bridges:
    br0:
      # muss mit der MAC-Adresse der Netzwerkschnittstelle
      # übereinstimmen!
      macaddress: aa:bb:cc:dd:ee:ff
      # die Netzwerkbrücke soll zur Schnittstelle `enp35s0 führen        
      interfaces:
        - enp35s0
      # vorgegebene IPv4- und IPv6-Adresse für den Server
      addresses:
        - a.b.c.d/32
        - 2a01:x.y.z::2/64
      routes:
        # IPv4-Routing zu den virtuellen Maschinen
        - to:      a.b.c.f
          scope:   link
        - to:      a.b.c.g
          scope:   link
        - to:      a.b.c.h
          scope:   link
        # vorgegebenes IPv4-Gateway (wie in der Ausgangskonfiguration)
        - to:      0.0.0.0/0
          via:     a.b.c.d.e
          on-link: true
      # vorgegebenes IPv6-Gateway
      gateway6: fe80::1
      nameservers:
        addresses:
          # Nameserver-Adressen von Hetzner
          - 213.133.100.100
          - 213.133.99.99
          - 213.133.98.98
          - 2a01:4f8:0:1::add:1010
          - 2a01:4f8:0:1::add:9999
          - 2a01:4f8:0:1::add:9898

Die geänderte Netzwerkkonfiguration kann mit netplan try ausprobiert bzw. mit netplan apply unmittelbar aktiviert werden. Aber Vorsicht: Wenn Ihnen ein Fehler unterläuft, verlieren Sie den SSH-Zugang zu Ihrem Server! Dann müssen Sie den Rechner über die Weboberfläche mit einem Rescue-Image neu starten, sich mit den SSH-Daten des Rescue-Images anmelden, das Dateisystem Ihres Servers manuell einbinden und die Konfigurationsdatei korrigieren. Das ist ziemlich mühsam …

Wenn alles klappt, läuft der Server mit der neuen Konfiguration unverändert. Der lokale Netzwerkverkehr geht jetzt eben über die Brücke br0, sonst hat sich für den Host nichts geändert.

Zu guter Letzt muss noch IP-Forwarding aktiviert werden, damit nicht nur die IP-Pakete des Hosts weitergeleitet werden, sondern auch die der virtuellen Maschinen . Dazu führen Sie die folgenden Änderungen in /etc/sysctl.conf durch:

# am Ende von /etc/sysctl.conf
...
# Forwarding für IPv4 und IPv6
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Mit sysctl -p (oder einem Neustart) werden die neuen Einstellungen wirksam.

Konfiguration der virtuellen Maschine (Ubuntu)

Beim Einrichten einer neuen virtuellen Maschine bzw. bei der Veränderung einer schon vorhandenen VM können Sie nun im virt-manager die Netzwerkschnittstelle br0 auswählen.

Im »virt-manager« kann der Netzwerkadapter nun mit der Brücke »br0« verbunden werden.

Wenn Sie die XML-Beschreibung der virtuellen Maschine direkt ändern, können Sie sich bei den Einstellungen des virtuellen Netzwerkadapters (NICs) an diesem Muster orientieren:

<interface type="bridge">
  <mac address="52:54:00:aa:bb:cc"/>
  <source bridge="br0"/>
  <target dev="vnet1"/>
  <model type="virtio"/>
  <alias name="net0"/>
  <address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
</interface>

Die Konfiguration des virtuellen NICs ist aber erst die halbe Miete. Jetzt müssen Sie die virtuelle Maschine starten und dort die Netzwerkkonfiguration durchführen. Beachten Sie, dass Sie dabei vorerst keinen SSH-Zugang haben! Sie müssen also die VNC-Funktion des Virtualisierungssystem verwenden.

Eine zum obigen Host passende Konfiguration für eine virtuelle Maschine unter Ubuntu sieht wie folgt aus. Beachten Sie drei Dinge:

  • Die gewünschten IPv4- und IPv6-Adressen müssen statisch eingestellt werden.
  • IPv4-Verkehr fließt über eine Punkt-zu-Punkt-Verbindung zum KVM-Host.
  • IPv6-Verkehr verwendet den KVM-Host als Gateway.
# /etc/netplan/01-config.yaml in einer virtuellen Maschine (Ubuntu)
network:
  version: 2
  ethernets:
    eth0:
      # IPv4- und IPv6-Adresse der virtuellen Maschine
      addresses:
        - a.b.c.f/32
        - "2a01:x:y:z::4/64"
      # IPv4: Punkt-zu-Punkt-Verbindung zur IPv4-Adresse des KVM-Hosts
      routes:
        - to:      0.0.0.0/0
          via:     168.119.33.119
          on-link: true
      # IPv6-Gateway: IPv6-Adresse des KVM-Hosts
      gateway6: "2a01:x:y:z::2"
      nameservers:
        addresses:
          - 213.133.100.100
          - 213.133.99.99
          - 213.133.98.98
          - "2a01:4f8:0:1::add:1010"
          - "2a01:4f8:0:1::add:9999"
          - "2a01:4f8:0:1::add:9898"

Sofern Sie keine YAML-Syntaxfehler gemacht haben, sollte mit netplan apply eine funktionierende IPv4- und IPv6-Verbindung ins Internet bestehen. Zum Testen verwenden Sie z.B. ping -4 google.de und ping -6 google.de.

Konfiguration der virtuellen Maschine (CentOS/Oracle/RHEL 8)

Das Einrichten einer neuen VM erfolgt unabhängig davon, ob darin Ubuntu, CentOS oder ein anderes Betriebssystem ausgeführt werden soll. Die Unterschiede ergeben sich erst bei der Netzwerkkonfiguration in der virtuellen Maschine. Wenn dabei CentOS 8 oder Oracle Linux 8 oder RHEL 8 zum Einsatz kommt, befinden sich die Konfigurationsdateien im Verzeichnis /etc/sysconfig/network-scripts. Trotz der Red-Hat-spezifischen Syntax werden die Dateien seit Version 8 durch den NetworkManager verarbeitet. (Für die Kompatiblität ist das Plugin ifcfg-rh zuständig.)

Die folgenden Zeilen zeigen eine statische Konfiguration für die IPv4-Adresse a.b.c.g und die IPv6-Adresse IPV6ADDR=2a01:x:y:z::5. Beachten Sie, dass der zweite Teil des Dateinamens mit dem Namen der Netzwerkschnittstelle (hier enp1s0) übereinstimmen muss!

# Datei /etc/sysconfig/network-scripts/ifcfg-enp1s0
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="none"
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
NAME="enp1s0"
DEVICE="enp1s0"
ONBOOT="yes"

# IPv4-Adresse und -Gateway
IPADDR="a.b.c.g"
PREFIX="24"
GATEWAY="a.b.c.d"

# IPv6-Adresse und -Gateway
IPV6ADDR=2a01:x.y.z::5
IPV6_DEFAULTGW=fe80::1
IPV6INIT="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
IPV6_PRIVACY="no"

# DNS-Adressen Hetzner
DNS1="213.133.100.100"
DNS2="213.133.99.99"
DNS3="213.133.98.98"
DNS4="2a01:4f8:0:1::add:1010"
DNS5="2a01:4f8:0:1::add:9999"
DNS6="2a01:4f8:0:1::add:9898"

MAC-Ärger

Grundsätzlich sollte jetzt alles funktionieren: Die virtuellen Maschinen können mit dem Internet kommunizieren und sind von außen auch sichtbar. Das lässt sich rasch mit ein paar ping-Kommandos überprüfen.

Die auf dem Host-Rechner eingerichtete Netzwerkbrücke hat allerdings einen gravierenden Nachteil: Sie belässt die in den IP-Paketen enthaltenen MAC-Adressen der virtuellen Maschinen.

Im Hetzner-internen Netzwerk ist man damit unglücklich. Ausgehende Pakete dürfen ausschließlich bekannte, den vermieteten Servern zugewiesenen MAC-Adressen haben. Ist das nicht der Fall, bekommen Sie als Hetzner-Kunde relativ rasch eine Mail, in der Sie samt Deadline gebeten werden, nur die zugewiesenen MAC-Adressen zu verwenden. Andernfalls droht eine Sperre des Servers.

So weit so unerfreulich. Hetzner schlägt in seinen Dokumentationsseiten zwei Lösungswege vor:

  • Ein Ansatz besteht darin, anstelle einer Netzwerkbrücke ein Routing-Setup zu verwenden. Das Routing führt dazu, dass die MAC-Adressen der virtuellen Maschinen durch die des Hosts ersetzt werden. Allerdings fehlt nicht nur auf den Hetzner-Seiten eine konkrete Anleitung, wie ein derartiges Setup mit Ubuntus Netplan aussehen könnte; ich habe auch bei einer stundenlangen Recherche im Internet kein entsprechendes Beispiel gefunden.

  • Alternativ besteht die Möglichkeit, in den Hetzner-Administrationsseiten für Einzel-IPs eine MAC-Adresse anzufordern. Das kostet nur einen Mausklick. Anschließend müssen Sie bei der Konfiguration der virtuellen Maschinen die anfangs zufällige MAC-Adresse des virtuellen Netzwerkadapters durch die von Hetzer gewünschte MAC ersetzen. Am einfachsten gelingt das in der XML-Ansicht der NIC-Einstellungen, nachdem Sie im virt-manager zuvor mit Bearbeiten / Einstellungen das Verändern von XML-Dateien ausdrücklich erlaubt haben.

MAC-Adresse des virtuellen Netzwerkadapters in »virt-manager« einstellen

Da ich bei der Bridge-Konfiguration bleiben wollte, habe ich mich für den zweiten Lösungsansatz entschieden. Die Konfiguration ist einfach. Die neue MAC wird nach einem Reboot der virtuellen Maschine wirksam. Alleine: Nach erfolgreicher Konfiguration (ip addr in der virtuellen Maschine zeigt die gewünschte MAC an, und auch tcpdump am Host liefert Pakete mit dieser MAC) ist kein IPv4-Verkehr mehr möglich. (IPv6 funktioniert weiter.) Die von Hetzer vorgeschlagene Konfiguration führt somit zumindest bei meinem Setup direkt in die Sackgasse. (Die gleichen Probleme hatte ich in der Vergangenheit schon bei einem anderen KVM-Host, auf dem eine ältere Ubuntu-Version ohne Netplan lief.)

Ich habe den Hetzner-Support kontaktiert, aber die Antwort war kurz und nichtssagend. (Hallo, Hetzner, ihr wart in der Vergangenheit deutlich besser …) Nach zwei Tagen Fehlersuche habe ich aufgegeben und die mit der Zusatz-IP-Adresse verbundene MAC-Adresse wieder freigegeben (deaktiviert). Zu diesem Zeitpunkt war ich kurz davor, den neuen Server wieder zu kündigen und mich nach einem anderen Provider umzusehen.

Auf einen neuen, durchaus originellen Lösungsansatz hat mich schließlich ein umfangreicher Blog-Artikel von Dirk Hillbrecht gebracht. Es schlägt (in einem zugegebenermaßen ganz anderen Setup) vor, die MAC-Adressen aller weitergeleiteten Pakete mit dem mir bisher unbekannten Kommando ebtables einfach zu überschreiben. Dazu reicht das folgende, winzige Script (wobei Sie natürlich aa:bb:... durch die MAC-Adresse Ihres Hosts ersetzen müssen):

#!/bin/bash
# Datei /etc/rc.local auf dem Host-System
ebtables -t nat -A POSTROUTING -j snat --to-src aa:bb:cc:dd:ee:ff 
exit 0

Die Datei muss mit chmod +x ausführbar gemacht werden und wird dann beim Start des Rechners einmalig ausgeführt. ebtables (das gleichnamige Paket muss vorher installiert werden) ist ein Gegenstück zum Firewall-Werkzeug iptables. Es arbeitet allerdings nicht auf IP-Paketebene, sondern auf Ethernet-Ebene. Das vereinfacht manche Aufgaben erheblich.

  • -t nat -A POSTROUTING fügt eine Regel in die vordefinierte Tabelle für NAT POSTROUTING hinzu.
  • -j snat --to-source <mac> bewirkt, dass die Quell-MAC aller Ethernet-Pakete, die diese Tabelle durchlaufen, neu eingestellt wird.

Um zu überprüfen, ob alles funktioniert, können Sie mit tcpdump alle Pakete ansehen, die die Netzwerkschnittstelle des Hosts durchlaufen. Mit Filterregeln können Sie die Fülle der Ausgaben reduzieren und gezielt nach Paketen für eine bestimmte IP-Adresse (z.B. die einer Ihrer virtuellen Maschinen) suchen:

tcpdump  -e -n -i enp35s0  "host a.b.c.f"

Ich gebe zu, dass mir das MAC-Rewriting ein wenig wie ein Hack nach der Holzhammermethode erscheint. Andererseits: Das Setup ist einfach, und es hat bisher keine unerwünschten Nebenwirkungen ausgelöst. Die virtuellen Maschinen können auch direkt miteinander kommunizieren.

Quellen

Zur MAC-Problematik:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Wenn Sie hier einen Kommentar absenden, erklären Sie sich mit der folgenden Datenschutzerklärung einverstanden.