Run virtual machines with KubeVirt
A KubeVirt VM is one of the heaviest stateful workloads you can put on Syself Autopilot. Nodes are ephemeral: they are drained and rebuilt on upgrades, rollouts, and repairs. A container restarts on another node in seconds, but a virtual machine holding 8Gi of memory and a disk on local NVMe does not move so cleanly, and it can lose its in-memory state and local disk when its node goes away. So before you reach for the manifest, understand what happens the first time that node is drained.
What happens to your VM when the node drains
When a bare-metal node is drained, the VM’s pod (the virt-launcher) is evicted like any other, and KubeVirt reschedules the VM onto another node, but as a fresh VM from its disk image. If that disk sat on the node’s local NVMe , its contents stayed on the old node and are gone. Take backups of all data on local NVMe disks and keep anything you cannot lose on network-backed storage or outside the VM.
A heavy VM shuts down cleanly only if the drain waits long enough. How long the drain waits before the node is torn down is set per pool by spec.topology.workers.machineDeployments[].deletion.nodeDrainTimeoutSeconds. Set it high enough on the pool running your VMs that a large VM can flush its state and power off instead of being cut off mid-write. A pool is one machineDeployments entry in your Cluster resource; see Node pools and machine deployments for where to set this field.
It runs on bare metal only
KubeVirt needs hardware virtualization (KVM). Hetzner bare-metal servers have it; Hetzner Cloud instances lack nested virtualization, so VMs will not run there. If your cluster has no bare-metal nodes, add a bare-metal pool first. It is the same KVM requirement as the secure runtime , and the two pair cleanly: KubeVirt for full virtual machines, the secure runtime for pod-level VM isolation.
Install and define a VM
KubeVirt is user-managed upstream software running in your cluster, not a platform component. Pin a specific release rather than installing from releases/latest/download, and design around the fact that node churn will reschedule your VMs.
Install KubeVirt and CDI (the Containerized Data Importer for disk images), pinning a version:
$ VERSION=v1.4.0
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt-operator.yaml
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt-cr.yaml
Then declare a VirtualMachine:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: builder
spec:
running: true
template:
spec:
domain:
cpu: {cores: 4}
resources:
requests: {memory: 8Gi}
devices:
disks:
- name: root
disk: {bus: virtio}
volumes:
- name: root
dataVolume: {name: builder-root}
Where it fits and what to reserve
A VM is worth its overhead when a container cannot provide the required isolation or environment. Common cases include isolated CI/CD runners, where each build runs in a clean machine sealed off from the host and the next build; legacy applications that require a full operating system or a specific kernel and cannot be containerized; and multi-tenant platforms, where each tenant receives a strongly isolated VM.
A running VM reserves its CPU and memory like a Guaranteed pod, so size the bare-metal pool for the VMs you intend to run plus headroom; otherwise a VM and your containers end up competing for the same cores. For latency-sensitive VMs on a multi-socket server, the same NUMA alignment rules apply.
Run NUMA-aligned workloads
Pin a pod's CPUs and memory to one socket on multi-socket bare metal so latency-sensitive databases and real-time services stop paying the cross-socket tax.
Container runtimes
The three runtime classes on Syself Autopilot (standard, runc, and secure), what each one is for, and how to select one per workload.