Distribución de imagenes de disco con Clonezilla y Netboot (iPXE)

Fecha publicación
#
38
Dificultad
Avanzado
3 more properties
Publicado
tags
netboot, IoT Gateway, iPXE, IoT, Industrial IoT, PXE, netboot.xyz, clonezilla, backup, restore, IoT GW, PXE, DHCP, Hyper-V, Image, Clone, NAS

Sinopsis:
El objetivo es arrancar nuestro IoT Gateway con un arranque por red usando iPXE (Preboot Execution Environment) y gracias a los menús de arranque poder hacer una imagen del disco, o restaurarla, usando Clonezilla.

Vídeos

Lista de reproducción con todos los vídeos, aquí.

Tabla de Contenidos:

Esquema del proceso

Netboot-Clonezilla.drawio.png
Image source:

Descripción del proceso

  1. Arrancar IoT GW usando el arranque por red.
  2. El servidor HTTP server le servirá un menú de arranque.
  3. El usuario, decide que acción quiere realizar (clonar el disco, o restaurar). Seguidamente se le preguntará por el hostname del ordenador sobre el que quiere actuar.
  4. Seguidamente el IoT GW le pedirá al servidor HTTP el Kernel (fichero, vmlinuz), para que arrancar. Además del fichero initrd, primer proceso del sistema, y el squashfs donde hay la raíz del disco con todos los ficheros de sistema.
  5. Los startup scripts; son unos simples shell scripts que se descargan del HTTP server y que se ejecutan en cadena. Los archivos se obtienen en formato TAR + GZIP (.tgz) y se descomprimen dentro del directorio /opt en el siguiente orden.
    1. Primero se descarga un fichero llamado boot.tgz y se descomprime en /opt
      Este paquet tiene estos ficheros:
      YAML
      boot.config: define variables necesarias para los siguientes scripts
      mount.sh: mounta del NAS el repositorio donde se localizan las imagenes del disco para ese hostname.
      backup.sh: lanza el proceso de clonado de clonezilla
      restore.sh: lanza el proceso de restauración de clonezilla
    2. Después otro fichero llamado hostname_del_ordenador.tgz y se descomprime en /opt
      Solo hay un fichero en este paquete:
      YAML
      hostname_del_ordenador.config: contiene la variable con el nombre de dispositivo (disco) a copiar/clonar o a donde restaurar.
    3. Seguidamente el fichero /etc/rc.local se encarga de ejecutar la acción seleccionada en el menú de arranque.
      1. Primero, se ejecuta mount.sh para montar el respositorio de las copias para ese host.
        En nuestro caso este montaje se hace a través del SSHFS.
      2. Seguidamente en función de la acción escogida en el menú de arranque se lanzará backup.sh o restore.sh
        Para saber cual de los dos scripts hay que lanzar nos fijaremos en el parámetro del kernel llamado clonezilla

        Echando un vistazo al fichero /etc/rc.local se puede entender la explicación anterior de una forma más precisa:
        Bash
        #!/bin/bash
        
        ACTION=$(cat /proc/cmdline | tr ' ' '\n' | grep clonezilla | cut -f 2 -d '=')
        cd /opt
        ./mount.sh && ./${ACTION}.sh
  6. En este punto ya podemos ejecutar Clonzilla para hacer la copia/clonar el disco, o restuarar una imagen. Recordemos que partimos de un escenario donde hay un Linux corriendo en RAM, en el que en un directorio del sistema de ficheros tenemos montado por SSHFS el repositorio donde colocar la imagen de la copia del disco o desde donde restaurar la imagen.
    El fichero de copia backup.sh, tiene esta pinta:
    Bash
    #!/bin/bash
    source /opt/boot.config
    source /opt/${MAQUINA_ID}.config
    
    ocs-sr -senc -noabo -fsck-y -gs -z1p -sc savedisk ${MAQUINA_ID} ${DISK}
    
    Detalles del comando de clonezilla y sus parámetros:
    • ocs-sr: Herramienta de línea de comandos para guardar o restaurar una imagen de disco o partición.
    • senc: Omite la encriptación de la imagen con una frase de paso.
    • noabo: Hace que la imagen de la partición pueda ser accedida por otros usuarios, no solo por el propietario, cambiando los permisos de acceso del archivo de imagen.
    • fsck-y: Ejecuta fsck automáticamente en el sistema de archivos fuente antes de guardarlo. Esta opción intentará corregir automáticamente cualquier corrupción detectada en el sistema de archivos sin pedir confirmación.
    • gs: Genera el checksum SHA1 para la imagen. Esto es útil para verificar la integridad de la imagen al restaurarla, aunque se señala que puede llevar bastante tiempo si el tamaño de la imagen es grande.
    • z1p: Comprime la imagen usando gzip en paralelo (pigz) durante el proceso de guardado. Esta opción es adecuada para aprovechar múltiples núcleos o CPUs, ofreciendo una compresión rápida y eficiente.
    • sc: Omite la verificación de si la imagen puede ser restaurada después de ser creada. Por defecto, Clonezilla verificaría esto, pero con este parámetro, esa verificación se salta.
    • savedisk: Indica que la operación guardará un disco entero en un archivo de imagen.
    • ${MAQUINA_ID}: Sería el identificador de la máquina o el nombre de la imagen bajo la cual se guardará el disco. Este parámetro especifica cómo se nombrará la carpeta de la imagen dentro de /home/partimag.
    • ${DISK}: El nombre del disco que se va a clonar o guardar. Este puede ser, por ejemplo, sda, y no necesita incluir el prefijo /dev/.
    Este comando, en resumen, inicia un proceso para guardar un disco completo (savedisk) en una imagen, omitiendo la encriptación de la imagen, cambiando los permisos para que otros usuarios puedan acceder a la imagen, verificando y corrigiendo automáticamente cualquier error en el sistema de archivos del disco fuente, generando un checksum SHA1 para la imagen, comprimiendo la imagen con gzip en paralelo para una mayor eficiencia, y omitiendo la verificación post-guardado sobre la capacidad de restauración de la imagen.
    Y el script de restauracion restore.sh, tiene este contenido:
    Bash
    #!/bin/bash
    source /opt/boot.config
    source /opt/${MAQUINA_ID}.config
    
    ocs-sr -e1 auto -e2 -r -j2 -scr -p reboot restoredisk ${MAQUINA_ID} ${DISK}
    Detalles del comando y sus parámetros:
    • ocs-sr: Herramienta de línea de comandos utilizada para guardar o restaurar una imagen de disco o partición.
    • e1 auto: Fuerza el cambio de los valores CHS (cilindros, cabezas, sectores) de la partición de arranque NTFS después de que se restaure la imagen, si "auto" está asignado, Clonezilla detectará automáticamente la partición de arranque NTFS.
    • e2: Fuerza el uso de los valores CHS (cilindros, cabezas, sectores) desde EDD (Enhanced Disk Device) al crear la tabla de particiones con sfdisk.
    • r: Redimensiona la partición cuando finaliza la restauración, ajustando el tamaño del sistema de archivos para que se ajuste al tamaño de la partición. Es útil cuando una imagen de una partición pequeña se restaura en una partición más grande.
    • j2: Utiliza dd para clonar la imagen de los datos entre el MBR (primer sector, es decir, 512 bytes) y la primera partición, lo cual puede ser útil para algunas herramientas de recuperación. Este parámetro solo funciona en el modo restoredisk, no en restoreparts.
    • scr: Omite la verificación de si la imagen es restaurable antes de restaurarla. Por defecto, Clonezilla realizaría esta verificación.
    • p reboot: Especifica que la máquina se reiniciará automáticamente después de que finalice la restauración de la imagen.
    • restoredisk: Indica que la operación restaurará un disco completo desde un archivo de imagen.
    • ${MAQUINA_ID}: Sería el identificador de la máquina o el nombre de la imagen que se va a restaurar. Este parámetro especifica el nombre de la carpeta de la imagen dentro de /home/partimag que se usará para la restauración.
    • ${DISK}: El nombre del disco al que se va a restaurar la imagen, por ejemplo, sda. Este puede ser con o sin el prefijo /dev/.
    Este comando inicia un proceso para restaurar un disco completo desde una imagen específica, ajustando automáticamente los valores CHS si es necesario, redimensionando la partición para que coincida con el tamaño del sistema de archivos, clonando datos ocultos que podrían ser importantes para algunas herramientas de recuperación, y omitiendo la verificación previa de restauración para agilizar el proceso. Al finalizar, el sistema se reiniciará automáticamente.

Configuración DHCP para arrancar con iPXE

Para que una máquina arranque en red hacen falta diferentes configuraciones.
  1. La primera depende totalmente de la BIOS del IoT Gateway y en esta no podemos ayudaros mucho, en esencia hay que decirle a la secuencia de arranque que debe empezar buscando un arranque en red.
  2. Seguidamente hay que tener configurado el servidor DHCP adecuadamente, otro tema que dependerá mucho del servidor DHCP que estemos usando. A cotinuación se describe como se debe configurar isc-dhcp-server para Ubuntu 22.04.
    Para ser concretos nos interesa configurar la opción filename.
    • filename: se puede configurar la ruta del menu de arranque, fichero *.ipxe, con diferentes protocolos. Nosotros hemos usado HTTP para indicar donde se encuentra el servidor HTTP server del esquema inicial. La peculiaridad es que esto nos permitiría tener este servidor fuera de la LAN, gracias a que ya tenemos el stack IP configurado.
    Ejemplo,
    Bash
    subnet 10.147.18.0 netmask 255.255.255.0 {
      range 10.147.18.25 10.147.18.50;       # Change this range as appropriate for your network
      option subnet-mask 255.255.255.0;
      option routers 10.147.18.99;           # Change this to the address of your router
      option broadcast-address 10.147.18.255;
      option domain-name "mynetwork.lan";    # This is optional
      option domain-name-servers 8.8.8.8;
      filename "http://example.tld/uri/menu.ipxe";
    }

Instalación HTTP server

Se usa docker, más concretamente docker compose para desplegar un simple servidor HTTP (HTTP server del esquema inicial). Este debe estar accesible por parte de la máquina que debe usar iPXE para arrancar. No hace falta que esten en la misma red; podría estar montado en Internet si hace falta.
Este es el fichero docker-compose.yml, o compose.yml que podéis usar:
Docker
version: '3.9'
services:
	netboot:
		image: p3terx/darkhttpd
		container_name: netboot
		restart: unless-stopped
		volumes:
		- './assets:/www:ro'
		entrypoint: ["/darkhttpd","/www"]
    ports:
      - 80:80

Menús iPXE

Configurar estos menús es un dolor de cabeza. En este caso se ha basado la configuración en los menús de Netboot.XYZ. En case al docker compose se deberá crear un directorio llamado config dentro del directorio assets. Dentro de este directorio se colocarán los scripts ipxe; los encargados de definir como deben ser los menús que debe mostrar la máquina que arranca por red.
Por defecto, netboot.xyz tiene muchos ficheros y opciones para definir un menú lleno de posibilidades, tanto para arrancar distribuciones de Linux live, como para arrancar soluciones de rescate y mucho más.
En este caso se ha reducido el menú a la mínima expresión:
Untitled
Finalmente en la carpeta config, dejaremos los siguientes ficheros:

Obtención del hostname

Con el fin de facilitar el proceso de copia/clonado y de restauración, se le pregunta al usuario cual es el nombre del hostname de la máquina sobre la que estamos actuando. Este proceso esta definido dentro de menu.ipxe.
Concretamente el código a tener en cuenta es este:
Bash
...
echo Please, tell the machine name: (ex. m5)
read maquina_id
...
El texto introducido por el usuario quedará registrado en la variable: maquina_id
Esta variable junto con otras se pasarán como parámetros de arranque. En el siguiente apartado se explica como se pasan estos varoles y como se recuperan después.

Recuperación de parámetros de arranque

Cuando se carga el kernel, este va acompañado de una serie de parámetros para indicarle cual debe ser su comportamiento concreto. Por ejemplo, el parámetro quiet sirve para que el kernel arranque en modo silencioso y sin todos esos mensajes detallados al que nos tiene acostumbrado Linux.
Pues bien, todos estos parámetros de arranque los podemos recuperar una vez arrancado el sistema consultando el fichero /proc/cmdline.
En el caso que nos ocupa si miramos concretamente en esta parte del código de menu.ipxe podremos ver todos los parámetros que se pasan al kernel:
Bash
...

:clonezilla-boot
imgfree
echo Please, tell the machine name: (ex. m5)
read maquina_id
set ocs_params ocs_live_run="sudo /etc/rc.local"
set extra_params maquina_id=${maquina_id}
set ocs_preload1 ocs_preload1=${live_endpoint}/boot.tgz 
set ocs_preload2 ocs_preload2=${live_endpoint}/${maquina_id}.tgz
set path /debian-squash/releases/download/3.1.2-9-80072992/
set url ${live_endpoint}${path}
kernel ${url}vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset keyboard-layouts=es locales=en_US.UTF-8 ${ocs_params} ${extra_params} ${ocs_preload1} ${ocs_preload2} ${clonezilla} ocs_live_batch=no ocs_screen_blank="no" net.ifnames=0 nosplash noprompt fetch=${url}filesystem.squashfs initrd=initrd.magic loglevel=3 ${cmdline} 
initrd ${url}initrd
boot

...

Al prestar atención se ve que hay unos cuantos parámetros que vale la pena comentar; seteo de las variables:
estás variables después se pasan al kernel así:
Bash
kernel ${url}vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset keyboard-layouts=es locales=en_US.UTF-8 ${ocs_params} ${extra_params} ${ocs_preload1} ${ocs_preload2} ${clonezilla} ocs_live_batch=no ocs_screen_blank="no" net.ifnames=0 nosplash noprompt fetch=${url}filesystem.squashfs initrd=initrd.magic loglevel=3 ${cmdline} 
Una vez ha iniciado el sistema como en /proc/cmdline hay todos los parámetros del kernel.
De forma que con simples scripts de shell se puede extraer sus valores, por ejemplo el hostname:
Bash
cat /proc/cmdline | tr ' ' '\n' | grep maquina_id= | cut -f 2 -d '='

Cambios en el squashfs original

Netboot.xyz ofrece varias imagenes de Clonezilla, para ser más exactos 4; un par para Debian y otras dos para Ubuntu. En sus versiones estables y de desarrollo.
A pesar de que las imágenes son muy parametrizables a través de los parámetros del kernel, hay algunas funciones que se echaban de menos.
Basándonos en la imagen Debian estable, se ha modificado la imagen original; con los siguientes cambios:

Paquetes añadidos: btop y byobu

Cambios relativos al SSH

Servicio rc.local

Modificaciones en /etc/profile

Como se introducen cambios en un sistema de ficheros squash

A partir de la web de Netboot.XYZ se puede descargar la imagen que interese. En este caso se descarga la versión de Clonezilla estable basada en Debian.
Se asume que se ha copiado y dejado en el directorio el fichero squashfs de clonezilla:
Bash
apt install -y squashfs-tools
cd clonezilla
sudo unsquashfs filesystem.squashfs
dentro del directorio: squashfs-root se pueden hacer los cambios que se necesiten squash.filesystem; una buena recomendación es montarlo en chroot para poder usar cosas como el sistema de paquetes y otros similares.
Para trabajar en chroot, una buena idea es usar esta secuencia de comandos:
Bash
sudo mount --bind /dev squashfs-root/dev
sudo mount --bind /proc squashfs-root/proc
sudo mount --bind /sys squashfs-root/sys
sudo chroot squashfs-root
Llegados a este punto se tiene acceso a una shell donde podemos hacer los cambios que queramos en la imagen.
Una vez listos los cambios para salir del entorno chrooted:
Shell
# inside chroot, run:
exit
# when chroot exits, run:
sudo umount -l squashfs-root/dev
sudo umount -l squashfs-root/proc
sudo umount -l squashfs-root/sys
Finalmente, para crear la nueva imagen del squashfs se puede ejecutar:
Shell
sudo mksquashfs squashfs-root filesystem.squashfs -b 1024k -comp xz -Xbcj x86 -e boot
Ahora solo falta mover el fichero filesystem.squashfs y copiarlo para que sea accesible des el servidor HTTP (HTTP server del esquema).

Script que se ejecuta post-booting rc.local

Este script es parte del squash.filesystem; por lo tanto no es buena idea ir modicándolo ya que después se deberá volver a crear el fichero squash y distribuirlo al servidor.
Por lo tanto interesa que el contenido de este script sea muy sencillo y flexible a la vez, así es como se ha diseñado:
Bash
#!/bin/bash

ACTION=$(cat /proc/cmdline | tr ' ' '\n' | grep clonezilla= | cut -f 2 -d '=')
cd /opt
./mount.sh && ./${ACTION}.sh
Se puede observar que lo primero es descubrir si queremos hacer un backup, o restaurar. Información almacenada en el parámetro del kernel clonezilla y guardamos este valor (backup|restore) en la variable ACTION.
A continuación ejecutamos el script mount.sh y si todo va bien, el script que tiene el nombre de la acción recuperada: backup o restore.

Montaje del repositorio mount.sh

Tal y como indica su nombre el objetivo del script es montar la unidad de red donde hay las copias de seguridad de la máquina.
Este script se encuentra dentro de boot.tgz; al arrancar la distro live se descomprime este paquete en /opt. Allí es donde se encuentra el script mount.sh. El encargado de ejecutar el script es /etc/rc.local.
Un ejemplo del contenido del script mount.sh:
Bash
#!/bin/bash
source /opt/boot.config
source /opt/${MAQUINA_ID}.config

mkdir -p /home/partimag/${MAQUINA_ID}
sshfs ${SSHFS} ${DESTPATH} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o Compression=no
if [ $? -ne 0 ];
then
  echo "Error mounting backup"
  exit 1;
fi

echo "Backup mount point: ${DESTPATH}"
exit 0
Podemos obsevar que el montaje se hace con SSHFS, esto implica que previamente ya se ha instalado la clave pública de SSH que hay en la live que estamos ejecutando al servidor NAS que nos esta ofreciendo el acceso por SSH, concretamente la clave púlica la hemos puesto en el fichero ~/.ssh/authorized_keys.

Copia y restauración del disco, scripts: backup.sh y restore.sh

Tal y como hemos visto en el punto anterior, backup.sh y restore.sh se distribuyen empaquetados en boot.tgz y este fichero se recoge del servidor local de Netboot.XYZ. Su nombre describe claramente su cometido y el contenido del script es realmetne senzillo:

backup.sh

Bash
#!/bin/bash
source /opt/boot.config
source /opt/${MAQUINA_ID}.config

ocs-sr -senc -noabo -fsck-y -gs -z1p -sc savedisk ${MAQUINA_ID} ${DISK}

restore.sh

Bash
#!/bin/bash
source /opt/boot.config
source /opt/${MAQUINA_ID}.config

ocs-sr -e1 auto -e2 -r -j2 -scr -p reboot restoredisk ${MAQUINA_ID} ${DISK}
Como se puede ver, simplemente se cargan las variables genéricas boot.config y las específicas de la máquina a restaurar ${MAQUINA_ID}.config y se ejecuta el comando de Clonezilla que se ha descrito al inicio de este documento.

Ficheros de configuración para boot.tgz y <hostname>.tgz

En la carpeta assets se esperan los ficheros boot.tgz y almenos un <hostname>.tgz; evidentemente puede haber tantos ficheros de host como equipos se quieran manejar.
Para ser más concretos la operativa recomendada, para crear boot.tgz podría ser:
Untitled
Una vez creados los ficheros, desde dentro del directorio boot, se puede ejecutar el siguiente comando:
Bash
cd boot
rm ../boot.tgz || tar cvfz ../boot.tgz .
Aquí hay un ejemplo de como debería quedar el paquete.
Para crear un fichero <hostname>.tgz; asumiremos que hostname=m5, por lo tanto el fichero será: m5.tgz. El procedimiento es análogo aunque no hace falta tantos ficheros.
Untitled
Una vez creados los ficheros, desde dentro del directorio boot, se puede ejecutar el siguiente comando:
Bash
cd m5
rm ../m5.tgz || tar cvfz ../m5.tgz .
Aquí hay un ejemplo de como debería quedar el paquete.

Conclusión

Los procedimientos descritos distan de ser simples y fáciles de entender. Pero el valor que añaden en pro de la automatización y la rápida recuperación en caso de desastre son realmente remarcables. Por lo tanto, a día de hoy es la mejor solución que hemos sabido encontrar para llevar a cabo este servicio.

Recursos