KubeVirt: Virtuelle Maschinen auf Kubernetes betreiben
Zurück zum Blog

KubeVirt: Virtuelle Maschinen auf Kubernetes betreiben

6 Min. Lesezeit
Lesen auf English

Wenn Container nicht reichen

Wir bei encircle360 setzen seit Jahren auf Kubernetes und Container. Unser gesamter Stack -- von Gitea über Keycloak bis Directus -- läuft containerisiert auf K3s-Nodes mit NixOS. Für unsere eigenen Services funktioniert das hervorragend. Aber in Kundenprojekten stoßen wir regelmäßig auf eine Grenze: Nicht jeder Workload lässt sich sinnvoll in einen Container packen.

Legacy-Anwendungen, die seit zehn Jahren auf einer dedizierten VM laufen und deren Innenleben niemand mehr vollständig versteht. Windows-Dienste, für die es schlicht kein Container-Image gibt. Datenbanken, die bare-metal-nahe Performance und direkten Festplattenzugriff erwarten. Oder schlicht regulatorische Anforderungen, die eine bestimmte Betriebssystemversion vorschreiben.

Bisher bedeutete das: zwei getrennte Welten. Container auf Kubernetes, VMs auf einem separaten Hypervisor. Zwei Plattformen, zwei Toolchains, zwei Betriebskonzepte. KubeVirt verspricht, diese Trennung aufzuheben.

Was ist KubeVirt?

KubeVirt ist ein Kubernetes-Add-on, das die Kubernetes-API um die Fähigkeit erweitert, virtuelle Maschinen zu erstellen und zu verwalten. Das Projekt ist seit 2019 ein CNCF-Incubating-Projekt und wird aktiv weiterentwickelt -- die aktuelle Version zum Zeitpunkt dieses Artikels liegt bei circa 0.55. Hinter dem Projekt stehen unter anderem Red Hat und eine wachsende Community.

Die zentrale Idee: VMs werden zu Kubernetes-nativen Objekten. Man definiert sie als YAML, deployt sie mit kubectl apply, überwacht sie mit den gewohnten Kubernetes-Tools und verwaltet ihren Lebenszyklus über die Kubernetes-API. Unter der Haube nutzt KubeVirt KVM und QEMU -- bewährte Linux-Virtualisierungstechnologie. Die VM läuft in einem Pod, der einen QEMU-Prozess enthält. Von außen betrachtet sieht Kubernetes einen Pod. Innerhalb dieses Pods läuft eine vollwertige virtuelle Maschine mit eigenem Kernel.

CRDs: VirtualMachine und VirtualMachineInstance

KubeVirt bringt mehrere Custom Resource Definitions mit, von denen zwei zentral sind.

VirtualMachineInstance (VMI) repräsentiert eine laufende VM-Instanz. Vergleichbar mit einem Pod: Wenn die VMI gelöscht wird, ist die VM weg. Für kurzlebige oder zustandslose VMs kann das ausreichen.

VirtualMachine (VM) ist die persistente Abstraktion darüber. Sie definiert die gewünschte VM-Konfiguration und verwaltet den Lebenszyklus der darunterliegenden VMI. Eine VirtualMachine kann gestoppt und wieder gestartet werden, ohne dass die Definition verloren geht -- analog zu einem Deployment, das Pods verwaltet.

Ein einfaches Beispiel für eine VirtualMachine-Definition:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: test-vm
  namespace: kubevirt-demo
spec:
  running: true
  template:
    metadata:
      labels:
        kubevirt.io/vm: test-vm
    spec:
      domain:
        resources:
          requests:
            memory: "1Gi"
        devices:
          disks:
            - name: rootdisk
              disk:
                bus: virtio
            - name: cloudinitdisk
              disk:
                bus: virtio
      volumes:
        - name: rootdisk
          containerDisk:
            image: quay.io/kubevirt/fedora-cloud-container-disk-demo:latest
        - name: cloudinitdisk
          cloudInitNoCloud:
            userData: |
              #cloud-config
              hostname: test-vm
              ssh_authorized_keys:
                - ssh-rsa AAAA...
              packages:
                - htop
                - curl

Wer YAML-Definitionen für Kubernetes gewohnt ist, fühlt sich sofort zu Hause. Die VM-Spezifikation definiert Ressourcen, Disks und Volumes -- nur dass es hier nicht um Container-Images geht, sondern um VM-Disk-Images und Cloud-Init-Konfiguration. Das containerDisk-Volume ist dabei ein cleverer Trick: Das VM-Image wird als Container-Image verpackt und über eine Container-Registry bereitgestellt. Das nutzt die bestehende Kubernetes-Infrastruktur für Image-Distribution.

virtctl: Die VM-Kommandozeile

Während kubectl für die meisten Operationen ausreicht, bietet KubeVirt mit virtctl ein ergänzendes CLI-Tool für VM-spezifische Aktionen.

# VM starten und stoppen
virtctl start test-vm
virtctl stop test-vm

# Konsole öffnen (wie SSH, aber direkt)
virtctl console test-vm

# VNC-Verbindung für grafische Oberfläche
virtctl vnc test-vm

# VM-Image als Volume exponieren
virtctl expose vmi test-vm --port=22 --name=test-vm-ssh --type=NodePort

Besonders virtctl console hat uns überzeugt. Direkter Konsolenzugriff auf die VM, ohne Netzwerkkonfiguration, ohne SSH-Setup -- einfach über die Kubernetes-API. Für Debugging und initiale Einrichtung ist das enorm praktisch.

Persistente Volumes für VM-Disks

Container sind typischerweise ephemeral -- beim Neustart ist alles weg. Für VMs ist das offensichtlich nicht akzeptabel. KubeVirt nutzt Kubernetes PersistentVolumeClaims, um VM-Disks dauerhaft zu speichern. Das bedeutet: Jeder Storage-Provider, den das Kubernetes-Cluster unterstützt, funktioniert auch als VM-Storage.

Auf unseren K3s-Nodes mit NixOS nutzen wir Longhorn als verteilten Storage. Damit können VM-Disks über Nodes repliziert werden -- eine Voraussetzung für Live Migration, dazu gleich mehr.

volumes:
  - name: rootdisk
    persistentVolumeClaim:
      claimName: fedora-vm-disk

Die Herausforderung liegt weniger in der Konfiguration als in der Performance. VM-Workloads stellen andere Anforderungen an Storage als Container. Insbesondere Datenbanken in VMs brauchen niedrige Latenz und hohen Durchsatz. Ob netzwerkbasierter Storage wie Longhorn dafür ausreicht, hängt stark vom konkreten Workload ab. Für unsere Tests war die Performance akzeptabel, aber wir würden das für produktive Datenbank-VMs im Einzelfall genauer evaluieren.

CDI: VM-Images importieren

Woher kommen die VM-Images? Neben den bereits erwähnten Container-Disks bietet der Containerized Data Importer (CDI) eine elegante Lösung. CDI ist ein separater Operator, der VM-Images aus verschiedenen Quellen importiert und als PersistentVolumes bereitstellt.

Unterstützt werden HTTP-URLs, S3-kompatible Speicher und bestehende Disk-Images in Formaten wie QCOW2 oder RAW. In der Praxis heißt das: Man kann bestehende VM-Images aus VMware, OpenStack oder einer lokalen Libvirt-Umgebung übernehmen, ohne sie manuell in Container-Images umpacken zu müssen.

apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: fedora-dv
spec:
  source:
    http:
      url: "https://download.fedoraproject.org/pub/fedora/linux/releases/36/Cloud/x86_64/images/Fedora-Cloud-Base-36-1.5.x86_64.qcow2"
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi

CDI lädt das Image herunter, konvertiert es bei Bedarf und schreibt es in ein PVC. Danach kann die VirtualMachine dieses Volume referenzieren. Der gesamte Prozess läuft als Kubernetes-Job -- vollständig automatisiert und überwachbar.

Live Migration

Eines der Features, das uns am meisten beeindruckt hat, ist Live Migration. KubeVirt kann laufende VMs von einem Node auf einen anderen verschieben -- ohne Downtime. Voraussetzung ist geteilter Storage (etwa über Longhorn oder Ceph) und eine kompatible CPU-Architektur auf den Ziel-Nodes.

# Live Migration auslösen
virtctl migrate test-vm

Für Kubernetes-Operationen wie Node-Drains bei Updates ist das entscheidend. Ohne Live Migration müsste eine VM heruntergefahren und auf einem anderen Node neu gestartet werden. Mit Live Migration wird der Speicherzustand inkrementell auf den Ziel-Node kopiert, während die VM weiterläuft. Der tatsächliche Ausfall beschränkt sich auf wenige Millisekunden beim finalen Umschalten.

Auf unseren K3s-Nodes haben wir das erfolgreich getestet: kubectl drain auf einem Node löst automatisch die Migration der VMs auf andere Nodes aus. Das Verhalten entspricht dem, was man von VMware vMotion oder OpenStack Live Migration kennt -- nur eben integriert in die Kubernetes-Plattform.

KubeVirt auf K3s mit NixOS

Wie bereits in unserem Beitrag zu K3s im Produktiveinsatz beschrieben, betreiben wir unsere Cluster auf K3s-Nodes mit NixOS als Host-Betriebssystem. KubeVirt auf K3s zu installieren erfordert ein paar Anpassungen.

Die Nodes müssen Hardware-Virtualisierung unterstützen -- KVM muss verfügbar sein. Auf NixOS aktiviert man das über die entsprechende Kernel-Konfiguration. Zusätzlich muss das /dev/kvm-Device in den Containerd-Konfigurationen zugänglich sein. Wir mussten die NixOS-Konfiguration unserer Nodes um einige Zeilen erweitern, aber der Aufwand hielt sich in Grenzen.

Die Installation von KubeVirt selbst erfolgt über den KubeVirt-Operator:

# KubeVirt Operator installieren
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.55.0/kubevirt-operator.yaml

# KubeVirt Custom Resource erstellen
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.55.0/kubevirt-cr.yaml

# Status prüfen
kubectl -n kubevirt wait kv kubevirt --for condition=Available --timeout=300s

Nach wenigen Minuten ist KubeVirt einsatzbereit. Die Custom Resources sind registriert, die notwendigen DaemonSets laufen auf allen Nodes und virtctl kann VMs steuern.

Wo sehen wir den Einsatz?

Nach einigen Wochen des Experimentierens sehen wir KubeVirt nicht als Ersatz für dedizierte Virtualisierungsplattformen, sondern als Brücke. Die Stärke liegt in Szenarien, in denen Container und VMs auf derselben Plattform koexistieren müssen.

Legacy-Migration: Workloads, die sich nicht sofort containerisieren lassen, können als VM auf Kubernetes laufen und schrittweise modernisiert werden. Das vermeidet den Big-Bang-Ansatz und erlaubt eine graduelle Migration.

Unified Operations: Ein Team, eine Plattform, ein Satz an Tools. Statt Kubernetes und VMware parallel zu betreiben, konsolidiert man auf einer Plattform. Monitoring, Networking, Storage und RBAC funktionieren einheitlich.

Entwicklungsumgebungen: Komplette Systemlandschaften -- inklusive VMs für Komponenten, die nicht containerisiert sind -- lassen sich als Kubernetes-Manifeste definieren und reproduzierbar aufbauen.

Offene Fragen

KubeVirt ist in aktivem Pre-1.0-Entwicklungsstadium, und das merkt man an einigen Stellen. Die Dokumentation hat Lücken, insbesondere bei komplexeren Netzwerk-Konfigurationen. Performance-Tuning für I/O-intensive Workloads erfordert tiefes Verständnis von QEMU und KVM. Und die Frage, wie gut KubeVirt mit hunderten VMs skaliert, können wir mit unseren kleinen Setups nicht beantworten.

Auch das Tooling-Ökosystem ist noch jung. Es gibt keinen ausgereiften GUI-basierten VM-Manager vergleichbar mit vCenter. Wer VMs über YAML-Dateien und CLI verwalten will, fühlt sich zu Hause. Wer eine grafische Konsole zur Verwaltung braucht, wird noch warten müssen.

Fazit

KubeVirt löst ein Problem, das in der Container-Welt gerne verdrängt wird: Nicht alles kann und sollte ein Container sein. Statt diese Realität zu ignorieren, erweitert KubeVirt Kubernetes um die fehlende Fähigkeit -- und tut das auf eine Weise, die sich nativ und durchdacht anfühlt. VMs als Kubernetes-Objekte, verwaltet mit denselben Tools, auf derselben Infrastruktur.

Für unsere Kundenprojekte sehen wir hier konkretes Potenzial. Die nächsten Monate werden zeigen, wie sich KubeVirt auf dem Weg zur Version 1.0 weiterentwickelt und ob die Pre-1.0-Einschränkungen für produktive Szenarien tragbar sind. Im Moment sind wir beeindruckt von dem Konzept und werden das Projekt eng verfolgen. Wer Kubernetes betreibt und VM-Workloads hat, sollte KubeVirt auf dem Radar haben.

Teilen

Patrick Hütter

Geschrieben von

Patrick Hütter

Gründer & Software-Architekt

Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.