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)
kubectlcon 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.


