Crear máquinas virtuales dentro de un clúster de Kubernetes con Kubevirt

Hace unas semanas atrás estuve jugando con un proyecto de la CNCF llamado KubeVirt, que te permite crear máquinas virtuales, Linux y Windows, dentro de un clúster de Kubernetes como si de otro recurso más se tratara. Me pareció súper interesante, sobre todo pensando en cargas que son difíciles de contenerizar o incluso para los entornos de pruebas donde necesitas instalar/ejecutar herramientas en estas máquinas contra otros pods del clúster. En este artículo quiero compartir contigo cómo usarlo.

Crear un clúster de Kubernetes

Lo primero que necesitas es un clúster de Kubernetes donde instalar Kubevirt. Al principio intenté usar kind pero tuve algunos problemas con algunas de las máquinas usando MacOS como host, por lo que finalmente monté un clúster en Azure con un pool de máquinas que tuvieran habilitado VT-x:

# Variables
RESOURCE_GROUP="kubevirt"
LOCATION="northeurope"
AKS_NAME="kubevirt-on-aks"
# Creare resource group
az group create -n $RESOURCE_GROUP -l $LOCATION
# Create cluster
az aks create \
--resource-group $RESOURCE_GROUP \
--network-policy calico \
--network-plugin kubenet \
--node-vm-size Standard_B4ms \
--node-count 1 \
--name $AKS_NAME
# Add nodepool with Intel virtualization extensions (VT-x) enabled
az aks nodepool add \
--resource-group $RESOURCE_GROUP \
--cluster-name $AKS_NAME \
--name nested \
--node-vm-size Standard_D4s_v3 \
--labels nested=true \
--node-count 1
# Get credentials
az aks get-credentials -g $RESOURCE_GROUP -n $AKS_NAME
# Check access
kubectl get nodes

Instalar Kubevirt en tu clúster

Una vez que ya tienes un clúster listo para tus pruebas lo siguiente es instalar Kubevirt:

# Install kubevirt
STABLE_VERSION="v0.58.0"
# export VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases | grep tag_name | grep -v -- '-rc' | sort -r | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)
# echo $VERSION
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${STABLE_VERSION}/kubevirt-operator.yaml"
# Again use kubectl to deploy the KubeVirt custom resource definitions
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${STABLE_VERSION}/kubevirt-cr.yaml"
# Check the components
watch kubectl get all -n kubevirt

Para evitar comportamientos anómalos he utilizado la variable STABLE_VERSION con el valor de la última versión estable en el momento de escribir este artículo. Si utilizas el código comentado justo debajo es posible que en ese momento exista alguna versión alfa que no se comporte como esperas y de error al crear las máquinas virtuales.

Si todo ha ido bien, deberías de tener todos los componentes en estado Running.

Instalar Containerized Data Importer

La forma más sencilla de crear máquinas virtuales con Kubevirt en Kubernetes es usando la funcionalidad Containerized Data Importer o CDI. Esto lo que te permite es, como su nombre indica, importar en este caso la ISO que hace falta para instalar la máquina virtual para el sistema operativo que necesites. Para desplegar esta herramienta en el clúster puedes hacerlo con los siguientes comandos:

# Install the CDI
export VERSION=$(basename $(curl -s -w %{redirect_url} https://github.com/kubevirt/containerized-data-importer/releases/latest))
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
# Check the status of the cdi CustomResource (CR) created in the previous step
kubectl get cdi cdi -n cdi
kubectl get pods -n cdi

Ahora, después de tanta configuración, vamos a ver un ejemplo con un Windows 11.

Crear una máquina virtual con Windows 11 con Kubevirt

Uno de los ejemplos que monté es una máquina virtual con Windows 11. Para ello, lo primero que hago es usar CDI de la siguiente forma:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "windows11"
  labels:
    app: containerized-data-importer
  annotations:
    cdi.kubevirt.io/storage.import.endpoint: "https://software.download.prss.microsoft.com/dbazure/Win11_22H2_English_x64v1.iso?t=ee63b31a-215b-49ba-b276-ddbb12882558&e=1672394475&h=297c582f25a7f02fabe15661d97edaf54490ec11c33416ad81dd11fcdeb15f6b"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 6Gi

Como ves, necesito crear un objeto del tipo PersistentVolumeClaim que tiene una anotación especial, cdi.kubevirt.io/storage.import.endpoint, a la que le paso la URL desde la que puede descargar la ISO, en este caso de Windows 11.

Nota: la URL que aparece en este código de ejemplo no te funcionará. En el caso de Windows 11 debes acceder a esta URL, hacer clic en el apartado Download Windows Disk 10 Image (ISO) seleccionar Windows 11 (multi-edition ISO), hacer clic en Download, seleccionar el idioma y hacer clic en Confirm. Una vez hecho esto te aparecerá un nuevo botón llamado 64-bit Download que tendrá una validez de 24 horas para descargarte la ISO.

Obtener el enlace de descarga de la ISO de Windows 11

Una vez copiada la URL correcta, si creas este recurso y revisas los logs:

# Create the pvc
# https://www.microsoft.com/software-download/windows11
k create -f windows/windows11-pvc.yml
# Check the progress
kubectl logs -f importer-windows11

Comprobarás al cabo de unos instantes que en ellos va apareciendo el porcentaje de descarga de la ISO:

Descargando la ISO de Windows 11 con CDI

Una vez que este proceso haya finalizado satisfactoriamente puedes definir un YAML como el siguiente para una máquina virtual con Windows 11:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-windows11
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 35Gi
---
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
  labels:
    special: vmi-windows
  name: vmi-windows
spec:
  nodeSelector: #nodeSelector matches nodes where performance key has high as value.
    nested: "true"
  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-windows11
    - name: winiso
      persistentVolumeClaim:
        claimName: windows11

Esta configuración la he copiado de un artículo del blog de KubeVirt, donde explica muchos de los parámetros que son necesarios para este sistema operativo. Si creas el recurso haciendo uso de esta configuración:

# Create the virtual machine using that pvc
kubectl create -f windows/win11vm.yaml

Puedes comprobar el proceso de alocación y de ejecución de la máquina virtual con este otro comando:

# Check virtual machine instance status
watch kubectl get vmi

Ahora que ya tienes la máquina arrancada el siguiente paso es conectarse a ella ¿pero cómo? puedes usar VNC para la conexión con esta:

# Connect VM via VNC
k virt vnc vmi-windows

Cuando arrancas la máquina debes hacer el mismo proceso que si una máquina física o virtual «normal» se tratase. Una vez tengas la misma con su instalación hecha te recomiendo que instales la última versión de los drivers de VirtIO que puedes encontrar aquí.

Probar la comunicación con otros pods dentro del clúster

Una de las cosas que me interesaba comprobar era la posibilidad de comunicarme con otras aplicaciones que tuviera desplegadas en el clúster. Es por ello que probé con mi Tour of Heroes:

# Apply tour of heroes manifests
kubectl apply -f tour-of-heroes --recursive

Una vez hecho esto, dentro de mi máquina virtual con Windows 11 intenté acceder a la API y al frontal web usando el FQDN y el resultado fue satisfactorio:

nslookup a otros servicios en el clúster de Kubernetes

También puedo acceder a través del navegador web usando el FQDN de mis servicios:

Acceder a un servicio en Kubernetes desde el navegador web de una VM con Kubevirt

¡Saludos!