La migración en vivo mueve una máquina virtual en ejecución de un host físico a otro sin apagarla: libvirt copia la memoria de la VM mientras sigue funcionando, repite la copia de las páginas que cambian y, en el último instante, pausa la VM unos milisegundos para transferir el estado final y reanudarla en el destino. En este tutorial prepararás dos hosts KVM con Ubuntu 24.04, compartirás el almacenamiento de las VMs por NFS y migrarás una VM de prueba con virsh migrate, además de ver cómo hacerlo sin almacenamiento compartido.
Requisitos previos
- Dos servidores físicos o bare metal con Ubuntu 24.04 LTS y virtualización por hardware activada (Intel VT-x o AMD-V). En esta guía se llaman
host-a(origen) yhost-b(destino). - CPUs del mismo fabricante en ambos hosts (Intel con Intel, AMD con AMD). Entre fabricantes distintos la migración en vivo no es posible.
- Un usuario no root con privilegios
sudoen los dos hosts, con el mismo nombre (en los ejemplos,your_user). - Conectividad de red entre ambos hosts, idealmente una red privada de 10 Gbit/s. Con 1 Gbit/s funciona, pero las VMs con mucha memoria tardan más en converger.
- Para la parte de almacenamiento compartido, un servidor NFS. En los ejemplos está en
10.0.0.30; también puede ser un tercer servidor o un NAS.
Sustituye 10.0.0.10 (host-a), 10.0.0.20 (host-b) y 10.0.0.30 (NFS) por las IPs de tu red privada.
Paso 1: Instalar KVM y libvirt en ambos hosts
Ejecuta estos comandos en host-a y en host-b. Comprueba primero que la CPU expone la virtualización por hardware:
sudo apt update
sudo apt install cpu-checker
kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used
Instala QEMU/KVM, libvirt y las herramientas de gestión:
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients virtinst nfs-common
Añade tu usuario al grupo libvirt para poder usar virsh contra qemu:///system sin sudo, y vuelve a iniciar sesión para que el cambio surta efecto:
sudo usermod -aG libvirt $USER
Verifica que libvirt responde:
virsh -c qemu:///system list --all
Id Name State
--------------------
Para no tener que escribir -c qemu:///system en cada comando, exporta la URI por defecto en tu ~/.bashrc:
echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> ~/.bashrc
source ~/.bashrc
Paso 2: Configurar resolución de nombres y SSH entre hosts
virsh migrate se conecta al destino por SSH (qemu+ssh://) y, por defecto, el host de destino anuncia su propio nombre para el canal de datos de la migración. Si el origen no puede resolver ese nombre, la migración falla. Añade ambos hosts a /etc/hosts en los dos servidores:
sudo nano /etc/hosts
10.0.0.10 host-a
10.0.0.20 host-b
Comprueba que hostname devuelve host-a y host-b respectivamente. Después, en host-a, genera una clave SSH (si no tienes una) y cópiala a host-b:
ssh-keygen -t ed25519
ssh-copy-id your_user@host-b
Verifica que puedes abrir una conexión de libvirt remota desde host-a sin contraseña:
virsh -c qemu+ssh://your_user@host-b/system list --all
Id Name State
--------------------
Repite la operación en sentido contrario (de host-b a host-a) si quieres poder devolver las VMs a su host original.
Paso 3: Abrir los puertos de migración
Con qemu+ssh, la conexión de control va por SSH (puerto 22), pero la memoria de la VM viaja por una conexión TCP directa entre los dos QEMU, en un puerto del rango 49152-49215 del host de destino. Si usas UFW, permite ese rango solo desde la IP del otro host. En host-b:
sudo ufw allow from 10.0.0.10 to any port 49152:49215 proto tcp
Y en host-a, para las migraciones de vuelta:
sudo ufw allow from 10.0.0.20 to any port 49152:49215 proto tcp
Asegúrate también de que SSH está permitido (sudo ufw allow OpenSSH) antes de activar UFW.
Paso 4: Montar el almacenamiento compartido por NFS
Con almacenamiento compartido, los discos de la VM están en la misma ruta en ambos hosts y solo hay que mover la memoria, que es lo más rápido. En el servidor NFS (10.0.0.30), instala el servidor y crea el directorio exportado:
sudo apt install nfs-kernel-server
sudo mkdir -p /srv/vm-images
Edita /etc/exports para exportar el directorio solo a los dos hosts KVM:
sudo nano /etc/exports
/srv/vm-images 10.0.0.10(rw,sync,no_subtree_check,no_root_squash) 10.0.0.20(rw,sync,no_subtree_check,no_root_squash)
no_root_squash es necesario porque libvirt cambia el propietario de las imágenes de disco al arrancar cada VM. Por eso la exportación debe limitarse a las IPs de los hosts de virtualización. Aplica la configuración y compruébala:
sudo exportfs -ra
sudo exportfs -v
/srv/vm-images 10.0.0.10(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/srv/vm-images 10.0.0.20(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
Ahora, en host-a y en host-b, monta el recurso en la misma ruta. La ruta tiene que ser idéntica en los dos hosts, porque la definición XML de la VM hace referencia a ella:
sudo mkdir -p /var/lib/libvirt/images/shared
sudo nano /etc/fstab
Añade esta línea al final:
10.0.0.30:/srv/vm-images /var/lib/libvirt/images/shared nfs defaults,_netdev 0 0
Monta y verifica:
sudo systemctl daemon-reload
sudo mount -a
df -h /var/lib/libvirt/images/shared
Filesystem Size Used Avail Use% Mounted on
10.0.0.30:/srv/vm-images 1.8T 1.2G 1.8T 1% /var/lib/libvirt/images/shared
Crea un archivo en un host y comprueba que aparece en el otro para confirmar que ambos ven el mismo almacenamiento.
Paso 5: Elegir un modelo de CPU compatible
La VM no puede cambiar de CPU a mitad de ejecución: todas las instrucciones que vea en el origen tienen que existir también en el destino. El modo host-passthrough expone la CPU física exacta y solo es seguro si ambos hosts tienen el mismo modelo de procesador y el mismo microcódigo. Para hosts distintos, usa un modelo con nombre que ambos soporten.
Lista en cada host los modelos de CPU que QEMU puede usar en esa máquina:
virsh domcapabilities | grep "usable='yes'"
<model usable='yes' vendor='Intel' canonical='Skylake-Server-v1'>Skylake-Server</model>
<model usable='yes' vendor='Intel' canonical='Cascadelake-Server-v1'>Cascadelake-Server</model>
<model usable='yes' vendor='Intel' canonical='Icelake-Server-v1'>Icelake-Server</model>
Elige el modelo más moderno que aparezca como usable='yes' en los dos hosts. Si los dos servidores son idénticos, puedes usar host-model, que libvirt traduce a un modelo concreto y lo transmite al destino durante la migración.
Notasi ya tienes VMs en producción con
host-passthrough, cambiar el modelo de CPU requiere apagarlas y arrancarlas de nuevo. Hazlo en una ventana de mantenimiento.
Paso 6: Crear una VM de prueba en el almacenamiento compartido
Para probar, crea una VM a partir de la imagen cloud oficial de Ubuntu 24.04. En host-a, descárgala al almacenamiento compartido y crea un disco de 10 GB basado en ella:
cd /var/lib/libvirt/images/shared
sudo wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
sudo qemu-img convert -O qcow2 noble-server-cloudimg-amd64.img testvm.qcow2
sudo qemu-img resize testvm.qcow2 10G
Crea la VM con virt-install. Sustituye Skylake-Server por el modelo que elegiste en el paso anterior:
sudo virt-install \
--name testvm \
--memory 2048 \
--vcpus 2 \
--cpu Skylake-Server \
--disk path=/var/lib/libvirt/images/shared/testvm.qcow2,format=qcow2,bus=virtio,cache=none \
--import \
--osinfo linux2022 \
--network network=default,model=virtio \
--cloud-init root-ssh-key=$HOME/.ssh/id_ed25519.pub \
--graphics none \
--noautoconsole
Dos detalles importan para la migración:
cache=none: libvirt rechaza la migración en vivo de discos en almacenamiento compartido con otros modos de caché, porque podrían quedar datos pendientes en la caché de página del origen.- La red: el destino debe tener una red o bridge con el mismo nombre. Aquí se usa la red NAT
defaultque crea libvirt en ambos hosts, suficiente para la prueba. En producción usarás un bridge (--network bridge=br0) presente con el mismo nombre en los dos hosts, para que la VM conserve su IP pública o privada al migrar.
Comprueba que la VM está en marcha y obtén su IP:
virsh list
virsh domifaddr testvm
Id Name State
------------------------
1 testvm running
Name MAC address Protocol Address
-------------------------------------------------------------
vnet0 52:54:00:3a:5c:81 ipv4 192.168.122.57/24
Paso 7: Migrar la VM en vivo
Lanza la migración desde host-a:
virsh migrate --live --persistent --undefinesource --verbose \
testvm qemu+ssh://your_user@host-b/system
Qué hace cada opción:
--live: la VM sigue funcionando durante la copia de memoria.--persistent: define la VM de forma permanente enhost-b, para que sobreviva a un reinicio de libvirt.--undefinesource: elimina la definición dehost-aal terminar, así la VM no queda duplicada.--verbose: muestra el progreso.
Migration: [100 %]
Mientras la migración está en curso, puedes seguir su progreso desde otra terminal en host-a:
virsh domjobinfo testvm
Job type: Unbounded
Operation: Outgoing migration
Time elapsed: 4213 ms
Data processed: 1.105 GiB
Data remaining: 512.320 MiB
Memory bandwidth: 268.410 MiB/s
Paso 8: Verificar la migración
En host-b, la VM debe aparecer en ejecución:
virsh list --all
Id Name State
------------------------
1 testvm running
Y en host-a ya no debe existir:
virsh list --all
Id Name State
--------------------
Para comprobar que la migración no interrumpe el servicio, repite la migración (de vuelta a host-a) mientras mantienes un ping continuo a la VM desde otra máquina de la misma red. Lo normal es no perder ningún paquete o, como mucho, uno.
Migrar sin almacenamiento compartido
Si los hosts no comparten almacenamiento, libvirt puede copiar también el disco durante la migración con --copy-storage-all. Es mucho más lento, porque transfiere el disco completo, pero no requiere NFS.
Antes de migrar, crea en el destino una imagen vacía del mismo tamaño virtual, formato y ruta que el disco original. En host-b:
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/testvm.qcow2 10G
Después, desde host-a:
virsh migrate --live --persistent --undefinesource --copy-storage-all --verbose \
testvm qemu+ssh://your_user@host-b/system
Consulta el tamaño virtual exacto del disco original con qemu-img info antes de crear la imagen de destino; si no coincide, la migración falla.
Ajustar migraciones de VMs con mucha carga
Una VM que escribe en memoria más deprisa de lo que la red puede copiarla no converge nunca. Tienes varias opciones:
-
Limitar el tiempo máximo de pausa aceptable en milisegundos. Un valor más alto permite terminar antes:
virsh migrate-setmaxdowntime testvm 500 -
Usar
--auto-converge, que frena progresivamente las vCPUs de la VM hasta que la copia alcanza a las escrituras:virsh migrate --live --auto-converge --persistent --undefinesource --verbose \ testvm qemu+ssh://your_user@host-b/system -
Usar post-copy (
--postcopy): la VM arranca en el destino antes de tener toda la memoria y pide las páginas que faltan bajo demanda. Converge siempre, pero si la red falla en esa fase la VM se pierde, así que úsalo solo en redes fiables.
Solución de problemas
the CPU is incompatible with host CPU: Host CPU does not provide required features: el modelo de CPU de la VM usa instrucciones que el destino no tiene. Cambia la VM a un modelo común (paso 5) y reiníciala.
Unsafe migration: Migration without shared storage is unsafe: libvirt no detecta que el disco esté en almacenamiento compartido. Comprueba que la ruta es un montaje NFS en ambos hosts y que la VM usa cache=none, o usa --copy-storage-all.
unable to connect to server at 'host-b:49152': No route to host: el origen no resuelve el nombre del destino o el firewall bloquea el rango 49152-49215. Revisa /etc/hosts y UFW, o indica la IP explícitamente con --migrateuri tcp://10.0.0.20.
Error sobre una red o un bridge inexistente (por ejemplo br0): la red que usa la VM no existe en el destino. Créala con el mismo nombre en host-b o comprueba con virsh net-list --all que la red default está activa.
Revisa los registros de libvirt en ambos hosts para cualquier otro error:
sudo journalctl -u libvirtd --since "10 minutes ago"
Conclusión
Has preparado dos hosts KVM con almacenamiento compartido por NFS, un modelo de CPU común y acceso SSH entre ellos, y has migrado una VM en marcha sin apagarla. Como siguientes pasos, puedes dedicar una red separada al tráfico de migración para no competir con el de las VMs, sustituir NFS por almacenamiento distribuido como Ceph para eliminar el punto único de fallo, o automatizar el vaciado de un host antes de un mantenimiento migrando todas sus VMs con un bucle sobre virsh list --name.
