Cloud Native 20 min read

How to Run a Windows VM on Kubernetes with KubeVirt: Step‑by‑Step Guide

This tutorial explains how to leverage KubeVirt on a Kubernetes cluster to create and run a Windows 10 virtual machine, covering architecture, disk options, preparation, installation, image upload, VM definition, networking, VNC/RDP access, and CNI troubleshooting in a comprehensive, hands‑on manner.

Programmer DD
Programmer DD
Programmer DD
How to Run a Windows VM on Kubernetes with KubeVirt: Step‑by‑Step Guide

Recently I discovered that my Kubernetes cluster has many idle resources, so I decided to use them to run a Windows virtual machine. Since I only have a MacBook without Windows, I chose KubeVirt, a Red Hat open‑source project that runs VMs as containers via CRDs.

1. KubeVirt Architecture Design

KubeVirt implements several resources to manage virtual machines: VirtualMachineInstance (VMI): the smallest VM resource, similar to a Pod, representing a running VM instance. VirtualMachine (VM): provides management functions (start/stop/reboot) for a VMI, analogous to a StatefulSet with spec.replicas set to 1. VirtualMachineInstanceReplicaSet: like a ReplicaSet, ensures a specified number of VMIs are running and can be scaled with HPA.

The overall KubeVirt architecture includes:

virt-api : exposes KubeVirt‑specific APIs such as console, VNC, startvm, stopvm.

virt-controller : monitors and updates VMI objects and their associated Pods.

virt-handler : runs as a DaemonSet on each node, reports VMI status and manages the VM lifecycle.

virt-launcher : runs as a Pod for each VMI, containing libvirtd to start and manage the VM.

2. Disks and Volumes

KubeVirt supports various disk types for VM images:

PersistentVolumeClaim : uses a PVC for persistent storage (block or filesystem). In filesystem mode, /disk.img (RAW format) is used; in block mode, the block device is attached directly.

ephemeral : creates a copy‑on‑write layer on local storage; the layer is removed when the VM stops.

containerDisk : a Docker image that contains a VM image; it can be pulled from a registry but does not support persistence.

hostDisk : uses a host‑node disk image, similar to hostPath, optionally creating an empty image.

dataVolume : CDI feature that imports a VM image into a PVC from HTTP, S3, another PVC, or a container registry.

3. Preparation

Install libvirt and QEMU packages first:

# Ubuntu
apt install -y qemu-kvm libvirt-bin bridge-utils virt-manager

# CentOS
yum install -y qemu-kvm libvirt virt-install bridge-utils

Verify KVM hardware support:

$ virt-host-validate qemu
# All checks PASS

If hardware acceleration is unavailable, enable software emulation:

$ kubectl create namespace kubevirt
$ kubectl create configmap -n kubevirt kubevirt-config \
    --from-literal debug.useEmulation=true

4. Install KubeVirt

Deploy the latest version

$ export VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases \
    | grep tag_name | grep -v -- '-rc' | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-operator.yaml
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-cr.yaml

Check the pods in the kubevirt namespace to ensure they are running.

Deploy CDI (Containerized Data Importer)

$ export VERSION=$(curl -s https://github.com/kubevirt/containerized-data-importer/releases/latest \
    | grep -o "v[0-9]\.[0-9]*\.[0-9]*")
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${VERSION}/cdi-operator.yaml
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/${VERSION}/cdi-cr.yaml

5. Client Tools

Download the virtctl CLI:

$ export VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases \
    | grep tag_name | grep -v -- '-rc' | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)
$ curl -L -o /usr/local/bin/virtctl https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/virtctl-${VERSION}-linux-amd64
$ chmod +x /usr/local/bin/virtctl

Alternatively install it as a kubectl plugin via krew:

$ kubectl krew install virt

6. Prepare the Windows Image

Download a Windows ISO from a trusted source (e.g., MSDN I Tell You or TechBench by WZT) and upload it to a PVC using CDI:

$ virtctl image-upload \
  --image-path='Win10_20H2_Chinese(Simplified)_x64.iso' \
  --pvc-name=iso-win10 \
  --pvc-size=7G \
  --uploadproxy-url=https://<cdi-uploadproxy_svc_ip> \
  --insecure \
  --wait-secs=240

Key parameters:

--image-path : path to the OS image.

--pvc-name : name of the PVC that will be created automatically.

--pvc-size : size of the PVC (slightly larger than the image).

--uploadproxy-url : service IP of the CDI upload proxy.

7. Create the VM

Define a VM manifest ( win10.yaml) that uses three volumes:

cdromiso : the PVC containing the Windows ISO.

harddrive : a hostDisk on the node for the OS installation.

virtiocontainerdisk : a container disk providing virtio drivers.

apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
  name: win10
spec:
  running: false
  template:
    metadata:
      labels:
        kubevirt.io/domain: win10
    spec:
      domain:
        cpu:
          cores: 4
        devices:
          disks:
          - bootOrder: 1
            cdrom:
              bus: sata
            name: cdromiso
          - disk:
              bus: virtio
            name: harddrive
          - cdrom:
              bus: sata
            name: virtiocontainerdisk
          interfaces:
          - masquerade: {}
            model: e1000
            name: default
        machine:
          type: q35
        resources:
          requests:
            memory: 16G
      networks:
      - name: default
        pod: {}
      volumes:
      - name: cdromiso
        persistentVolumeClaim:
          claimName: iso-win10
      - name: harddrive
        hostDisk:
          capacity: 50Gi
          path: /data/disk.img
          type: DiskOrCreate
      - name: virtiocontainerdisk
        containerDisk:
          image: kubevirt/virtio-container-disk

Create and start the VM:

$ kubectl apply -f win10.yaml
$ virtctl start win10   # or: kubectl virt start win10

Verify the VM pod is running:

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
virt-launcher-win10-...  2/2     Running   0          15s

8. Access the VM

Use VNC for initial access (install a VNC viewer such as RealVNC on macOS):

$ virtctl vnc win10   # or: kubectl virt vnc win10

After Windows installation, install the virtio drivers from the attached containerDisk to enable proper disk detection.

9. CNI Plugin Issue (Calico)

If using Calico, enable IP forwarding for the masquerade mode:

$ kubectl -n kube-system edit cm calico-config
# Add:
"container_settings": {"allow_ip_forwarding": true}

Restart the Calico node pods:

$ kubectl -n kube-system delete pod -l k8s-app=calico-node

10. Remote Desktop (RDP)

Enable RDP inside Windows, then expose the RDP port via a NodePort service:

$ kubectl virt expose vm win10 --name win10-rdp --port 3389 --target-port 3389 --type NodePort
$ kubectl get svc
NAME        TYPE      CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
win10-rdp   NodePort  10.98.20.203    <none>        3389:31192/TCP   20m

Connect using the node IP and the allocated node port (e.g., NodeIP:31192) from any RDP client (Windows Remote Desktop, Microsoft Remote Desktop on macOS, etc.).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Cloud NativeKubernetesVirtualizationKubeVirtCDIRDPWindows VM
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.