KubeVirt: installing Microsoft Windows 11 from an ISO

This blog post describes a simple way to deploy a Windows 11 VM with KubeVirt, using an installation ISO as a starting point.
Although only tested with Windows 11, the steps described here should also work to deploy other recent versions of Windows.

Pre-requisites

A suitable test cluster can easily be deployed thanks to KubeVirtCI by running the following commands from the KubeVirt source repository:

$ export KUBEVIRT_MEMORY_SIZE=8192M
$ export KUBEVIRT_STORAGE=rook-ceph-default
$ make cluster-up && make cluster-sync

Preparation

Before the virtual machine can be created, we need to setup storage volumes for the ISO and the drive, and write the appropriate VM(I) yaml.

  1. Uploading the ISO to a PVC

    KubeVirt provides a simple tool that is able to do that for us: virtctl.
    Here’s the command to upload the ISO, just replace /storage/win11.iso with the path to your Windows 11 ISO: virtctl image-upload pvc win11cd-pvc --size 6Gi --image-path=/storage/win11.iso --insecure

  2. Creating a persistent volume to use as the Windows drive

    This will depend on the storage configuration of your cluster. The following yaml, to apply to the cluster using kubectl create, should work just fine on a KubeVirtCI cluster:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: task-pv-volume
      labels:
        type: local
    spec:
      storageClassName: hostpath
      capacity:
        storage: 15Gi
      accessModes:
        - ReadWriteOnce
      hostPath:
        path: "/tmp/hostImages/win11"
    

Note

Microsoft actually recommends at least 64GB of storage. But, unlike some other requirements, the installer will accept smaller disks. This is convenient when testing with KubeVirtCI, as nodes only have about 20GB of free space. However, please bear in mind that such a small drive should only be used for testing purposes, and might lead to instabilities.

  1. Creating a persistent volume claim (PVC) for the drive

    Once again, your milage may vary, but the following PVC yaml works fine on KubeVirtCI:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: disk-windows
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 15Gi
      storageClassName: hostpath
    

    The name of PVC, disk-windows here, will be used in the yaml of the VM(I) as the main volume.

  2. Creating the VM(I) yaml file

    KubeVirt already includes an example Windows VMI yaml file, which we’ll use as a starting point here for convenience.
    Using a VMI yaml is more than enough for testing purposes, however for more serious applications you might want to consider changing it into a VM.

    First, in the yaml above, bump the memory up to 4Gi, which is a hard requirement of Windows 11. (Windows 10 is happy with 2Gi).

    Then, let’s add the ISO created above. Add is as a cdrom in the disks section:

    - cdrom:
        bus: sata
      name: winiso
    

    And the corresponding volume at the bottom:

      - name: winiso
        persistentVolumeClaim:
          claimName: win11cd-pvc
    

    Note that the names should match, and that the claimName is what we used in the virtctl command above.

    Here is what the VMI looks like after those changes:

    ---
    apiVersion: kubevirt.io/v1
    kind: VirtualMachineInstance
    metadata:
      labels:
        special: vmi-windows
      name: vmi-windows
    spec:
      domain:
        clock:
          timer:
            hpet:
              present: false
            hyperv: {}
            pit:
              tickPolicy: delay
            rtc:
              tickPolicy: catchup
          utc: {}
        cpu:
          cores: 2
        devices:
          disks:
          - disk:
              bus: sata
            name: pvcdisk
          - cdrom:
              bus: sata
            name: winiso
          interfaces:
          - masquerade: {}
            model: e1000
            name: default
          tpm: {}
        features:
          acpi: {}
          apic: {}
          hyperv:
            relaxed: {}
            spinlocks:
              spinlocks: 8191
            vapic: {}
          smm: {}
        firmware:
          bootloader:
            efi:
              secureBoot: true
          uuid: 5d307ca9-b3ef-428c-8861-06e72d69f223
        resources:
          requests:
            memory: 4Gi
      networks:
      - name: default
        pod: {}
      terminationGracePeriodSeconds: 0
      volumes:
      - name: pvcdisk
        persistentVolumeClaim:
          claimName: disk-windows
      - name: winiso
        persistentVolumeClaim:
          claimName: win11cd-pvc
    

Note

When customizing this VMI definition or creating your own, please keep in mind that the TPM device and the UEFI firmware with SecureBoot are both hard requirements of Windows 11. Not having them will cause the Windows 11 installation to fail early. Please also note that the SMM CPU feature is required for UEFI + SecureBoot. However, they can all be omitted in the case of a Windows 10 VM(I). Finally, we do not currently support TPM persistence, so any secret stored in the emulated TPM will be lost next time you boot the VMI. For example, do not enable BitLocker, as it will fail to find the encryption key next boot and you will have to manually enter the (55 characters!) recovery key each boot.

Windows installation

You should now be able to create the VMI and start the Windows installation process.
Just use kubectl to start the VMI created above: kubectl create -f vmi-windows.yaml.
Shortly after, open a VNC session to it using virtctl vnc vmi-windows (keep trying until the VMI is running and the VNC session pops up).
You should now see the boot screen, and shortly after a prompt to “Press any key to boot from CD or DVD…”. You have a few seconds to do so or the VM will fail to boot. Then just follow the steps to install Windows.

VirtIO drivers installation (optional)

Once Windows is installed, it’s a good ideas to install the VirtIO drivers inside the VM, as they can drastically improve performance. The latest version can be downloaded here. virtio-win-gt-x64.msi is the simplest package to install, as you just have to run it as Administrator.

Alternatively, KubeVirt has a containerdisk image that can be mounted inside the VM.
To use it, just add a simple cdrom disk to the VMI, like:

- cdrom:
    bus: sata
  name: virtio

and the volume:

  - containerDisk:
      image: kubevirt/virtio-container-disk
    name: virtio

When using KubeVirtCI, a local copy of the image is also available at registry:5000/kubevirt/virtio-container-disk:devel.

Further performance improvements

Windows is quite resource-hungry, and you might find that the VM created above is too slow, even with the VirtIO drivers installed.
Here are a few steps you can take to improve things:

  • Increasing the RAM is always a good idea, if you have enough available of course.
  • Increasing the number of CPUs, and/or using CPUManager to assign dedicated CPU to the VM should also help a lot.
  • Once the VirtIO drivers are installed, the main drive can also be switched from sata to virtio, and the attached CDROMs can be removed.