Installing KubeVirt on an Existing Kubernetes Cluster

Install KubeVirt on an existing Kubernetes cluster and enable Kubernetes-native virtual machines. This guide covers KubeVirt, CDI, virtctl, NFS storage setup, dynamic provisioning, StorageClass creation, and deploying your first VM.

By Network Nuts Team · Published 2026-08-08

Kubernetes is designed primarily for running containers. But many organizations still have workloads that cannot immediately be containerized.

This creates an interesting requirement:

What if you could run virtual machines alongside containers inside the same Kubernetes cluster?

That is exactly what KubeVirt provides.

KubeVirt extends Kubernetes with virtualization capabilities, allowing virtual machines to be created, scheduled, managed, and monitored using the same Kubernetes APIs and tools you already use for containers.

In this guide, we will install KubeVirt on an existing Kubernetes cluster.

We will also assume that the cluster does not currently have a StorageClass, which is a common situation in bare-metal, lab, kubeadm, and self-managed Kubernetes environments.

As part of the setup, we will configure a simple NFS-backed dynamic StorageClass that KubeVirt can use for virtual machine disks.


What We Are Going to Build

Our environment will look approximately like this:

                    Kubernetes Cluster
                           |
          +----------------+----------------+
          |                                 |
      Containers                       Virtual Machines
                                             |
                                          KubeVirt
                                             |
                                      PersistentVolumeClaim
                                             |
                                      NFS StorageClass
                                             |
                                         NFS Server

By the end of this guide, you will have:

  • KubeVirt installed
  • virtctl installed
  • KubeVirt CRDs available
  • NFS dynamic provisioning configured
  • A default Kubernetes StorageClass
  • Containerized Data Importer (CDI) installed
  • Persistent storage available for VM disks
  • A simple virtual machine running on Kubernetes

What Is KubeVirt?

KubeVirt is an open-source project that adds virtualization APIs to Kubernetes.

Instead of managing virtual machines using platforms such as:

  • VMware ESXi
  • vCenter
  • Proxmox
  • Hyper-V
  • OpenStack

you can define virtual machines using Kubernetes YAML.

For example:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: demo-vm
spec:
  running: true

The virtual machine becomes another Kubernetes resource.

You can therefore interact with VMs using commands such as:

kubectl get vm

and:

kubectl get vmi

KubeVirt internally uses technologies including:

  • KVM
  • QEMU
  • libvirt concepts
  • Kubernetes scheduling
  • Kubernetes networking
  • Kubernetes storage

Prerequisites

Before installing KubeVirt, you should already have a working Kubernetes cluster.

Verify it:

kubectl get nodes

Example:

NAME       STATUS   ROLES           AGE   VERSION
master     Ready    control-plane   10d   v1.35.x
worker1    Ready    <none>          10d   v1.35.x
worker2    Ready    <none>          10d   v1.35.x

Also verify that your cluster networking is working:

kubectl get pods -A

Your CNI components should be running successfully.


Hardware Virtualization Requirement

KubeVirt normally uses KVM hardware virtualization.

Your Kubernetes nodes should therefore expose virtualization extensions.

On an Intel system, look for:

vmx

On AMD:

svm

Check using:

egrep -c '(vmx|svm)' /proc/cpuinfo

If the output is greater than 0, virtualization extensions are visible.

You can also check:

ls -l /dev/kvm

A working node should normally have:

/dev/kvm

available.


What If Kubernetes Is Running Inside Virtual Machines?

This is common in labs.

For example:

Physical Server
      |
   Proxmox
      |
 Kubernetes VMs
      |
   KubeVirt
      |
 KubeVirt VMs

This is called nested virtualization.

Your hypervisor must expose virtualization extensions to the Kubernetes VMs.

Otherwise KubeVirt cannot use KVM normally.

For lab environments, KubeVirt can also use software emulation, although performance will be significantly worse.

We will discuss that later.


Step 1: Check Whether a StorageClass Already Exists

Run:

kubectl get storageclass

or:

kubectl get sc

If your cluster was created using something like kubeadm, you may see:

No resources found

This is completely normal.

Kubernetes itself does not automatically provide persistent storage.

Managed Kubernetes platforms usually install storage integrations automatically, but bare-metal Kubernetes normally requires you to configure storage yourself.

Because virtual machines require persistent disks, we need to solve this before deploying useful VMs.


Step 2: Prepare an NFS Server

For a simple lab environment, NFS is one of the easiest ways to provide dynamic storage.

Our example architecture will be:

Kubernetes Nodes
       |
       |
       v
192.168.1.50
   NFS Server
       |
       v
/srv/kubernetes

Assume the NFS server has the IP:

192.168.1.50

and exports:

/srv/kubernetes

Step 3: Install NFS Server

On a RHEL-compatible server:

dnf install nfs-utils -y

Create the directory:

mkdir -p /srv/kubernetes

For a simple lab configuration:

chmod 777 /srv/kubernetes

777 is convenient for a lab but should not be considered a good production security configuration.

Add the export:

vi /etc/exports

Add:

/srv/kubernetes *(rw,sync,no_root_squash,no_subtree_check)

Reload exports:

exportfs -rav

Enable NFS:

systemctl enable --now nfs-server

Verify:

exportfs -v

Step 4: Install NFS Client Packages on Kubernetes Nodes

Each Kubernetes node that may access the storage should have the NFS client utilities installed.

On RHEL-based nodes:

dnf install nfs-utils -y

On Ubuntu:

apt update
apt install nfs-common -y

Test the export manually:

showmount -e 192.168.1.50

You should see something similar to:

Export list for 192.168.1.50:

/srv/kubernetes *

Step 5: Install the NFS Dynamic Provisioner

Having an NFS server alone does not automatically create Kubernetes PersistentVolumes.

We will install the NFS Subdir External Provisioner.

It dynamically creates a directory on the NFS server whenever Kubernetes receives a new PVC.

The flow becomes:

PVC Created
    |
    v
StorageClass
    |
    v
NFS Provisioner
    |
    v
Directory created under /srv/kubernetes

The easiest installation method is Helm.

Add the repository:

helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

Update Helm repositories:

helm repo update

Create a namespace:

kubectl create namespace nfs-provisioner

Install the provisioner:

helm install nfs-provisioner \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace nfs-provisioner \
--set nfs.server=192.168.1.50 \
--set nfs.path=/srv/kubernetes

Replace:

192.168.1.50

with the IP of your NFS server.


Step 6: Verify the NFS Provisioner

Check the pod:

kubectl get pods -n nfs-provisioner

You should see something similar to:

NAME                                                 READY   STATUS
nfs-provisioner-nfs-subdir-external-provisioner     1/1     Running

Now check StorageClasses:

kubectl get sc

You should see something similar to:

NAME         PROVISIONER                                     RECLAIMPOLICY
nfs-client   cluster.local/nfs-provisioner-nfs-subdir...     Delete

Step 7: Make the NFS StorageClass the Default

KubeVirt-related tools such as CDI often work more conveniently when Kubernetes has a default StorageClass.

Set nfs-client as the default:

kubectl patch storageclass nfs-client \
-p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Verify:

kubectl get sc

You should now see:

NAME                   PROVISIONER
nfs-client (default)   cluster.local/nfs-provisioner-nfs-subdir-external-provisioner

Step 8: Test the StorageClass Before Installing KubeVirt

Before introducing KubeVirt, make sure ordinary Kubernetes PVC provisioning works.

Create:

vi test-pvc.yaml

Add:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Create it:

kubectl apply -f test-pvc.yaml

Verify:

kubectl get pvc

You should see:

NAME       STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS
test-pvc   Bound    pvc-xxxx    1Gi        RWO            nfs-client

Also check:

kubectl get pv

If the PVC reaches:

Bound

your dynamic provisioning is working.

Delete the test PVC:

kubectl delete pvc test-pvc

Step 9: Install KubeVirt

KubeVirt installation consists primarily of two components:

  1. KubeVirt Operator
  2. KubeVirt Custom Resource

The operator manages the KubeVirt deployment for us.

First define the KubeVirt version.

For example:

export KUBEVIRT_VERSION=$(curl -s \
https://api.github.com/repos/kubevirt/kubevirt/releases/latest \
| grep tag_name \
| cut -d '"' -f 4)

Check it:

echo $KUBEVIRT_VERSION

Step 10: Install the KubeVirt Operator

Run:

kubectl apply -f \
https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml

Verify:

kubectl get pods -n kubevirt

Initially, you should see the operator pods starting.


Step 11: Create the KubeVirt Custom Resource

Install KubeVirt itself:

kubectl apply -f \
https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml

Now monitor:

kubectl get pods -n kubevirt -w

Eventually you should see components such as:

virt-api
virt-controller
virt-handler
virt-operator

running.


Understanding the Main KubeVirt Components

The KubeVirt installation introduces several important components.

virt-api

Provides the Kubernetes API extensions required for virtualization operations.


virt-controller

Controls the lifecycle of virtual machines.

It watches KubeVirt resources and coordinates creation of VM workloads.


virt-handler

Runs on Kubernetes nodes.

It handles node-level virtualization operations and communicates with the underlying virtualization runtime.

Conceptually:

Kubernetes API
      |
 virt-controller
      |
 virt-handler
      |
     KVM
      |
     VM

virt-launcher

Each running VM gets a corresponding virt-launcher pod.

For example:

VirtualMachine
      |
VirtualMachineInstance
      |
virt-launcher Pod
      |
 QEMU / KVM
      |
 Virtual Machine

This is one of the most important concepts to understand about KubeVirt.

The virtual machine itself does not become a container.

Instead, a Kubernetes pod manages the QEMU/KVM process responsible for running the VM.


Step 12: Verify the KubeVirt Installation

Run:

kubectl get kubevirt -n kubevirt

You should eventually see:

NAME       AGE   PHASE
kubevirt   5m    Deployed

Check pods:

kubectl get pods -n kubevirt

Everything should be:

Running

Step 13: Install virtctl

virtctl is the KubeVirt command-line utility.

Think of it as a companion to kubectl.

It provides VM-specific commands including:

virtctl start
virtctl stop
virtctl restart
virtctl console
virtctl vnc

Download the appropriate binary from the KubeVirt release.

For Linux x86-64, it typically looks like:

curl -L \
-o virtctl \
https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64

Make it executable:

chmod +x virtctl

Move it into your path:

mv virtctl /usr/local/bin/

Verify:

virtctl version

Step 14: Check KubeVirt CRDs

Run:

kubectl api-resources | grep kubevirt

You should see resources including:

virtualmachines
virtualmachineinstances
virtualmachineinstancemigrations
virtualmachinepools

The two most important are:

VirtualMachine

and:

VirtualMachineInstance

VirtualMachine vs VirtualMachineInstance

This distinction is similar to the relationship between a Kubernetes Deployment and the workload it creates.

A:

VirtualMachine

defines the desired VM configuration.

A:

VirtualMachineInstance

represents a currently running VM.

For example:

kubectl get vm

might show:

NAME       AGE   STATUS
demo-vm    10m   Running

while:

kubectl get vmi

shows the running instance.


Step 15: Install CDI

Installing KubeVirt gives Kubernetes the ability to run virtual machines.

However, we still need a convenient way to import VM disk images into PersistentVolumeClaims.

This is where Containerized Data Importer, or CDI, comes in.

CDI can:

  • Import VM images from HTTP
  • Clone VM disks
  • Upload local disk images
  • Import container disks
  • Populate PVCs automatically

The architecture becomes:

VM Image
   |
   v
  CDI
   |
   v
DataVolume
   |
   v
PVC
   |
   v
StorageClass
   |
   v
NFS

Step 16: Install CDI Operator

Get the latest CDI version:

export CDI_VERSION=$(curl -s \
https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest \
| grep tag_name \
| cut -d '"' -f 4)

Verify:

echo $CDI_VERSION

Install the operator:

kubectl apply -f \
https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml

Step 17: Create the CDI Resource

Install CDI:

kubectl apply -f \
https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-cr.yaml

Check:

kubectl get pods -n cdi

You should eventually see CDI components running.


Step 18: Verify DataVolume Support

Run:

kubectl api-resources | grep -i datavolume

You should see:

datavolumes

CDI introduces the DataVolume resource.

Instead of manually creating a PVC and copying a disk into it, you can now describe the disk source directly.


Step 19: Create Our First KubeVirt VM

For the first test, we can use a small CirrOS image.

Create:

vi cirros-vm.yaml

Add:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: cirros-vm
spec:
  runStrategy: Always

  dataVolumeTemplates:
    - metadata:
        name: cirros-disk
      spec:
        source:
          http:
            url: "https://download.cirros-cloud.net/0.6.3/cirros-0.6.3-x86_64-disk.img"

        storage:
          resources:
            requests:
              storage: 2Gi

  template:
    metadata:
      labels:
        kubevirt.io/domain: cirros-vm

    spec:
      domain:
        devices:
          disks:
            - name: rootdisk
              disk:
                bus: virtio

          interfaces:
            - name: default
              masquerade: {}

        resources:
          requests:
            memory: 512Mi

      networks:
        - name: default
          pod: {}

      volumes:
        - name: rootdisk
          dataVolume:
            name: cirros-disk

Create it:

kubectl apply -f cirros-vm.yaml

Step 20: Watch the VM Disk Import

Check DataVolumes:

kubectl get dv

Example:

NAME          PHASE       PROGRESS
cirros-disk   Importing   45%

Check again:

kubectl get dv

Eventually:

NAME          PHASE       PROGRESS
cirros-disk   Succeeded   100%

Behind the DataVolume, CDI created a PVC.

Check:

kubectl get pvc

You should see something similar to:

NAME          STATUS   CAPACITY   STORAGECLASS
cirros-disk   Bound    2Gi        nfs-client

This demonstrates how KubeVirt, CDI, Kubernetes storage, and NFS fit together.


Step 21: Check the Virtual Machine

Run:

kubectl get vm

Example:

NAME        AGE   STATUS    READY
cirros-vm   2m    Running   True

Now check the VirtualMachineInstance:

kubectl get vmi

You may see:

NAME        AGE   PHASE     IP
cirros-vm   2m    Running   10.244.2.15

Step 22: Check the virt-launcher Pod

Run:

kubectl get pods

You should see something similar to:

virt-launcher-cirros-vm-xxxxx

This pod is responsible for running the VM.

You can inspect it:

kubectl describe pod virt-launcher-cirros-vm-xxxxx

Step 23: Access the VM Console

Use:

virtctl console cirros-vm

You should eventually reach the VM console.

For CirrOS, typical credentials are:

username: cirros
password: gocubsgo

To exit the console, use:

Ctrl + ]

Starting and Stopping Virtual Machines

If the VM uses a manually controlled run strategy, you can start it using:

virtctl start cirros-vm

Stop it:

virtctl stop cirros-vm

Restart it:

virtctl restart cirros-vm

You can also inspect it with normal Kubernetes commands:

kubectl describe vm cirros-vm

Understanding KubeVirt Storage

For traditional Kubernetes applications, persistent storage often contains:

Database files
Application uploads
Configuration
Logs

For KubeVirt, a PVC can contain an entire VM disk:

PersistentVolumeClaim
         |
         v
   VM Disk Image
         |
         v
 Operating System
 Applications
 VM Data

A simplified architecture is:

VirtualMachine
      |
      v
DataVolume
      |
      v
PersistentVolumeClaim
      |
      v
StorageClass
      |
      v
Storage Backend

The backend could be:

  • NFS
  • Ceph
  • Ceph RBD
  • Longhorn
  • OpenEBS
  • local storage
  • CSI SAN storage
  • cloud block storage

Is NFS Good Storage for KubeVirt?

For a lab, proof-of-concept, training environment, or small non-critical deployment:

Yes.

NFS is convenient because:

  • It is easy to configure
  • Multiple Kubernetes nodes can access the same storage
  • Dynamic provisioning is straightforward
  • VM disks remain available if the VM moves between nodes

However, NFS may not provide the disk performance expected from serious virtualization workloads.

For production KubeVirt, you may want to investigate block-oriented storage such as:

Ceph RBD

or another CSI storage backend designed for VM workloads.


Important Limitation of the NFS Subdir Provisioner

The nfs-subdir-external-provisioner primarily provides filesystem-backed volumes.

This works well for labs and many simple VM workloads, but advanced KubeVirt scenarios can benefit significantly from storage systems that support:

  • Block mode volumes
  • Volume snapshots
  • Cloning
  • ReadWriteMany where necessary
  • CSI expansion
  • High IOPS
  • Low latency
  • Storage-aware live migration

Therefore:

NFS Subdir Provisioner

should be treated as an excellent learning and lab solution, rather than the automatic default for enterprise virtualization.


Testing Whether KVM Is Being Used

Check:

kubectl get kubevirt kubevirt -n kubevirt -o yaml

You can also inspect the nodes for /dev/kvm.

KubeVirt's node-level components should discover hardware virtualization support.

If /dev/kvm is unavailable because your Kubernetes cluster itself runs in VMs without nested virtualization, KubeVirt may not be able to launch VMs normally.


Enabling Software Emulation for a Lab

If nested virtualization is unavailable, KubeVirt can use software emulation.

This is useful for demonstrations but much slower than KVM.

Edit the KubeVirt resource:

kubectl edit kubevirt kubevirt -n kubevirt

Under:

spec:
  configuration:

add:

developerConfiguration:
  useEmulation: true

The relevant configuration becomes approximately:

spec:
  configuration:
    developerConfiguration:
      useEmulation: true

After the configuration updates, KubeVirt can run VM workloads without hardware KVM acceleration.

Again, this should primarily be considered a lab feature.


Useful KubeVirt Commands

List VMs:

kubectl get vm

List running instances:

kubectl get vmi

List VM disks:

kubectl get dv

List PVCs:

kubectl get pvc

Start a VM:

virtctl start VM_NAME

Stop a VM:

virtctl stop VM_NAME

Restart:

virtctl restart VM_NAME

Open console:

virtctl console VM_NAME

Describe VM:

kubectl describe vm VM_NAME

Check VM launcher pods:

kubectl get pods

Troubleshooting: VM Is Stuck in Scheduling

Start with:

kubectl describe vmi VM_NAME

and:

kubectl get events --sort-by=.lastTimestamp

Common reasons include:

  • /dev/kvm unavailable
  • insufficient CPU
  • insufficient RAM
  • node selectors
  • storage not mounted
  • PVC not Bound

Troubleshooting: PVC Is Pending

Check:

kubectl get pvc

If you see:

Pending

inspect it:

kubectl describe pvc PVC_NAME

Then verify:

kubectl get sc

Make sure you have a default StorageClass or explicitly reference the desired StorageClass.

Also check the provisioner:

kubectl get pods -n nfs-provisioner

Troubleshooting: NFS Provisioner Cannot Mount Storage

Verify that each node can reach the NFS server:

showmount -e 192.168.1.50

Try mounting manually:

mkdir /mnt/nfstest
mount -t nfs 192.168.1.50:/srv/kubernetes /mnt/nfstest

Verify:

mount | grep nfs

Then unmount:

umount /mnt/nfstest

Potential causes include:

  • Firewall blocking NFS
  • Missing nfs-utils
  • Incorrect export configuration
  • SELinux configuration
  • Wrong NFS server IP
  • Routing issues between Kubernetes nodes and the NFS server

Troubleshooting: DataVolume Import Fails

Check:

kubectl describe dv cirros-disk

Check CDI pods:

kubectl get pods -n cdi

Check importer pods:

kubectl get pods | grep importer

View logs:

kubectl logs POD_NAME

Common problems include:

  • Image URL unavailable
  • DNS failures
  • Internet connectivity problems
  • PVC allocation issues
  • Storage permissions
  • Insufficient disk space

Troubleshooting: VM Cannot Access the Network

A simple KubeVirt VM can use Kubernetes pod networking through:

interfaces:
  - name: default
    masquerade: {}

and:

networks:
  - name: default
    pod: {}

This effectively connects the VM to the Kubernetes pod network.

For more advanced networking, KubeVirt is commonly combined with Multus.

Multus allows VMs to have additional interfaces attached to:

  • VLANs
  • bridge networks
  • SR-IOV devices
  • physical networks
  • secondary CNIs

That becomes important when migrating more traditional virtualization workloads into Kubernetes.


Where KubeVirt Fits

A normal Kubernetes environment looks like:

Kubernetes
   |
   +-- Deployment
   +-- StatefulSet
   +-- DaemonSet
   +-- Pod

With KubeVirt:

Kubernetes
   |
   +-- Deployment
   +-- StatefulSet
   +-- DaemonSet
   +-- Pod
   |
   +-- VirtualMachine
          |
          +-- VirtualMachineInstance
                    |
                    +-- virt-launcher
                            |
                            +-- QEMU/KVM
                                    |
                                    +-- Guest OS

This means the Kubernetes API becomes the control plane for both:

Containers + Virtual Machines

Why Storage Is So Important for KubeVirt

Installing KubeVirt itself is relatively straightforward.

The more important architectural decision is often storage.

A container can frequently be recreated from an image.

A VM, however, usually contains substantial state:

VM
 |
 +-- Operating System
 +-- Installed packages
 +-- Configuration
 +-- Applications
 +-- Application data

Therefore a useful KubeVirt environment normally requires reliable persistent storage.

That is why our installation flow was:

Kubernetes
    |
    v
NFS Storage
    |
    v
StorageClass
    |
    v
KubeVirt
    |
    v
CDI
    |
    v
Virtual Machine

instead of installing KubeVirt alone.


Complete Installation Flow

The complete process can be summarized as:

Existing Kubernetes Cluster
          |
          v
Verify Nested Virtualization / KVM
          |
          v
Configure NFS Server
          |
          v
Install NFS Provisioner
          |
          v
Create nfs-client StorageClass
          |
          v
Mark StorageClass Default
          |
          v
Test PVC
          |
          v
Install KubeVirt Operator
          |
          v
Create KubeVirt CR
          |
          v
Install virtctl
          |
          v
Install CDI
          |
          v
Create DataVolume
          |
          v
Import VM Disk
          |
          v
Create VirtualMachine
          |
          v
virt-launcher
          |
          v
QEMU/KVM Virtual Machine

KubeVirt Does Not Replace Containers

One misconception is that KubeVirt converts virtual machines into containers.

It does not.

Instead, KubeVirt allows Kubernetes to orchestrate both workload types.

You might therefore have:

Kubernetes Cluster
 |
 +-- nginx container
 |
 +-- PostgreSQL StatefulSet
 |
 +-- Python API
 |
 +-- Windows Server VM
 |
 +-- Legacy Linux VM
 |
 +-- Vendor appliance VM

all controlled through the Kubernetes API.


When Is KubeVirt Particularly Useful?

KubeVirt becomes interesting when an organization wants to standardize infrastructure management around Kubernetes but still has VM-based applications.

Examples include:

Legacy applications

Applications that cannot easily be containerized can remain inside virtual machines.

VM migration projects

Organizations migrating from VMware, OpenStack, or traditional virtualization can gradually bring VM workloads into Kubernetes.

Mixed workloads

Applications may contain both VM and containerized components.

For example:

Frontend          Container
API               Container
Database          Container
Legacy ERP        VM
Windows Service   VM

Kubernetes-native infrastructure management

Instead of maintaining two separate management environments:

VMware APIs
+
Kubernetes APIs

teams can move toward:

Kubernetes API

as a unified infrastructure control plane.


Production Considerations

The installation in this article is intentionally simple and suitable for:

  • Training
  • Labs
  • Proofs of concept
  • Development
  • Architecture testing

For production, additional areas should be evaluated carefully.

These include:

  • Enterprise-grade CSI storage
  • Live migration support
  • Multus networking
  • VLAN integration
  • Load balancing
  • VM backup
  • Volume snapshots
  • Disaster recovery
  • CPU pinning
  • NUMA awareness
  • HugePages
  • SR-IOV
  • GPU passthrough
  • Node maintenance
  • VM high availability
  • Monitoring
  • Security policies

A production architecture may look more like:

                      Kubernetes
                           |
            +--------------+--------------+
            |                             |
        Containers                       VMs
                                          |
                                       KubeVirt
                                          |
                    +---------------------+--------------------+
                    |                     |                    |
                 Multus                 Ceph                  CDI
                    |                     |                    |
                  VLANs               VM Disks           Image Import

Final Thoughts

Installing KubeVirt is only one part of creating a Kubernetes virtualization platform.

You need three major pieces:

Compute
+
Networking
+
Storage

KubeVirt gives Kubernetes the virtualization compute layer.

Kubernetes CNI handles basic networking, while Multus can provide more advanced VM networking.

Persistent storage gives the virtual machines somewhere to keep their operating systems and data.

For a simple lab environment, the combination of:

Kubernetes
+
KubeVirt
+
CDI
+
NFS Subdir External Provisioner

provides an excellent platform for understanding how Kubernetes-based virtualization works.

Once everything is installed, a VM is no longer something that must be created through a traditional hypervisor interface.

It becomes another declarative Kubernetes resource:

kubectl apply -f vm.yaml

That single architectural shift is what makes KubeVirt so powerful.

Containers and virtual machines can coexist while Kubernetes provides a common API, scheduling system, security model, automation layer, and operational workflow for both.