Kubernetes Ecosystem: From User to Contributor, Episode 10 (Series Finale)
← EP09: Karpenter vs Cluster Autoscaler · EP10: KubeVirt · All Kubernetes Ecosystem Episodes →
11 min read
TL;DR
- KubeVirt runs full virtual machines as first-class Kubernetes objects — a
VirtualMachineInstancewraps a real QEMU/KVM VM inside a special pod, scheduled and managed the same way any other workload is - The real reason to run KubeVirt isn’t nostalgia for VMs — it’s giving legacy, non-containerizable, or Windows workloads the same control plane, networking, and operational tooling as everything else, instead of maintaining a separate VM infrastructure stack alongside Kubernetes
- Multus (multiple network interfaces per pod) and CDI (Containerized Data Importer, for getting disk images into the cluster) are the supporting pieces that make VM networking and storage work inside a Kubernetes-native model
- Live migration — moving a running VM to another node with no downtime — is KubeVirt’s answer to node maintenance, and it’s meaningfully different from how Kubernetes handles pod disruption
- Nodes running VM workloads need actual hardware virtualization support (KVM) — the same nested-virtualization requirement that constrained Minikube’s VM drivers back in EP02, now showing up at the production node level
- Contribution opportunity: GPU and device-plugin support for VM workloads specifically lags behind the maturity of the container device-plugin ecosystem — a real, scoped gap
The Big Picture
CONTAINER POD VM VIA KUBEVIRT
────────────── ───────────────
Pod VirtualMachineInstance
└── container └── virt-launcher Pod
runs directly on the └── QEMU/KVM process
node's kernel via running a REAL VM,
containerd/runc with its own kernel
(Linux, Windows,
Scheduling, networking, anything QEMU
resource limits — standard supports)
Kubernetes primitives
Scheduling, networking, resource
limits — SAME standard Kubernetes
primitives, wrapping a VM instead
What is KubeVirt? It’s the project that makes a virtual machine look like just another workload to Kubernetes — the VM runs inside a virt-launcher pod, so everything Kubernetes already does for scheduling, networking policy, and resource accounting applies to the VM too, without Kubernetes itself needing to understand virtualization at all.
The Core Abstraction: VirtualMachine, VirtualMachineInstance, and virt-launcher
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: legacy-app-vm
spec:
running: true
template:
spec:
domain:
cpu: {cores: 2}
memory: {guest: 4Gi}
devices:
disks:
- name: rootdisk
disk: {bus: virtio}
volumes:
- name: rootdisk
dataVolume: {name: legacy-app-disk}
$ kubectl get vmi
NAME AGE PHASE IP NODENAME
legacy-app-vm 2m Running 10.244.1.15 worker-03
$ kubectl get pods
NAME READY STATUS
virt-launcher-legacy-app-vm-x7k2l 2/2 Running
# ^^^^^^^^^^^^ — this IS the pod hosting the real QEMU process
VirtualMachine is the persistent declaration (should this VM exist, running or stopped); VirtualMachineInstance is the actual running instance; virt-launcher is the pod — an ordinary Kubernetes pod from the scheduler’s point of view — that contains the QEMU process actually executing the VM. This layering mirrors Deployment/ReplicaSet/Pod deliberately, the same pattern CAPI (EP05) uses for Cluster/Machine.
Why Run VMs on Kubernetes At All
The honest case for KubeVirt isn’t “VMs are better than containers” — it’s consolidation. Most real infrastructure estates have some workloads that genuinely can’t be containerized cleanly: legacy applications tightly coupled to a specific OS version, Windows-only software, or workloads with licensing/compliance requirements demanding a VM-level boundary. Without KubeVirt, those workloads need an entirely separate VM infrastructure stack — its own hypervisor management, its own networking, its own monitoring — running alongside Kubernetes rather than on it. KubeVirt collapses that into one control plane, one set of operational tooling, one team’s expertise, for both categories of workload.
Networking and Storage for VMs: Multus, DataVolumes, CDI
# Multus lets a VM's pod attach to more than one network —
# often needed for VMs expecting a dedicated network interface,
# unlike a typical single-network container workload
$ kubectl get network-attachment-definitions
# CDI (Containerized Data Importer) gets an existing VM disk image
# into the cluster as a DataVolume
$ kubectl apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: legacy-app-disk
spec:
source:
http:
url: "https://images.example.com/legacy-app.qcow2"
pvc:
accessModes: ["ReadWriteOnce"]
resources:
requests: {storage: 20Gi}
EOF
Regular container images assume a filesystem layer model that doesn’t map cleanly onto “import this existing 20GB qcow2 disk image a legacy app already runs from.” CDI exists specifically to bridge that gap — pulling real VM disk images from HTTP, S3, or a registry into a DataVolume a VirtualMachine can boot from.
Live Migration: KubeVirt’s Answer to Node Maintenance
$ virtctl migrate legacy-app-vm
VM legacy-app-vm was scheduled to migrate
$ kubectl get vmim
NAME PHASE VMI
legacy-app-vm-migrate Running legacy-app-vm
# The VM keeps running, on a different node, with no restart —
# genuinely different from how Kubernetes handles pod disruption
A pod being evicted for node maintenance simply gets rescheduled — it restarts fresh somewhere else. A live-migrated VM keeps running throughout, its in-memory state transferred to the new node without a restart, the same live-migration capability traditional hypervisor platforms have offered for years. This is one behavior category where “VM on Kubernetes” is genuinely doing something a plain pod cannot.
⚠ Production Gotchas
Nodes need real KVM support — nested virtualization if the node itself is already a VM (common on cloud instances). This is the same underlying requirement that constrained Minikube’s VM drivers back in EP02, now at the production node level: if your cloud instance type doesn’t expose nested virtualization, KubeVirt workloads simply won’t schedule there.
VM disk I/O performance depends heavily on the underlying storage class and driver (virtio vs alternatives) — don’t assume container-workload storage benchmarks transfer to VM workloads on the same cluster.
Live migration isn’t guaranteed to succeed for every VM under every condition — a VM under heavy memory-write load, or a migration across a congested network path, can fail or take far longer than expected. Test migration behavior under realistic load before depending on it for a real maintenance window.
Windows VMs specifically need the right virtio drivers pre-installed in the image, or networking/storage won’t work post-boot — this is a common first-VM stumbling block distinct from anything Linux-VM users hit.
Quick Reference
kubectl get vm # VirtualMachine declarations
kubectl get vmi # running VirtualMachineInstances
kubectl get vmim # migration status
virtctl start / stop / restart <vm> # lifecycle control
virtctl console <vm> # serial console access
virtctl migrate <vm> # trigger a live migration
kubectl get datavolumes # CDI-imported disk images
Contribution Opportunity: Device-Plugin Parity for VM Workloads
The limitation: Kubernetes’ device-plugin ecosystem for GPUs and other specialized hardware is mature and well-supported for container workloads. Extending that same hardware access cleanly into KubeVirt-managed VMs — GPU passthrough with the same operational ergonomics containers already have — is real, current work with meaningfully more rough edges than the container-side equivalent.
Why it’s hard to fix: Passing a physical device through to a VM (as opposed to a container, which can often share the host’s device more directly) involves IOMMU configuration, PCI passthrough mechanics, and driver considerations inside the guest OS that simply don’t exist for container device access — it’s a genuinely harder problem, not a lagging implementation of an equally-easy one.
What a contribution-shaped fix looks like: KubeVirt’s own documentation and issue tracker identify specific, scoped GPU/device-passthrough gaps — particular hardware combinations without a documented working configuration, or specific device-plugin integrations that work for containers but haven’t been extended to the VM path. Reproducing one of these documented gaps on real hardware you have access to, and contributing either the fix or a verified, tested configuration guide back to the project, is exactly the kind of practitioner-shaped contribution this series has pointed toward in every episode — you need access to the hardware and patience to reproduce, not novel research.
Key Takeaways
- KubeVirt makes a VM look like an ordinary Kubernetes workload by wrapping it in a
virt-launcherpod — the same scheduling, networking, and resource primitives apply to both - The real case for running VMs on Kubernetes is infrastructure consolidation — one control plane for both container and VM workloads, not VM enthusiasm for its own sake
- Multus and CDI solve the networking and storage problems that don’t map cleanly from the container model onto VM disk images and multi-interface networking
- Live migration is a genuine capability gap between VMs and plain pods — Kubernetes’ own disruption model for pods has no equivalent
- The clearest current contribution opportunity is closing specific, documented device-passthrough gaps for VM workloads — hardware-access work, not abstract feature design
Series Wrap: From User to Contributor
Ten episodes, ten tools, and one repeated pattern: every project in this series exists because something else fell short — k3s and MicroK8s because upstream Kubernetes was too heavy for the edge, Karpenter because Cluster Autoscaler’s node-group abstraction had a ceiling, Crossplane because Terraform’s model didn’t fit a fully Kubernetes-native platform team. Each one of those gaps was found by someone who was using the previous tool closely enough to feel exactly where it stopped working.
That’s the actual path from user to contributor this series has been pointing at in every Contribution Opportunity section: not a certification, not a novel research breakthrough — close, careful attention to a tool you already run, precise enough to name what’s actually missing and specific enough to describe what fixing it would look like. Every gap named across these ten episodes is real, documented, and waiting for exactly that kind of attention.
Get future series and episodes in your inbox → linuxcent.com/subscribe