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
virtctlinstalled- 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
777is 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:
- KubeVirt Operator
- 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/kvmunavailable- 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.