KubeVirt: Máquinas Virtuales en Kubernetes

KubeVirt extiende Kubernetes para ejecutar máquinas virtuales junto con contenedores, usando los mismos mecanismos de orquestación para ambos tipos de carga de trabajo. Esto permite a los equipos migrar gradualmente de VMs a contenedores manteniendo las aplicaciones heredadas en máquinas virtuales mientras aprovechan toda la infraestructura de Kubernetes para networking, almacenamiento y gestión.

Requisitos Previos

  • Kubernetes 1.26+ con nodos Linux
  • Soporte de virtualización en los nodos (Intel VT-x / AMD-V)
  • kubectl con acceso de administrador al clúster
  • Al menos 4 vCPUs y 8 GB de RAM por nodo que ejecutará VMs
  • CDI (Containerized Data Importer) para importar imágenes de disco
# Verificar soporte de virtualización en los nodos
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity}{"\n"}{end}'

# Verificar desde un nodo directamente
egrep -c '(vmx|svm)' /proc/cpuinfo
# Si el resultado es 0, no hay soporte de virtualización por hardware

# Verificar si KVM está disponible
ls -la /dev/kvm

Instalación de KubeVirt

# Obtener la última versión estable de KubeVirt
KUBEVIRT_VERSION=$(curl -s https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt)
echo "Instalando KubeVirt $KUBEVIRT_VERSION"

# Instalar el operador de KubeVirt
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml"

# Desplegar el recurso KubeVirt para activar los componentes
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml"

# Esperar a que la instalación complete
kubectl -n kubevirt wait kv kubevirt \
  --for condition=Available \
  --timeout=10m

# Verificar todos los pods de KubeVirt
kubectl get pods -n kubevirt
# Instalar el CDI (Containerized Data Importer) para gestionar imágenes de disco
CDI_VERSION=$(curl -s https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest \
  | jq -r '.tag_name')

kubectl apply -f "https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-operator.yaml"
kubectl apply -f "https://github.com/kubevirt/containerized-data-importer/releases/download/${CDI_VERSION}/cdi-cr.yaml"

kubectl -n cdi wait cdi cdi --for condition=Available --timeout=5m

# Instalar virtctl (CLI para gestionar VMs)
curl -Lo /usr/local/bin/virtctl \
  "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64"
chmod +x /usr/local/bin/virtctl

Creación de Máquinas Virtuales

# Importar una imagen de disco de Ubuntu en un DataVolume
cat > ubuntu-datavolume.yaml << 'EOF'
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: ubuntu-2204-disco
  namespace: vms
spec:
  source:
    http:
      url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 20Gi
    storageClassName: longhorn
EOF

kubectl create namespace vms
kubectl apply -f ubuntu-datavolume.yaml

# Verificar el progreso de la importación
kubectl get datavolumes -n vms -w
# Crear una VirtualMachine a partir del DataVolume
cat > vm-ubuntu.yaml << 'EOF'
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: ubuntu-vm-01
  namespace: vms
spec:
  running: false  # Cambiar a true para iniciar automáticamente
  template:
    metadata:
      labels:
        kubevirt.io/vm: ubuntu-vm-01
        app: ubuntu-vm
    spec:
      domain:
        cpu:
          cores: 2
          threads: 1
          sockets: 1
        resources:
          requests:
            memory: 2Gi
          limits:
            memory: 4Gi
        devices:
          disks:
            - name: disco-principal
              disk:
                bus: virtio
            - name: cloud-init
              disk:
                bus: virtio
          interfaces:
            - name: red-principal
              masquerade: {}
          networkInterfaceMultiqueue: true
      networks:
        - name: red-principal
          pod: {}
      volumes:
        - name: disco-principal
          dataVolume:
            name: ubuntu-disco-01
        - name: cloud-init
          cloudInitNoCloud:
            userData: |
              #cloud-config
              hostname: ubuntu-vm-01
              users:
                - name: ubuntu
                  sudo: ALL=(ALL) NOPASSWD:ALL
                  ssh_authorized_keys:
                    - ssh-rsa AAAAB3NzaC1... tu-clave-publica-ssh
              packages:
                - nginx
                - htop
              runcmd:
                - systemctl enable nginx
                - systemctl start nginx
  dataVolumeTemplates:
    - metadata:
        name: ubuntu-disco-01
      spec:
        pvc:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 20Gi
          storageClassName: longhorn
        source:
          http:
            url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
EOF

kubectl apply -f vm-ubuntu.yaml

Migración en Caliente (Live Migration)

La migración en caliente permite mover VMs entre nodos sin interrupciones.

# Iniciar una migración en caliente
cat > migration.yaml << 'EOF'
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstanceMigration
metadata:
  name: migrar-ubuntu-vm-01
  namespace: vms
spec:
  vmiName: ubuntu-vm-01
EOF

kubectl apply -f migration.yaml

# Verificar el estado de la migración
kubectl get vmim -n vms
kubectl describe vmim migrar-ubuntu-vm-01 -n vms

# Usar virtctl para migrar (más sencillo)
virtctl migrate ubuntu-vm-01 -n vms

# Verificar en qué nodo está corriendo la VM antes y después
kubectl get vmi ubuntu-vm-01 -n vms -o jsonpath='{.status.nodeName}'
# Configurar políticas de migración para el clúster
cat > migration-policy.yaml << 'EOF'
apiVersion: migrations.kubevirt.io/v1alpha1
kind: MigrationPolicy
metadata:
  name: politica-migracion-produccion
spec:
  selectors:
    namespaceSelector:
      matchLabels:
        env: produccion
  # Número máximo de migraciones simultáneas
  bandwidthPerMigration: "512Mi"
  completionTimeoutPerGiB: 300
  allowPostCopy: false
  allowAutoConverge: true
EOF

kubectl apply -f migration-policy.yaml

Almacenamiento para VMs

# Clonar un DataVolume existente (clonar una VM)
cat > clone-datavolume.yaml << 'EOF'
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: ubuntu-vm-02-disco
  namespace: vms
spec:
  source:
    pvc:
      namespace: vms
      name: ubuntu-disco-01  # PVC de origen
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 20Gi
    storageClassName: longhorn
EOF

kubectl apply -f clone-datavolume.yaml

# Agregar un disco adicional a una VM existente (hotplug)
cat > hotplug-volume.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disco-datos-extra
  namespace: vms
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 100Gi
EOF

kubectl apply -f hotplug-volume.yaml

# Adjuntar el disco en caliente a la VM
virtctl addvolume ubuntu-vm-01 \
  --volume-name=disco-datos-extra \
  --persist \
  -n vms

Networking de Máquinas Virtuales

# Exponer una VM mediante un Service de Kubernetes
cat > vm-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: ubuntu-vm-ssh
  namespace: vms
spec:
  selector:
    kubevirt.io/vm: ubuntu-vm-01
  ports:
    - protocol: TCP
      port: 22
      targetPort: 22
  type: ClusterIP
EOF

kubectl apply -f vm-service.yaml

# Crear un Service con LoadBalancer para acceso externo
cat > vm-service-lb.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: ubuntu-vm-web
  namespace: vms
spec:
  selector:
    app: ubuntu-vm
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
    - name: https
      protocol: TCP
      port: 443
      targetPort: 443
  type: LoadBalancer
EOF

kubectl apply -f vm-service-lb.yaml

Gestión de VMs con virtctl

# Iniciar y detener VMs
virtctl start ubuntu-vm-01 -n vms
virtctl stop ubuntu-vm-01 -n vms
virtctl restart ubuntu-vm-01 -n vms

# Conectarse a la consola serie de la VM
virtctl console ubuntu-vm-01 -n vms
# Salir con Ctrl+]

# Conectarse mediante VNC
virtctl vnc ubuntu-vm-01 -n vms

# Tunelizar SSH a través de la API de Kubernetes
virtctl ssh ubuntu@ubuntu-vm-01 -n vms

# Copiar archivos hacia/desde la VM (sftp)
virtctl scp archivo.txt ubuntu@ubuntu-vm-01:/tmp/ -n vms

# Ver el estado de todas las VMs
kubectl get vms -n vms
kubectl get vmis -n vms  # VirtualMachineInstance (instancias en ejecución)

# Ver uso de recursos de las VMs
kubectl top pods -n vms --sort-by=memory

Cargas de Trabajo Híbridas VM/Contenedor

# Ejemplo: base de datos en VM + API en contenedor, comunicados por Service
# La VM con PostgreSQL expone el servicio en el mismo namespace

# Service para la VM de base de datos
cat > db-vm-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: mi-app
spec:
  selector:
    kubevirt.io/vm: postgres-vm
  ports:
    - port: 5432
      targetPort: 5432
EOF

kubectl apply -f db-vm-service.yaml

# El Deployment de la API puede conectarse como si fuera un pod normal
cat > api-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-backend
  namespace: mi-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: mi-api:v1.0
          env:
            - name: DATABASE_URL
              # Se conecta a la VM de PostgreSQL mediante el Service
              value: "postgresql://user:pass@postgresql:5432/miapp"
EOF

kubectl apply -f api-deployment.yaml

Solución de Problemas

VM en estado Pending:

# Verificar que los nodos tienen virtualización habilitada
kubectl get nodes -o json | jq '.items[].status.capacity'
kubectl describe node <nombre-nodo> | grep kubevirt

# Ver los eventos de la VMI
kubectl describe vmi ubuntu-vm-01 -n vms

# Verificar el pod virt-launcher
kubectl get pods -n vms -l kubevirt.io/vm=ubuntu-vm-01
kubectl logs -n vms <virt-launcher-pod> -c compute

Error al iniciar la VM por falta de KVM:

# En entornos sin KVM, usar el modo de emulación (solo para desarrollo)
kubectl edit kubevirt kubevirt -n kubevirt
# Agregar en spec.configuration:
# developerConfiguration:
#   useEmulation: true

La migración falla:

# Verificar que el PVC usa acceso ReadWriteMany para migración
kubectl get pvc -n vms

# Ver los logs del migration controller
kubectl logs -n kubevirt -l kubevirt.io/component=virt-controller | grep migration

Conclusión

KubeVirt unifica la gestión de máquinas virtuales y contenedores bajo la misma plataforma Kubernetes, eliminando la necesidad de mantener infraestructuras separadas para ambos tipos de cargas de trabajo. Su capacidad de migración en caliente, almacenamiento flexible y networking integrado con Kubernetes lo convierte en una solución ideal para modernizar gradualmente aplicaciones heredadas mientras se mantiene toda la operatividad del ecosistema de contenedores.