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:

ComponentRole
virt-operatorInstalls/upgrades KubeVirt and manages component lifecycle
virt-apiExtends the Kubernetes API with the VM CRDs and subresources
virt-controllerCluster-level controller; creates a launcher Pod per running VM
virt-handlerPer-node DaemonSet (like a kubelet for VMs); talks to libvirt
virt-launcherOne 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):

StrategyBehavior
AlwaysAlways keep a VMI running; respawn on any termination (= running: true)
RerunOnFailureRestart only after an uncontrolled failure (crash/node loss), not a clean guest shutdown
OnceRun a single VMI to completion, never restart
ManualNever auto start/stop; user drives it with virtctl start/stop/restart
HaltedEnsure 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 sourceUse
containerDiskBoot image shipped inside an OCI image (ephemeral, read-only base)
persistentVolumeClaimBack a disk with an existing PVC
dataVolumeA CDI-managed PVC (import/clone/upload, see below)
cloudInitNoCloudInject cloud-init userData/networkData
cloudInitConfigDriveSame, ConfigDrive datasource (e.g. for Windows/cloudbase-init)
emptyDiskScratch disk, lives with the VMI
ephemeralCopy-on-write overlay over a read-only backing PVC
hostDiskDisk 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

ResourceAnalogy / purpose
VirtualMachinePersistent VM definition with a run state (≈ Deployment)
VirtualMachineInstance (VMI)A single running VM (≈ Pod)
VirtualMachineInstanceMigrationRequest to live-migrate a VMI
VirtualMachineSnapshot / RestorePoint-in-time backup and restore
DataVolume (CDI)Provision + populate a PVC for VM disks
VirtualMachineInstancetype / PreferenceReusable 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.