KubeVirt: Running VMs in Kubernetes

KubeVirt extends Kubernetes to manage virtual machines alongside containers. This article covers installation, configuration, and running VMs in our homelab.

What is KubeVirt?

KubeVirt is a Kubernetes operator that adds VM management capabilities:

Why KubeVirt?

Traditional Approach:

KubeVirt Approach:

Architecture

┌─────────────────────────────────────┐
│   Kubernetes API Server             │
│   + KubeVirt CRDs                   │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│   KubeVirt Operator                 │
│   - virt-api (API server)           │
│   - virt-controller (VM lifecycle)  │
│   - virt-handler (node agent)       │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│   VM Pod (virt-launcher)            │
│   └─ QEMU/KVM (actual VM)           │
└─────────────────────────────────────┘

Installation

Prerequisites

Hardware Virtualization:

# Check CPU support
talosctl -e 192.168.1.100 --nodes 192.168.1.100 read /proc/cpuinfo | grep -E "vmx|svm"
# Should show: vmx (Intel) or svm (AMD)

BIOS Settings:

Install KubeVirt

# Set version
export KUBEVIRT_VERSION=v1.1.2

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

# Wait for operator
kubectl wait --for=condition=ready pod -n kubevirt-system -l kubevirt.io=virt-operator --timeout=120s

# Install KubeVirt CR
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml

# Wait for installation
kubectl wait --for=condition=ready pod -n kubevirt-system -l kubevirt.io=virt-handler --timeout=300s

Verify Installation

# Check pods
kubectl get pods -n kubevirt-system

# Expected:
# NAME                               READY   STATUS
# virt-api-xxxxx                     1/1     Running
# virt-controller-xxxxx              1/1     Running
# virt-handler-xxxxx                  1/1     Running
# virt-operator-xxxxx                1/1     Running

# Check CRDs
kubectl get crd | grep kubevirt
# virtualmachines.kubevirt.io
# virtualmachineinstances.kubevirt.io
# etc.

Creating Your First VM

Simple Ubuntu VM

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: ubuntu-vm
  namespace: default
spec:
  running: true
  template:
    metadata:
      labels:
        kubevirt.io/domain: ubuntu-vm
    spec:
      domain:
        cpu:
          cores: 2
        memory:
          guest: 4Gi
        devices:
          disks:
          - name: rootdisk
            disk:
              bus: virtio
          - name: cloudinitdisk
            disk:
              bus: virtio
          interfaces:
          - name: default
            masquerade: {}
      networks:
      - name: default
        pod: {}
      volumes:
      - name: rootdisk
        persistentVolumeClaim:
          claimName: ubuntu-vm-disk
      - name: cloudinitdisk
        cloudInitNoCloud:
          userData: |
            #cloud-config
            password: ubuntu
            chpasswd:
              expire: false
            ssh_authorized_keys:
              - ssh-rsa AAAAB3...

Create PVC for VM Disk

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ubuntu-vm-disk
spec:
  storageClassName: local-path
  resources:
    requests:
      storage: 20Gi

Apply VM

kubectl apply -f vm-ubuntu.yaml
kubectl get vms
# NAME        AGE   STATUS    READY
# ubuntu-vm   10s   Running   True

VM Management

Start/Stop VM

Via kubectl:

# Stop
kubectl patch vm ubuntu-vm --type merge -p '{"spec":{"running":false}}'

# Start
kubectl patch vm ubuntu-vm --type merge -p '{"spec":{"running":true}}'

Via virtctl:

# Install virtctl
export VIRTCTL_VERSION=v1.1.2
curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/${VIRTCTL_VERSION}/virtctl-${VIRTCTL_VERSION}-darwin-amd64
chmod +x virtctl
sudo mv virtctl /usr/local/bin/

# Start/Stop
virtctl start ubuntu-vm
virtctl stop ubuntu-vm
virtctl restart ubuntu-vm

Via Custom Console:

Console Access

VNC Console:

virtctl console ubuntu-vm

SSH (if configured):

# Get VM IP
kubectl get vmi ubuntu-vm -o jsonpath='{.status.interfaces[0].ipAddress}'

# SSH
ssh user@<vm-ip>

VM Status

# List VMs
kubectl get vms

# Detailed status
kubectl describe vm ubuntu-vm

# VM Instance (actual running VM)
kubectl get vmi ubuntu-vm

# Logs
kubectl logs -n kubevirt-system virt-handler-xxxxx

Networking

Pod Network (Default)

VMs get IP from pod network:

networks:
- name: default
  pod: {}
interfaces:
- name: default
  masquerade: {}

Bridge Network (Advanced)

For direct network access:

Storage

PVC for VM Disks

volumes:
- name: rootdisk
  persistentVolumeClaim:
    claimName: vm-disk

DataVolumes (CDI)

For importing ISOs/images:

apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: ubuntu-iso
spec:
  source:
    http:
      url: https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso
  pvc:
    storageClassName: local-path
    resources:
      requests:
        storage: 10Gi

Example VMs

Ubuntu Server

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: ubuntu-server
spec:
  running: true
  template:
    spec:
      domain:
        cpu:
          cores: 4
        memory:
          guest: 8Gi
        devices:
          disks:
          - name: root
            disk: {}
          interfaces:
          - name: default
            masquerade: {}
      volumes:
      - name: root
        persistentVolumeClaim:
          claimName: ubuntu-disk

Windows VM

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: windows-vm
spec:
  running: false
  template:
    spec:
      domain:
        cpu:
          cores: 4
        memory:
          guest: 8Gi
        devices:
          disks:
          - name: windows-disk
            disk:
              bus: sata
          - name: windows-iso
            cdrom: {}
          interfaces:
          - name: default
            masquerade: {}
      volumes:
      - name: windows-disk
        persistentVolumeClaim:
          claimName: windows-disk
      - name: windows-iso
        dataVolume:
          name: windows-iso-dv

Performance

Hardware Virtualization

KubeVirt uses hardware virtualization (VT-x/AMD-V):

Resource Allocation

resources:
  requests:
    cpu: 2
    memory: 4Gi
  limits:
    cpu: 4
    memory: 8Gi

Troubleshooting

VM Won’t Start

Check VMI status:

kubectl get vmi ubuntu-vm
kubectl describe vmi ubuntu-vm

Check virt-handler logs:

kubectl logs -n kubevirt-system -l kubevirt.io=virt-handler

Verify hardware virtualization:

talosctl -e 192.168.1.100 --nodes 192.168.1.100 read /proc/cpuinfo | grep vmx

VM Stuck in Pending

Check PVC:

kubectl get pvc
kubectl describe pvc vm-disk

Check node resources:

kubectl describe node r430-k8s-master

Console Not Working

Install virtctl:

# Required for console access
virtctl console ubuntu-vm

Check VNC service:

kubectl get svc -n kubevirt-system

Best Practices

  1. Resource Limits - Set CPU/memory limits to prevent resource exhaustion
  2. Storage Planning - Allocate appropriate disk sizes
  3. Backup Strategy - Backup PVCs regularly
  4. Network Isolation - Use NetworkPolicies for security
  5. Monitoring - Monitor VM resource usage
  6. Naming Convention - Use descriptive names

Use Cases

Development Environments

Legacy Applications

Testing

Comparison: Containers vs VMs

FeatureContainersVMs (KubeVirt)
Startup TimeSecondsMinutes
Resource UsageLowHigher
IsolationProcessFull OS
Use CaseCloud-native appsLegacy apps, full OS
ManagementSame (kubectl)Same (kubectl)

Future Enhancements

Conclusion

KubeVirt brings VM management into Kubernetes:

Perfect for homelabs that need both containers and VMs.


Next: Troubleshooting Guide