Published in


Containers and Virtualization

Create a Windows VM in Kubernetes using KubeVirt

Windows VM in a Kubernetes Cluster

KubeVirt provides a unified development platform where Containers and VMs can reside in a shared environment

Everybody likes working with Containers. You can nearly perform whatever you want with them. However, the need for Virtual Machines has not diminished yet completely. As of this day, there are still many tasks that need Virtual Machines. KubeVirt comes into play right here. With the help of KubeVirt, you can run Virtual Machines inside of any Kubernetes Cluster.

In this article, I will be demonstrating how to create and run a Windows VM inside a KinD Cluster that is running on an Ubuntu machine.


You need to have an Ubuntu Machine that should have Docker, Kubectl, and Kind installed as well as Nested Virtualization enabled. If you have one already, you can skip over the “Before Starting” part.

Before Starting

In this article, I will be using Google Cloud Console to host the Ubuntu Machine. You can follow along if you do not have one already, but make sure Nested Virtualization is enabled. You can check it via the command below. It should return something other than 0

grep -cw vmx /proc/cpuinfo

Go to GCP console, activate cloud shell and run the command below to create a disk

gcloud compute disks create kubevirt-disk \--zone=us-west4-b \--image-project=ubuntu-os-cloud \--image-family=ubuntu-2004-lts \--size=200GB

Then create an image out of that disk

gcloud compute images create kubevirt-image \--source-disk kubevirt-disk \--source-disk-zone us-west4-b \--licenses ""

Then the virtual machine

gcloud compute instances create kubevirt-vm \--custom-cpu=8 \--custom-memory=16GB \--zone=us-west4-b \--image kubevirt-image

Then create a firewall rule that will allow your local machine to access port 31002 of the Ubuntu machine

gcloud compute firewall-rules create allow-nodeport-31002 \— action=ALLOW \— direction=INGRESS \— network=default \— priority=10 \— rules=tcp:31002 \— source-ranges=<your-local-ip>/32

Be aware that the public IP of this VM is an Ephemeral one and will change if you stop and start the VM again.

Now ssh into the VM and install Docker

sudo su----------------------------------------------------------------apt update && apt upgrade -y && apt install -y ca-certificates curl gnupg lsb-release----------------------------------------------------------------curl -fsSL | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg----------------------------------------------------------------echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null----------------------------------------------------------------apt update && apt install -y docker-ce docker-ce-cli docker-compose-plugin

Then install kind and kubectl

curl -Lo ./kind +x ./kind && mv kind /usr/local/bin----------------------------------------------------------------curl -LO "$(curl -L -s"----------------------------------------------------------------sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl----------------------------------------------------------------echo "alias k=kubectl" >> ~/.bashrc && bash --login

1) Create a KinD Cluster

Here we will be creating a KinD cluster and exposing 31001 and 31002 ports for further use. If you have an existing KinD cluster and want to work on it, go to section 1.b

OPTIONAL: You can install “Nginx Ingress Controller” if you want, but we will not be needing it in our case.

k apply -f

1. b) Configure an existing KinD Cluster

If you want to create the Windows VM on an existing KinD cluster, you just need to run the two commands below in order to expose the NodePorts. Get the container name of your KinD cluster via docker ps command. It will be something like {your-cluster-name}-control-plane

docker run -d -p 31001:1234 --net kind alpine/socat \tcp-listen:1234,fork,reuseaddr tcp-connect:{your-cluster-name}-control-plane:31001----------------------------------------------------------------    docker run -d -p 31002:1234 --net kind alpine/socat \tcp-listen:1234,fork,reuseaddr tcp-connect:{your-cluster-name}-control-plane:31002

2) Install KubeVirt on the Cluster

export VERSION=$(curl -s | grep tag_name | grep -v -- '-rc' | sort -r | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)----------------------------------------------------------------k create -f${VERSION}/kubevirt-operator.yaml----------------------------------------------------------------k create -f${VERSION}/kubevirt-cr.yaml----------------------------------------------------------------k get pods -n kubevirt # wait until you see all 7 pods----------------------------------------------------------------root@virt-vm:/home/manas_petschenek# k pods -n kubevirtNAME                               READY   STATUS    RESTARTS   AGEvirt-api-77df5c4f87-26mgm          1/1     Running   0          103svirt-api-77df5c4f87-jqpmh          1/1     Running   0          103svirt-controller-749d8d99d4-nxvw2   1/1     Running   0          78svirt-controller-749d8d99d4-rmg9j   1/1     Running   0          78svirt-handler-52km7                 1/1     Running   0          78svirt-operator-564f568975-5cccj     1/1     Running   0          2m2svirt-operator-564f568975-k9c54     1/1     Running   0          2m2s

3) Install “kubectl krew” and “kubectl virt”

(set -x; cd "$(mktemp -d)" &&OS="$(uname | tr '[:upper:]' '[:lower:]')" &&ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&KREW="krew-${OS}_${ARCH}" &&curl -fsSLO "${KREW}.tar.gz" &&tar zxvf "${KREW}.tar.gz" &&./"${KREW}" install krew)----------------------------------------------------------------echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bashrc && bash --login----------------------------------------------------------------k krew install virt

4) Install Containerized Data Importer (CDI)

k apply -f apply -f get cdi cdi -n cdiroot@kubevirt-vm:/home/manas_petschenek# k get cdi cdi -n cdi
cdi 118m Deployed
----------------------------------------------------------------k get pods -n cdiroot@kubevirt-vm:/home/manas_petschenek# k get pods -n cdi
cdi-apiserver-66998fc655-v5lb2 1/1 Running 0 118m
cdi-deployment-7b4986d49d-7n82s 1/1 Running 0 119m
cdi-operator-8496bfc49c-m4zl7 1/1 Running 0 119m
cdi-uploadproxy-5984d69cc7-tcq6j 1/1 Running 0 118m

5) Download Windows ISO

Visit Copy the dowload link adress and download the iso via wget command

wget -O win.iso 'link-you-just-copied'It will look like this:wget -O win.iso ''

6) Expose the “cdi-uploadproxy” Service

To upload data to the cluster, the cdi-uploadproxy service must be accessible from outside the cluster. Since the data is too much for ingress to handle, we are going to be using a NodePort service for this purpose. In a production environment consider using a LoadBalancer service instead

7) Upload the ISO file into a PVC

Run the following command where you downloaded the Windows ISO

k virt image-upload \--image-path=win.iso \--pvc-name=iso-win10 \--access-mode=ReadWriteOnce \--pvc-size=7G \--uploadproxy-url=https://localhost:31001 \--insecure \--wait-secs=60

You should see something like

root@kubevirt-vm:/home/manas_petschenek# k virt image-upload \
> --image-path=win.iso \
> --pvc-name=iso-win10 \
> --access-mode=ReadWriteOnce \
> --pvc-size=7G \
> --uploadproxy-url=https://localhost:31001 \
> --insecure \
> --wait-secs=60
PVC default/iso-win10 not found
PersistentVolumeClaim default/iso-win10 created
Waiting for PVC iso-win10 upload pod to be ready...
Pod now ready
Uploading data to https://localhost:31001
4.23 GiB / 4.23 GiB [====================================================================================================================================================================================] 100.00% 1m18sUploading data completed successfully, waiting for processing to complete, you can hit ctrl-c without interrupting the progress
Processing completed successfully
Uploading win.iso completed successfully

8) Create the Windows VM

First, pull the Docker image

docker pull

Then create the VirtualMachine and PersistentVolumeClaim

Then start the VirtualMachine to create a VirtualMachineInstance

k virt start iso-win10

Check if they are running

root@kubevirt-vm:/home/manas_petschenek# k get vm,vmi
NAME AGE PHASE IP NODENAME READY 11s Running kubevirt-control-plane True

9) Connect to Windows VM

There are two different methods

  • If you have a graphical interface, run:
k virt vnc iso-win10
  • If you do not, then follow the steps below:
wget -O virtvnc.yaml -i 's/targetPort\: 8001/targetPort\: 8001\n    nodePort\: 31002/' virtvnc.yaml----------------------------------------------------------------k apply -f virtvnc.yaml

Then access the console via http://{public-ip-of-ubuntu-vm}:31002

Click on the VNC button

10) Install Drivers

Follow the screenshots below

Select the language
Select the second option
Accept the license terms
Select the Custom installation path
Click on “Load driver”
Click on “Browse”
Expand the “virtio drive”
Go to the bottom and expand “viostor” and then “2k12R2”. Select “amd64” and click Ok
Click on Next
Click on Next
Wait until it finishes
If you see this page, Do not press any button just wait
Set admin password
Click on the “Send CtrlAltDel” button
Login with the admin password
Close this page
Windows is ready




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store