Skip to content

Windows 11 VM Tutorial - Complete Setup Guide

This comprehensive guide covers the complete process of setting up Windows 11 VMs in KubeVirt, from infrastructure setup to golden image creation and deployment.

Overview

This tutorial provides two deployment methods:

  1. Golden Image Deployment (Recommended) - Deploy pre-configured VMs in 5-10 minutes
  2. Fresh Installation - Create custom Windows installations with full control

Prerequisites

  • KubeVirt and CDI installed and running
  • Multus CNI with OVN network configured
  • StorageClass local-path available
  • Ingress controller for HTTP access to ISOs

Step 1: Create ISO Hosting Environment

1.1 Create Dedicated Namespace

# hack/windows/iso-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: iso

1.2 Create Storage for ISOs

# hack/windows/iso-storage-pvc-iso-ns.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: iso-storage
  namespace: iso
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Gi  # Increased for Windows ISO + golden images
  storageClassName: local-path

1.3 HTTP Server for ISOs

# hack/windows/nginx-iso-server-iso-ns.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-iso-server
  namespace: iso
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-iso-server
  template:
    metadata:
      labels:
        app: nginx-iso-server
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: iso-storage
          mountPath: /usr/share/nginx/html
        - name: nginx-config
          mountPath: /etc/nginx/conf.d
      volumes:
      - name: iso-storage
        persistentVolumeClaim:
          claimName: iso-storage
      - name: nginx-config
        configMap:
          name: nginx-iso-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-iso-config
  namespace: iso
data:
  default.conf: |
    server {
        listen 80;
        server_name _;
        root /usr/share/nginx/html;

        location / {
            autoindex on;
            autoindex_exact_size off;
            autoindex_localtime on;
        }

        location ~* \.(iso|img|qcow2)$ {
            add_header Content-Type application/octet-stream;
            add_header Cache-Control "public, max-age=3600";
        }
    }
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-iso-server
  namespace: iso
spec:
  selector:
    app: nginx-iso-server
  ports:
  - port: 80
    targetPort: 80

1.4 Ingress Configuration with TLS

# hack/windows/iso-ingress-iso-ns.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: iso-server-ingress
  namespace: iso
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod-http
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - iso.stage.kube-dc.com
    secretName: iso-server-tls
  rules:
  - host: iso.stage.kube-dc.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: iso-server
            port:
              number: 80

1.5 Deploy Infrastructure

# Deploy all infrastructure components
kubectl apply -f hack/windows/iso-namespace.yaml
kubectl apply -f hack/windows/iso-storage-pvc-iso-ns.yaml
kubectl apply -f hack/windows/nginx-iso-server-iso-ns.yaml
kubectl apply -f hack/windows/iso-ingress-iso-ns.yaml

# Verify deployment
kubectl get pods -n iso
kubectl get ingress -n iso

Step 2: Download and Upload Windows ISO

2.1 Download Windows 11 Enterprise ISO

  1. Visit: https://www.microsoft.com/en-us/evalcenter/download-windows-11-enterprise
  2. Select: ISO – Enterprise download 64-bit edition (90-day evaluation)
  3. Download the ISO file (approximately 5.4GB)

2.2 Download VirtIO Drivers

# Download latest VirtIO drivers
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso

2.3 Upload Files to Cluster

# Create temporary upload pod
kubectl apply -f hack/windows/iso-upload-pod.yaml

# Wait for pod to be ready
kubectl wait --for=condition=Ready pod/iso-upload-pod -n iso --timeout=60s

# Upload Windows 11 ISO (replace with your actual filename)
kubectl cp ~/Win11_24H2_EnglishInternational_x64.iso iso/iso-upload-pod:/storage/win11-x64.iso

# Upload VirtIO drivers
kubectl cp ~/virtio-win.iso iso/iso-upload-pod:/storage/virtio-win.iso

# Upload OpenSSH installation script
kubectl cp hack/windows/install-openssh-windows.ps1 iso/iso-upload-pod:/storage/install-openssh-windows.ps1

# Clean up upload pod
kubectl delete pod iso-upload-pod -n iso --wait=true

# Verify files are accessible
curl -I https://iso.stage.kube-dc.com/win11-x64.iso
curl -I https://iso.stage.kube-dc.com/virtio-win.iso

Step 3: Fresh Windows Installation

3.1 Deploy Installation VM

Use the complete VM manifest that includes all required DataVolumes:

# Deploy Windows VM with installation ISOs
kubectl apply -f hack/windows/windows11-vm.yaml

# Monitor DataVolume download progress
kubectl get dv -n shalb-dev

# Check VM status
kubectl get vm,vmi -n shalb-dev | grep windows11

3.2 Windows Installation Process

# Access VM console via VNC
virtctl vnc windows11-vm -n shalb-dev

# Or use VNC proxy
virtctl vnc windows11-vm -n shalb-dev --proxy-only --port 5900
# Then connect VNC client to localhost:5900

Installation Steps:

  1. Boot from Windows ISO: VM boots from Windows installer (bootOrder: 1)

  2. Load VirtIO Drivers:

  3. When prompted for disk drivers, click "Load driver"
  4. Browse to VirtIO drivers CDROM
  5. Navigate to /amd64/w11/ folder
  6. Install VirtIO SCSI controller drivers (for disk access)
  7. DO NOT install network drivers yet (to create local account)

  8. Install Windows:

  9. Select the 60GB VirtIO disk for installation
  10. Complete Windows 11 setup with local account

  11. Post-Installation:

  12. Install remaining VirtIO drivers from CDROM (network, balloon, RNG)

  13. Install QEMU Guest Agent from VirtIO drivers CDROM
  14. Run Windows Updates

3.3 Configure SSH and RDP

After Windows installation, configure SSH and RDP access:

# Method 1: Download and run script
Invoke-WebRequest -Uri "https://iso.stage.kube-dc.com/install-openssh-windows.ps1" -OutFile "install-openssh-windows.ps1"
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
.\install-openssh-windows.ps1

# Method 2: Direct execution (bypass execution policy)
PowerShell -ExecutionPolicy Bypass -Command "Invoke-Expression (Invoke-WebRequest -Uri 'https://iso.stage.kube-dc.com/install-openssh-windows.ps1').Content"

Script Features:

  • ✅ Installs OpenSSH Server using Windows capabilities
  • ✅ Configures SSH service for automatic startup
  • ✅ Opens SSH port 22 in Windows Firewall (all network profiles)
  • ✅ Enables Remote Desktop (port 3389)
  • ✅ Enables ICMP ping (IPv4 and IPv6)
  • ✅ Provides detailed verification and status reporting

Step 4: Create Golden Image

4.1 Prepare VM for Golden Image

# 1. Inside Windows VM, run Sysprep (optional but recommended)
# Navigate to: C:\Windows\System32\Sysprep\sysprep.exe
# Options: Generalize, Enter System Out-of-Box Experience (OOBE), Shutdown

# 2. Stop the source VM (CRITICAL for export)
kubectl patch vm windows11-vm -n shalb-dev --type merge -p '{"spec":{"runStrategy":"Halted"}}'
kubectl wait --for=delete vmi/windows11-vm -n shalb-dev --timeout=300s

4.2 Export to QCOW2 Golden Image

# hack/windows/export-golden-image.yaml
apiVersion: v1
kind: Pod
metadata:
  name: export-golden-image
  namespace: shalb-dev
spec:
  restartPolicy: Never
  containers:
    - name: exporter
      image: ubuntu:22.04
      command: ["sh", "-c"]
      args:
        - |
          set -e
          apt-get update && apt-get install -y qemu-utils curl
          cd /pvc

          echo "=== Disk Information ==="
          DISK_SIZE_BYTES=$(qemu-img info --output=json disk.img | grep '"virtual-size"' | cut -d: -f2 | tr -d ' ,')
          DISK_SIZE_GB=$((DISK_SIZE_BYTES / 1024 / 1024 / 1024))
          echo "Source disk: ${DISK_SIZE_GB}GB (${DISK_SIZE_BYTES} bytes)"

          echo "=== Converting to compressed QCOW2 ==="
          qemu-img convert -p -O qcow2 -c disk.img windows11-x64-golden.qcow2

          echo "=== Final Golden Image ==="
          ls -lh windows11-x64-golden.qcow2
          qemu-img info windows11-x64-golden.qcow2

          echo "=== Upload to ISO server ==="
          # Upload to ISO server storage
          curl -T windows11-x64-golden.qcow2 http://nginx-iso-server.iso.svc.cluster.local/windows11-x64-golden.qcow2 || echo "Upload failed, manual copy required"

          echo "=== Export completed, sleeping for inspection ==="
          sleep 3600
      volumeMounts:
        - name: source-disk
          mountPath: /pvc
  volumes:
    - name: source-disk
      persistentVolumeClaim:
        claimName: windows11-disk

4.3 Export Process

# Export golden image
kubectl apply -f hack/windows/export-golden-image.yaml
kubectl wait --for=condition=Ready pod/export-golden-image -n shalb-dev --timeout=120s

# Monitor export progress
kubectl logs -n shalb-dev export-golden-image -f

# Manual copy to ISO server (if curl upload fails)
kubectl cp shalb-dev/export-golden-image:/pvc/windows11-x64-golden.qcow2 /tmp/
kubectl cp /tmp/windows11-x64-golden.qcow2 iso/iso-upload-pod:/storage/

# Verify golden image is available
curl -I https://iso.stage.kube-dc.com/windows11-x64-golden.qcow2

# Clean up export pod
kubectl delete pod export-golden-image -n shalb-dev --wait=true

Step 5: Deploy from Golden Image

# Deploy VM from golden image
kubectl apply -f hack/windows/win11-x64.yaml

# Create SSH key secret for key injection
kubectl create secret generic authorized-keys-default \
  --from-file=key1=~/.ssh/id_rsa.pub \
  -n shalb-dev

# Monitor deployment
kubectl get vm,vmi,dv -n shalb-dev | grep win11-x64

# Get VM IP when ready
kubectl get vmi win11-x64 -n shalb-dev -o jsonpath='{.status.interfaces[0].ipAddress}'

# SSH to VM (once guest agent is ready)
ssh kube-dc@<vm-ip>

5.2 Golden Image Benefits

Aspect Golden Image Fresh Install
Deployment Time 5-10 minutes 30+ minutes
Download Size 21.3GB compressed 5.4GB + drivers
Configuration Pre-configured Manual setup required
SSH/RDP Ready immediately Requires script execution
VirtIO Drivers Pre-installed Manual installation
Use Case Production deployment Custom configurations

Step 6: Troubleshooting

6.1 Common Issues

DataVolume stuck in ImportScheduled:

# Check CDI importer pods
kubectl get pods -n cdi
kubectl logs -n cdi <importer-pod>

# Check storage provisioner
kubectl get pods -n local-path-storage

VM won't start - CPU resources:

# Check node resources
kubectl describe nodes | grep -A 10 "Allocated resources"

# Reduce VM CPU if needed
kubectl patch vm <vm-name> -n <namespace> --type merge -p '{"spec":{"template":{"spec":{"domain":{"cpu":{"cores":2}}}}}}'

SSH not working:

# Check guest agent connection
kubectl describe vmi <vm-name> -n <namespace> | grep -i agent

# Test network connectivity
kubectl run test-pod --image=nicolaka/netshoot --rm -it -- ping <vm-ip>
kubectl run test-pod --image=nicolaka/netshoot --rm -it -- nc -zv <vm-ip> 22

Storage issues with local-path:

# Check local-path provisioner
kubectl get pods -n local-path-storage
kubectl logs -n local-path-storage <provisioner-pod>

# Note: local-path doesn't support Block mode, use Filesystem mode

6.2 Verification Commands

# Check all Windows VMs
kubectl get vm -A | grep -i win

# Check DataVolume progress
kubectl get dv -n <namespace>

# Access VM console
virtctl vnc <vm-name> -n <namespace>

# Check VM resource usage
kubectl top pods -n <namespace> | grep virt-launcher

Required Manifests Summary

Infrastructure (Step 1): - hack/windows/iso-namespace.yaml - ISO namespace - hack/windows/iso-storage-pvc-iso-ns.yaml - Storage for ISOs - hack/windows/nginx-iso-server-iso-ns.yaml - HTTP server - hack/windows/iso-ingress-iso-ns.yaml - Ingress configuration

Utilities: - hack/windows/iso-upload-pod.yaml - Upload files to cluster - hack/windows/install-openssh-windows.ps1 - SSH/RDP configuration script

VM Deployment: - hack/windows/windows11-vm.yaml - Fresh installation VM - hack/windows/win11-x64.yaml - Golden image deployment

Golden Image Creation: - hack/windows/export-golden-image.yaml - Export VM to QCOW2 - hack/windows/windows11-enterprise-golden-datasource.yaml - DataSource for cloning

Optional: - hack/windows/windows11-from-datasource.yaml - Deploy from DataSource (same namespace) - hack/windows/pvc-init-windows11-disk.yaml - Fix disk size issues if needed

Available Resources

Once deployed, the following resources are available:

  • Windows 11 ISO: https://iso.stage.kube-dc.com/win11-x64.iso (5.4GB)
  • VirtIO Drivers: https://iso.stage.kube-dc.com/virtio-win.iso (700MB)
  • SSH Script: https://iso.stage.kube-dc.com/install-openssh-windows.ps1 (5KB)
  • Golden Image: https://iso.stage.kube-dc.com/windows11-x64-golden.qcow2 (21.3GB)

Security Considerations

  • SSH Keys: Use KubeVirt accessCredentials for secure key injection {{ ... }}
  • Network Policies: Implement Kubernetes network policies for VM isolation
  • Sysprep: Run before creating golden images to avoid SID conflicts
  • Firewall: Script configures Windows Firewall appropriately for SSH, RDP, and ICMP

✅ Complete Windows 11 VM infrastructure with golden image support ready for production use!