Backup de máquinas virtuales con KVM: save + restore

per Victor Carceler darrera modificació 2021-01-07T10:31:43+01:00

Rohan ejecuta las máquinas virtuales que mantienen los servicios del centro. Para realizar los backups de estas MVs se ha estado utilizado una estrategia muy simple: apagar las máquinas, realizar un snapshot del pool ZFS en el que están sus discos, arrancar de nuevo las máquinas y propagar el snapshot de ZFS a otra máquina.

Tanto Rohan como Gondolin —la máquina física a la que se copian los snapshots— mantienen los últimos 20 snapshots. Además, gracias a la magia de ZFS los snapshots no consumen demasiado (porque comparten muchos datos) y enviar el último snapshot desde Rohan a Gondolin es una operación eficiente que se realiza transmitiendo únicamente las diferencias respecto al snapshot anterior. Afortunadamente en nuestro entorno resultaba aceptable que se interrumpa el servicio de madrugada durante unos minutos.

Este esquema en general ha funcionado y cuando ha sido necesario recuperar una MV se ha podido hacer con gran comodidad. Pero hay que admitir que este esquema tan sencillo tiene algún inconveniente muy molesto: no se puede confiar en que una MV se detenga por completo al recibir la correspondiente señal ACPI.

El propio manual de virsh lo explica muy bien:

       shutdown domain [--mode MODE-LIST]
           Gracefully shuts down a domain.  This coordinates with the domain OS to perform graceful shutdown, so there
           is no guarantee that it will succeed, and may take a variable length of time depending on what services
           must be shutdown in the domain.

Y lamentablemente de tanto en tanto alguna máquina obstinada no se detiene al recibir uno —o reiterados— virsh shutdown. Cuando pasa esto el proceso de backup queda esperando a que se detengan las MVs y no llega a completarse nunca. Un desastre. Total, que es necesario refinar algo la estrategia de backups para que funcione sin colaboración del SO de la MV.

La wiki de libvirt tiene un artículo en el que se propone una estrategia alternativa: Efficient live disk backup with active blockcommit.

Pero esta estrategia no me ha convencido porque no guarda todo el estado de la MV. Si queda descartado apagar las MVs para hacer el backup, entonces es necesario guardar tanto el disco como la RAM de la MV. Para este fin se puede utilizar los comandos virsh save o virsh managedsave.

El comando save permite especificar un fichero en el que se guardará la ram, así una vez guardadas las MVs es posible tomar un snapshot del pool zfs que contenga las imágenes de disco y los ficheros en los que se ha guardado la ram. Y finalmente utilizar virsh restore para volver a leer esos ficheros y hacer que continúe la ejecución de las MVs.

Es importante notar que cuando se ha guardo una MV no conviene lanzarla con virsh start, pues entonces se arrancará la máquina indicada utilizando sus discos duros sin cargar el estado de la RAM. Es decir, se tratará la MV como si estuviera arrancando por primera vez.

El comando virsh managedsave simplifica este proceso pues:

  • Se decide automáticamente guardar la RAM de cada MV en /var/lib/libvirt/qemu/save
  • Se puede arrancar de nuevo la MV con virsh start (y en caso de que se trate de una MV guardada cargará su estado para reanudar la ejecución)
Pero en nuestro caso he optado por utilizar la combinación save / restart porque el pool de zfs no está montado en /var/lib/libvirt y la ruta en la que se guardan los estados de las MVs con managedsave queda fuera del control de zfs.

Copias de seguridad con el nuevo sistema

Las copias de seguridad están programadas en cron para comenzar a las 5:00. Al revisar el fichero de registro se puede ver:

Mar 21 05:00:01 rohan CRON[26950]: (root) CMD (/root/bin/backup-images-saverestore)
Mar 21 05:00:01 rohan backup-images-saverestore: Start backup-images-saverestore
Mar 21 05:00:01 rohan backup-images-saverestore: copy .xml domain files
Mar 21 05:00:01 rohan backup-images-saverestore: Ok
Mar 21 05:00:01 rohan backup-images-saverestore: Saving VMs:
Mar 21 05:00:01 rohan backup-images-saverestore: Saving: elastix-2.5
Mar 21 05:00:04 rohan backup-images-saverestore: Saving: owncloud
Mar 21 05:00:14 rohan backup-images-saverestore: Saving: moodle-1804
Mar 21 05:01:01 rohan backup-images-saverestore: Saving: cirdan-16.04
Mar 21 05:01:58 rohan backup-images-saverestore: Saving: baba-yaga-1804-v2
Mar 21 05:03:24 rohan backup-images-saverestore: Saving: blog
Mar 21 05:03:53 rohan backup-images-saverestore: Saving: plone
Mar 21 05:05:15 rohan backup-images-saverestore: Saving: mp08
Mar 21 05:05:20 rohan backup-images-saverestore: Saving: unifi
Mar 21 05:05:31 rohan backup-images-saverestore: Saving: matrioska-1804
Mar 21 05:06:01 rohan backup-images-saverestore: Saving: matrioska
Mar 21 05:06:45 rohan backup-images-saverestore: Saving: valinor
Mar 21 05:07:11 rohan backup-images-saverestore: All VMs saved.
Mar 21 05:07:11 rohan backup-images-saverestore: Creating snapshot pool/images@2019-03-21_05:00:01
Mar 21 05:07:21 rohan backup-images-saverestore: Created new snapshot: pool/images@2019-03-21_05:00:01
Mar 21 05:07:21 rohan backup-images-saverestore: Restoring VMs:
Mar 21 05:07:21 rohan backup-images-saverestore: Restoring: elastix-2.5
Mar 21 05:07:25 rohan backup-images-saverestore: Restoring: owncloud
Mar 21 05:08:20 rohan backup-images-saverestore: Restoring: moodle-1804
Mar 21 05:09:18 rohan backup-images-saverestore: Restoring: cirdan-16.04
Mar 21 05:10:01 rohan backup-images-saverestore: Restoring: baba-yaga-1804-v2
Mar 21 05:10:32 rohan backup-images-saverestore: Restoring: blog
Mar 21 05:10:47 rohan backup-images-saverestore: Restoring: plone
Mar 21 05:11:17 rohan backup-images-saverestore: Restoring: mp08
Mar 21 05:11:24 rohan backup-images-saverestore: Restoring: unifi
Mar 21 05:11:34 rohan backup-images-saverestore: Restoring: matrioska-1804
Mar 21 05:11:45 rohan backup-images-saverestore: Restoring: matrioska
Mar 21 05:12:00 rohan backup-images-saverestore: Restoring: valinor
Mar 21 05:12:02 rohan backup-images-saverestore: All VMs restored.
Mar 21 05:12:02 rohan backup-images-saverestore: Wake On LAN: Gondolin
Mar 21 05:12:02 rohan backup-images-saverestore: Waiting for Gondolin to boot
Mar 21 05:13:02 rohan backup-images-saverestore: Start zfs send to Gondolin
Mar 21 05:13:02 rohan backup-images-saverestore: BASE_SNAPSHOT=pool/images@2019-03-20_15:50:01 NEW_SNAPSHOT=pool/images@2019-03-21_05:00:01
Mar 21 05:19:53 rohan backup-images-saverestore: zfs send completed
Mar 21 05:19:53 rohan backup-images-saverestore: Shutting down Gondolin
Mar 21 05:19:53 rohan backup-images-saverestore: End backup-images-saverestore

 

Todo el proceso ha durado cerca de 20 minutos, pero hay que notar que solo durante 12 minutos ha habido alguna máquina virtual apagada. El resto del tiempo corresponde al envío del snapshot ZFS de Rohan a Gondolín.

En esta ejecución se ha guardado el estado de 12 MVs que consumían aproximadamente 16GiB de RAM en 7 minutos y 11 segundos, lo que ha resultado en una velocidad efectiva de escritura a disco de 39.2MB/s.

Restaurar las máquinas ha llevado 4 minutos y 41 segundos.

Enviar el snapshot de los discos, las configuraciones .xml y la ram de las MVs de Rohan a Gondolin ha llevado 6 minutos y 51 segundos.

Se podría reducir el tiempo sin servicio haciendo un ciclo de guardado, snapshot y restauración independiente para cada una de las MVs en lugar de hacerlo con todas a la vez. Pero eso implicaría utilizar un dataset zfs diferente para guardar los datos de cada máquina. Es posible, pero es demasiado complejo y resulta más estimulante pensar en una nueva versión de Rohan con discos SSD M.2 :-)