--- title: "Cómo ejecutar máquinas virtuales en Kubernetes con KubeVirt" description: "Instala KubeVirt y CDI en Kubernetes, crea una VM Ubuntu 24.04 con cloud-init, accede por SSH, publícala con un Service y migra la VM en caliente." category: "Ecosistema Kubernetes" language: es source: https://cubepath.com/es/docs/ecosistema-kubernetes/kubevirt-maquinas-virtuales-en-kubernetes updated: 2026-09-25 --- # Cómo ejecutar máquinas virtuales en Kubernetes con KubeVirt KubeVirt es un proyecto de la CNCF que añade a Kubernetes los recursos necesarios para ejecutar máquinas virtuales KVM junto a los contenedores. Cada VM corre dentro de un pod, así que usa las mismas redes, Services, almacenamiento y herramientas que el resto del clúster. Es útil para cargas que todavía no se pueden contenerizar (sistemas heredados, Windows, appliances) o para unificar VMs y contenedores en una sola plataforma. En este tutorial instalarás KubeVirt y CDI, crearás una VM Ubuntu 24.04 configurada con cloud-init, la publicarás con un Service y la migrarás en caliente entre nodos. ## Requisitos previos Para seguir esta guía necesitas: - Un clúster Kubernetes en una de las versiones soportadas por la release actual de KubeVirt (normalmente las tres últimas menores), con nodos Ubuntu 24.04 LTS x86_64. - Virtualización por hardware en los nodos que ejecutarán VMs: servidores bare metal o VPS con virtualización anidada activada, con `/dev/kvm` disponible. - Al menos 4 vCPU y 8 GB de RAM libres en esos nodos para la VM de ejemplo y los componentes de KubeVirt. - Una StorageClass predeterminada en el clúster (por ejemplo Longhorn). Para la migración en caliente del paso 7, la StorageClass debe admitir `ReadWriteMany`. - `kubectl` con permisos de administrador del clúster y un par de claves SSH en tu equipo (`~/.ssh/id_ed25519.pub`). ## Paso 1: Comprobar el soporte de virtualización En **cada nodo** donde vayan a correr VMs, comprueba que la CPU expone las extensiones de virtualización y que existe el dispositivo KVM: ```bash grep -Ec '(vmx|svm)' /proc/cpuinfo ls -l /dev/kvm ``` ```output 4 crw-rw---- 1 root kvm 10, 232 Sep 25 09:14 /dev/kvm ``` Un número mayor que `0` indica que la CPU tiene VT-x (`vmx`) o AMD-V (`svm`). Si el resultado es `0` o `/dev/kvm` no existe, el nodo no puede ejecutar VMs aceleradas: activa la virtualización en la BIOS o pide a tu proveedor la virtualización anidada. Para pruebas sin KVM consulta la sección de solución de problemas. ## Paso 2: Instalar KubeVirt KubeVirt se instala en dos partes: un operador y un recurso `KubeVirt` que le indica que despliegue los componentes. Obtén la última versión estable publicada por el proyecto: ```bash export KUBEVIRT_VERSION=$(curl -s https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt) echo "$KUBEVIRT_VERSION" ``` Aplica el operador y el recurso personalizado: ```bash kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml" kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml" ``` Espera a que la instalación termine. Tarda unos minutos porque despliega `virt-api`, `virt-controller` y un `virt-handler` por nodo: ```bash kubectl -n kubevirt wait kv kubevirt --for condition=Available --timeout=10m ``` ```output kubevirt.kubevirt.io/kubevirt condition met ``` Comprueba que los nodos anuncian el dispositivo KVM a Kubernetes. Sustituye `worker-1` por el nombre de uno de tus nodos: ```bash kubectl get node worker-1 -o jsonpath='{.status.allocatable.devices\.kubevirt\.io/kvm}{"\n"}' ``` ```output 1k ``` Si la salida está vacía, `virt-handler` no ha encontrado `/dev/kvm` en ese nodo. ## Paso 3: Instalar CDI y virtctl CDI (Containerized Data Importer) descarga imágenes de disco desde una URL o un registro y las vuelca en un PVC. Es lo que usarás para convertir la imagen cloud de Ubuntu en el disco de la VM: ```bash export CDI_VERSION=$(basename $(curl -s -w '%{redirect_url}' https://github.com/kubevirt/containerized-data-importer/releases/latest)) 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" ``` Espera a que CDI esté disponible: ```bash kubectl wait cdi cdi --for condition=Available --timeout=10m ``` `virtctl` es el cliente para operaciones que `kubectl` no cubre: consola serie, VNC, SSH, arranque y migración. Descarga la versión que coincide con KubeVirt: ```bash curl -L -o virtctl "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64" sudo install -m 755 virtctl /usr/local/bin/virtctl rm virtctl virtctl version ``` ## Paso 4: Crear una máquina virtual Ubuntu 24.04 Crea un namespace para las VMs: ```bash kubectl create namespace vms ``` La VM se define con un recurso `VirtualMachine`. Su bloque `dataVolumeTemplates` pide a CDI que cree un PVC de 20 GiB y descargue en él la imagen cloud de Ubuntu 24.04; el volumen `cloudinitdisk` configura el hostname, tu clave SSH y el agente de invitado en el primer arranque. ```bash nano vm-ubuntu.yaml ``` ```yaml apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: ubuntu-vm-01 namespace: vms spec: runStrategy: Always dataVolumeTemplates: - metadata: name: ubuntu-vm-01-root spec: storage: resources: requests: storage: 20Gi source: http: url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img" template: metadata: labels: app: ubuntu-vm-01 spec: domain: cpu: cores: 2 resources: requests: memory: 2Gi devices: disks: - name: rootdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio interfaces: - name: default masquerade: {} networks: - name: default pod: {} volumes: - name: rootdisk dataVolume: name: ubuntu-vm-01-root - name: cloudinitdisk cloudInitNoCloud: userData: | #cloud-config hostname: ubuntu-vm-01 ssh_authorized_keys: - ssh-ed25519 AAAA... tu_clave_publica package_update: true packages: - qemu-guest-agent - nginx runcmd: - systemctl enable --now qemu-guest-agent ``` Sustituye `ssh-ed25519 AAAA... tu_clave_publica` por el contenido de tu `~/.ssh/id_ed25519.pub`. Las claves de `ssh_authorized_keys` se añaden al usuario por defecto de la imagen, que en Ubuntu es `ubuntu`. Con `runStrategy: Always`, KubeVirt arranca la VM en cuanto el disco esté listo y la vuelve a arrancar si se para. ```bash kubectl apply -f vm-ubuntu.yaml ``` Sigue la importación del disco. El DataVolume pasa por `ImportInProgress` y termina en `Succeeded`: ```bash kubectl -n vms get datavolume -w ``` ```output NAME PHASE PROGRESS RESTARTS AGE ubuntu-vm-01-root ImportInProgress 47.12% 35s ubuntu-vm-01-root Succeeded 100.0% 1m20s ``` Pulsa `Ctrl+C` y comprueba que la VM está en marcha. `vm` es la definición; `vmi` (VirtualMachineInstance) es la instancia que se está ejecutando, con su IP y su nodo: ```bash kubectl -n vms get vm,vmi ``` ```output NAME AGE STATUS READY virtualmachine.kubevirt.io/ubuntu-vm-01 2m Running True NAME AGE PHASE IP NODENAME READY virtualmachineinstance.kubevirt.io/ubuntu-vm-01 40s Running 10.42.1.37 worker-2 True ``` ## Paso 5: Acceder a la VM Conéctate a la consola serie para ver el arranque y el final de cloud-init. Sal con `Ctrl+]`: ```bash virtctl console ubuntu-vm-01 -n vms ``` Para trabajar en la VM es más cómodo SSH. `virtctl ssh` crea un túnel a través de la API de Kubernetes, así que no necesitas exponer el puerto 22: ```bash virtctl ssh -n vms -i ~/.ssh/id_ed25519 ubuntu@vm/ubuntu-vm-01 ``` Una vez dentro, comprueba que cloud-init ha terminado y que nginx está activo: ```bash cloud-init status systemctl is-active nginx ``` ```output status: done active ``` Con el agente de invitado en marcha, KubeVirt obtiene del sistema invitado datos como sus interfaces de red y su versión, que puedes consultar desde fuera: ```bash kubectl -n vms get vmi ubuntu-vm-01 -o jsonpath='{.status.guestOSInfo.prettyName}{"\n"}' ``` La salida debe mostrar el nombre completo de la versión de Ubuntu 24.04 instalada en la VM. Si está vacía, el agente todavía no ha arrancado. ## Paso 6: Publicar la VM con un Service Como la VM corre en un pod con las etiquetas de `template.metadata.labels`, un Service normal puede seleccionarla. Así, los pods del clúster pueden acceder a ella por DNS igual que a cualquier otro servicio, lo que permite combinar VMs y contenedores en una misma aplicación: ```bash nano vm-service.yaml ``` ```yaml apiVersion: v1 kind: Service metadata: name: ubuntu-vm-01-http namespace: vms spec: selector: app: ubuntu-vm-01 ports: - name: http port: 80 targetPort: 80 type: ClusterIP ``` ```bash kubectl apply -f vm-service.yaml ``` Prueba el acceso desde un pod temporal. El nombre `ubuntu-vm-01-http.vms.svc.cluster.local` es el que usaría cualquier aplicación del clúster: ```bash kubectl run prueba --rm -it --image=curlimages/curl --restart=Never -- \ curl -sI http://ubuntu-vm-01-http.vms.svc.cluster.local ``` ```output HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) ``` Para acceso desde fuera del clúster, cambia `type` a `LoadBalancer` (si tienes un balanceador como MetalLB) o expón el Service con un Ingress. ## Paso 7: Migrar la VM en caliente La migración en caliente mueve una VM en ejecución a otro nodo copiando su memoria, sin reiniciar el sistema invitado. Es lo que permite vaciar un nodo para mantenimiento. Tiene dos condiciones: el disco debe estar en un PVC `ReadWriteMany` para que ambos nodos lo vean a la vez, y la red debe usar `masquerade` (o bridge sobre una red secundaria), como en este ejemplo. Comprueba si KubeVirt considera la VM migrable: ```bash kubectl -n vms get vmi ubuntu-vm-01 -o jsonpath='{.status.conditions[?(@.type=="LiveMigratable")].status}{"\n"}' ``` Si devuelve `False`, consulta el motivo con `kubectl -n vms describe vmi ubuntu-vm-01`; lo habitual es que el PVC sea `ReadWriteOnce`. En ese caso, crea la VM con una StorageClass que ofrezca `ReadWriteMany` en modo bloque o sistema de archivos. Si devuelve `True`, anota el nodo actual e inicia la migración: ```bash kubectl -n vms get vmi ubuntu-vm-01 -o jsonpath='{.status.nodeName}{"\n"}' virtctl migrate ubuntu-vm-01 -n vms ``` `virtctl migrate` crea un recurso `VirtualMachineInstanceMigration`. Sigue su progreso: ```bash kubectl -n vms get vmim -w ``` ```output NAME PHASE VMI kubevirt-migrate-vm-8xk2p Scheduling ubuntu-vm-01 kubevirt-migrate-vm-8xk2p Running ubuntu-vm-01 kubevirt-migrate-vm-8xk2p Succeeded ubuntu-vm-01 ``` Comprueba que la VM está ahora en otro nodo y que la sesión SSH o el Service siguen respondiendo: ```bash kubectl -n vms get vmi ubuntu-vm-01 -o jsonpath='{.status.nodeName}{"\n"}' ``` Para que `kubectl drain` migre la VM en lugar de apagarla al vaciar un nodo, añade `evictionStrategy: LiveMigrate` en `spec.template.spec` de la `VirtualMachine`. ## Operaciones habituales con virtctl | Acción | Comando | |---|---| | Parar la VM | `virtctl stop ubuntu-vm-01 -n vms` | | Arrancarla | `virtctl start ubuntu-vm-01 -n vms` | | Reiniciarla | `virtctl restart ubuntu-vm-01 -n vms` | | Consola gráfica (requiere un visor VNC local) | `virtctl vnc ubuntu-vm-01 -n vms` | | Copiar un archivo a la VM | `virtctl scp -n vms -i ~/.ssh/id_ed25519 archivo.txt ubuntu@vm/ubuntu-vm-01:/tmp/` | Ten en cuenta que con `runStrategy: Always`, `virtctl stop` cambia la estrategia a `Halted` para que la VM no vuelva a arrancar sola; `virtctl start` la pone de nuevo en marcha. ## Solución de problemas **La VMI se queda en `Scheduling` o el pod `virt-launcher` en `Pending`.** Revisa los eventos con `kubectl -n vms describe vmi ubuntu-vm-01`. Si mencionan `devices.kubevirt.io/kvm`, ningún nodo tiene KVM disponible (repite el paso 1). Si mencionan memoria o CPU, no hay nodos con recursos libres suficientes. **El DataVolume falla o no avanza.** Consulta los logs del pod importador, que se llama `importer-`: ```bash kubectl -n vms logs -f importer-ubuntu-vm-01-root ``` Los fallos habituales son una URL inaccesible desde el clúster o que no haya StorageClass predeterminada, en cuyo caso el PVC queda en `Pending`. **Nodos sin KVM (solo para pruebas).** KubeVirt puede usar emulación por software, mucho más lenta y no apta para producción: ```bash kubectl -n kubevirt patch kubevirt kubevirt --type=merge \ -p '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}' ``` **No puedes entrar por SSH.** Confirma en la consola serie que cloud-init terminó sin errores (`cloud-init status --long`) y que la clave pública del manifiesto es exactamente la de tu `id_ed25519.pub`. cloud-init solo aplica la configuración en el primer arranque, así que si cambias la clave tendrás que recrear la VM. ## Conclusión Tienes KubeVirt y CDI funcionando, una VM Ubuntu 24.04 creada de forma declarativa con cloud-init, accesible por consola, SSH y un Service de Kubernetes, y has visto qué necesita para migrar en caliente entre nodos. Como siguientes pasos, prueba a importar una imagen propia en un DataVolume y clonarla para crear varias VMs, añade una segunda red con Multus para conectar las VMs a una VLAN existente, o gestiona las definiciones de VMs con Argo CD como cualquier otro manifiesto.