KubeVirt
8 min read
KubeVirt is a Kubernetes add-on that runs full virtual machines as native cluster workloads, alongside containers. VMs are defined declaratively as Custom Resources and scheduled, networked, and stored using the same primitives as Pods. Under the hood each VM is a QEMU/KVM process managed by libvirt, wrapped in a regular Pod — so it benefits from Kubernetes scheduling, namespaces, RBAC, services, and storage.
It exists to bridge two worlds: workloads that can't (yet) be containerized — legacy apps, Windows, appliances, kernels needing full isolation — can live in the same cluster and CI/CD flow as your containers, instead of a separate hypervisor stack.
How it works
KubeVirt is installed and reconciled by the virt-operator. Its runtime components run in the kubevirt namespace:
| Component | Role |
|---|---|
virt-operator | Installs/upgrades KubeVirt and manages component lifecycle |
virt-api | Extends the Kubernetes API with the VM CRDs and subresources |
virt-controller | Cluster-level controller; creates a launcher Pod per running VM |
virt-handler | Per-node DaemonSet (like a kubelet for VMs); talks to libvirt |
virt-launcher | One Pod per VM; hosts the libvirt + QEMU process for that guest |
The mental model maps onto Kubernetes:
• VirtualMachine ≈ Deployment — the persistent, declarative definition with a desired run state.
• VirtualMachineInstance (VMI) ≈ Pod — a single running VM. A VM owns/creates one VMI when running.
Hardware virtualization (/dev/kvm) is strongly recommended. Without it, set spec.configuration.developerConfiguration.useEmulation: true on the KubeVirt CR for slow software emulation (fine for testing). Requires the apiserver to run with --allow-privileged=true.
Install
KubeVirt ships an operator + a CR that triggers the actual install:
sh
export RELEASE=$(curl -sL https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt)
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-cr.yaml
kubectl -n kubevirt wait kv kubevirt --for condition=Available
On bare-metal distros like k3s it works out of the box once kvm and vhost_net modules are loaded on the nodes. Most useful deployments also install CDI (see below) for disk image management.
virtctl
virtctl is the companion CLI for the VM-specific operations kubectl can't do (console, VNC, image upload, migration). Grab the binary from the releases page or install it as a kubectl plugin via krew:
sh
kubectl krew install virt # then use `kubectl virt <cmd>`
Defining a VM
A minimal VM booting a CirrOS container disk with cloud-init:
yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: testvm
spec:
runStrategy: Halted # start stopped; see Run strategies
template:
metadata:
labels:
kubevirt.io/vm: testvm
spec:
domain:
cpu:
cores: 1
resources:
requests:
memory: 1Gi
devices:
disks:
- name: containerdisk
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: quay.io/containerdisks/fedora:latest
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#cloud-config
password: fedora
chpasswd: { expire: false }
ssh_authorized_keys:
- ssh-ed25519 AAAA... me@host
spec.template is the VMI spec (analogous to a Pod template). domain describes the virtual hardware: CPU, memory, disks, and interfaces.
Run strategies
spec.runStrategy controls the VM's desired power state (mutually exclusive with the older boolean spec.running):
| Strategy | Behavior |
|---|---|
Always | Always keep a VMI running; respawn on any termination (= running: true) |
RerunOnFailure | Restart only after an uncontrolled failure (crash/node loss), not a clean guest shutdown |
Once | Run a single VMI to completion, never restart |
Manual | Never auto start/stop; user drives it with virtctl start/stop/restart |
Halted | Ensure no VMI runs (= running: false) |
Managing VMs
Lifecycle is driven with virtctl (or kubectl virt):
sh
virtctl start testvm
virtctl stop testvm
virtctl stop testvm --grace-period 0 --force # hard power-off
virtctl restart testvm
virtctl pause vm testvm # freeze CPU/IO (libvirt suspend)
virtctl unpause vm testvm
virtctl migrate testvm # trigger live migration
Inspect with plain kubectl:
sh
kubectl get vm # persistent definitions + run state
kubectl get vmi # running instances (IP, node, phase)
kubectl get vmi testvm -o yaml
Deleting a VMI stops/destroys that running instance; deleting the VM removes the definition entirely.
Generating manifests
virtctl create vm scaffolds manifests, which pairs well with instance types:
sh
virtctl create vm --name example \
--instancetype u1.medium --preference fedora \
--volume-containerdisk src:quay.io/containerdisks/fedora:latest | kubectl apply -f -
Accessing VMs
sh
virtctl console testvm # serial console
virtctl vnc testvm # graphical console (needs a local VNC viewer)
virtctl vnc --proxy-only testvm
virtctl ssh user@testvm # SSH (KubeVirt brokers the connection)
virtctl scp file user@testvm:/path
virtctl port-forward vm/testvm 8080:80
To expose a VM through a Kubernetes Service, label the VMI template so the pod is selectable, then create the Service (or use the shortcut):
sh
virtctl expose vm testvm --name testvm-ssh --port 22 --type NodePort
Service types ClusterIP (default), NodePort, and LoadBalancer are supported — see the Kubernetes Service concept.
Disks and volumes
A disk device in the domain references a volume by name. Common volume sources:
| Volume source | Use |
|---|---|
containerDisk | Boot image shipped inside an OCI image (ephemeral, read-only base) |
persistentVolumeClaim | Back a disk with an existing PVC |
dataVolume | A CDI-managed PVC (import/clone/upload, see below) |
cloudInitNoCloud | Inject cloud-init userData/networkData |
cloudInitConfigDrive | Same, ConfigDrive datasource (e.g. for Windows/cloudbase-init) |
emptyDisk | Scratch disk, lives with the VMI |
ephemeral | Copy-on-write overlay over a read-only backing PVC |
hostDisk | Disk image on the node filesystem |
Disk bus is typically virtio (best performance); disks can also be presented as lun, cdrom, or carry a serial. Volumes can be hot-plugged into a running VM with virtctl addvolume/removevolume.
CDI (Containerized Data Importer)
CDI is the companion project that gets real disk images into PVCs. A DataVolume is a higher-level object that provisions a PVC and populates it — importing from a URL/registry, cloning another PVC, or receiving an upload.
yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: fedora-disk
spec:
source:
http:
url: https://download.fedoraproject.org/.../Fedora-Cloud-Base.qcow2
storage:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Reference it from a VM as a dataVolume volume, or inline it as a dataVolumeTemplates entry so the VM owns its disk's lifecycle. Other source types: registry (container image), pvc (clone), blank, and upload. Upload a local image directly:
sh
virtctl image-upload dv fedora-disk --size 10Gi --image-path ./fedora.qcow2
Networking
Each interface in domain.devices.interfaces binds to a network. The default is the pod network via a binding method:
• masquerade: {} — NAT behind the pod IP (default, simplest; works with any CNI).
• bridge: {} — bridge the VM directly onto the pod's interface.
• Plus sriov, macvtap, passt, slirp for specific needs.
Secondary networks attach via Multus + a NetworkAttachmentDefinition:
yaml
networks:
- name: default
pod: {}
- name: bridge-net
multus:
networkName: linux-bridge-net # NAD name
interfaces:
- name: default
masquerade: {}
- name: bridge-net
bridge: {}
Interfaces can also be hot-plugged at runtime. Standard Kubernetes NetworkPolicy applies to VMI pods.
Live migration
Move a running VM between nodes without downtime (memory is copied while it runs, then control hands over). Requires the LiveMigration feature gate and, crucially, ReadWriteMany (RWX) storage so both nodes can access the disk — e.g. Rook/Ceph or Longhorn.
sh
virtctl migrate testvm
virtctl migrate-cancel testvm
Or declaratively:
yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstanceMigration
metadata:
name: migrate-testvm
spec:
vmiName: testvm
Migration is what makes node maintenance (drain/upgrade) non-disruptive for VMs.
Snapshots and restore
With a CSI driver that supports VolumeSnapshotClass, you can snapshot a VM's disks and configuration:
yaml
apiVersion: snapshot.kubevirt.io/v1beta1
kind: VirtualMachineSnapshot
metadata:
name: snap-testvm
spec:
source:
apiGroup: kubevirt.io
kind: VirtualMachine
name: testvm
Restore (stop the VM first):
yaml
apiVersion: snapshot.kubevirt.io/v1beta1
kind: VirtualMachineRestore
metadata:
name: restore-testvm
spec:
target:
apiGroup: kubevirt.io
kind: VirtualMachine
name: testvm
virtualMachineSnapshotName: snap-testvm
Even without a snapshot-capable storage class, the VM's configuration is still captured.
Instance types and preferences
Rather than repeating CPU/memory and device tuning in every VM, factor them out into reusable resources (similar to T-shirt sizes + OS profiles):
• VirtualMachineInstancetype / VirtualMachineClusterInstancetype — resource shape (CPU, memory, dedicated CPUs, GPUs). Namespaced vs. cluster-wide.
• VirtualMachinePreference / VirtualMachineClusterPreference — workload-specific preferences (preferred bus, machine type, features) the guest OS wants.
yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: example
spec:
instancetype:
kind: VirtualMachineClusterInstancetype
name: u1.medium
preference:
kind: VirtualMachineClusterPreference
name: fedora
runStrategy: Always
template:
spec:
domain:
devices: {}
volumes:
- name: disk
dataVolume:
name: fedora-disk
KubeVirt can even infer the right instance type/preference from a labeled boot volume (inferFromVolume). A community set of common instance types and preferences can be deployed ready-made. On creation the referenced spec is snapshotted into a ControllerRevision, so later edits to the instance type don't silently change running VMs.
Resources & key concepts at a glance
| Resource | Analogy / purpose |
|---|---|
VirtualMachine | Persistent VM definition with a run state (≈ Deployment) |
VirtualMachineInstance (VMI) | A single running VM (≈ Pod) |
VirtualMachineInstanceMigration | Request to live-migrate a VMI |
VirtualMachineSnapshot / Restore | Point-in-time backup and restore |
DataVolume (CDI) | Provision + populate a PVC for VM disks |
VirtualMachineInstancetype / Preference | Reusable size + OS profiles |
KubeVirt is the engine behind larger platforms like OpenShift Virtualization. It composes naturally with the rest of a cluster — define VMs as YAML manifests in GitOps, give them RWX storage for migration, and manage them with kubectl/virtctl. See also general Virtual Machine concepts.