Cómo montar un servidor de arranque PXE en Ubuntu 22.04
Tags: ubuntu, server, pxe, bash
Published: 2022-12-14 17:54:11

Eres SysAdmin de una empresa y tienes por delante restaurar la distribución de 25 equipos que necesitan reinstalación. Puede considerarse una labor tediosa que piensas que es mejor automatizar, además añadiendo una configuración personalizada. Pero, ¿esto es posible? Sí. ¿Que puedo dejar instalados 25 equipos todos iguales de manera simultánea? ¡Sí! Existe PXE, como si tienes 90 o 100 equipos, pero, ¿qué es eso y cómo puedo montarlo? Vamos a ello, solo necesitaremos Ubuntu Server 22.04 instalado en una máquina virtual donde montaremos este servidor PXE para poder instalar imágenes de Ubuntu 22.04. Podemos elegir otra versión, como la 20.04, pero así comprobamos la compatibilidad con la más LTS más reciente. El último punto será crear la personalización que, como las opiniones, cada uno puede tener la suya. 

## Introducción

PXE es un protocolo utilizado para iniciar sistemas operativos a través de la red. Se puede usar este método para instalar nuestra distribución de GNU/Linux favorita en portátil, servidor u ordenador de escritorio a través de la red.

## Configurando IP estática en Ubuntu Server 22.04 LTS

Antes de continuar, es mejor configurar el servidor de arranque PXE con una dirección IP estática. En esta primera sección, veremos cómo configurarla.

Para configurar una dirección IP estática en Ubuntu Server 22.04 LTS, abrimos el archivo de configuración de netplan con un editor de texto, en este caso **nano**:

```
sudo nano /ect/netplan/00-installer-config.yaml
```
E incluimos lo siguiente teniendo en cuenta que:
- La interfaz en este caso es enp0s3, puedes saber cuál es al teclear ```ip a``` en el terminal,
- 192.168.180/24 es la IP que deseamos darle y la máscara de red de la configuración,
- 192.168.1.1 es la IP del router como servidor DHCP,
- 1.1.1.1 y 8.8.8.8 son direcciones DNS reconocidas

y podemos crearlo bajo nuestros propios parámetros.

```bash
#This is the network config written by 'subiquity'. IP addresses are an example
network:
  ethernets:
    enp0s3:
      dhcp4: no
      addresses:
        - 192.168.1.180/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses:
          - 1.1.1.1
          - 8.8.8.8
  version: 2
```
Para aplicar los cambios, ejecutamos:

```
sudo netplan apply
```

## Creando la estructura de directorios requerida

Nuestra estructura de directorios tendrá la siguiente forma:

**`/pxeboot`**

y como subdirectorios

- **`/config/`** , donde guardaremos los archivos de arranque.
- **`/firmware/`**, donde guardaremos los firmware.
- **`/images/`**, donde guardaremos las imágenes que pueden listarse en el menú.

En el directorio `/pxeboot/images/` copiaremos el contenido de cada imagen ISO en un subdirectorio por cada distribución. Esto lo veremos más adelante.  

Para crear todos los directorios requeridos en un solo comando, ejecutamos:

```
sudo mkdir -pv /pxeboot/{config,firmware,images}
```

## Descargando el código fuente de iPXE y compilación en Ubuntu 22.04

Estos pasos los haremos en nuestra carpeta de `\home\$USER`, lo último será moverlo todo a los directorios creados en el paso anterior.

En primer lugar, actualizamos los repositorios e instalamos los siguientes paquetes:

```
sudo apt update && sudo apt install build-essential liblzma-dev isolinux git -y
```

Clonamos el repositorio de iPXE en GitHub en nuestro servidor:

```
git clone https://github.com/ipxe/ipxe.git
```

Accedemos a la carpeta `ipxe/src`:

```
cd ipxe/src
```

Para configurar iPXE como arranque automático, hemos de crear el script correspondiente y relacionarlo con los firmwares de BIOS y UEFI cuando ya esté todo compilado.

Creamos el script de arranque para iPXE **bootconfig.ipxe** y lo abrimos con nuestro editor favorito:

```
nano bootconfig.ipxe
```

Copiando el siguiente contenido:

```bash
#!ipxe
dhcp
chain tftp://192.168.1.180/config/boot.ipxe
```
Recordemos que la IP es la que hemos configurado como IP estática del servidor.

Para compilar los firmwares de iPXE tanto para BIOS como para UEFI y relacionadrlo con el menú de arranque, ejecutamos el siguiente comando:

```
make bin/ipxe.pxe bin/undionly.kpxe bin/undionly.kkpxe bin/undionly.kkkpxe bin-x86_64-efi/ipxe.efi EMBED=bootconfig.ipxe
```

Esta tarea tomará varios minutos.

## Copiando los firmwares ya compilados

Una vez que los firmwares de iPXE estén compilados, los copiamos en `/pxeboot/firmware` de nuestro servidor de arranque para que los equipos donde deseemos instalar los sistemas operativos puedan acceder vía TFTP.

```
sudo cp -v bin/{ipxe.pxe,undionly.kpxe,undionly.kkpxe,undionly.kkkpxe} bin-x86_64-efi/ipxe.efi /pxeboot/firmware/
```

Los archivos **ipxe.pxe, undionly.kpxe, undionly.kkpxe, and undionly.kkkpxe** son para arranques BIOS, mientras que **ipxe.efi** es para arranques UEFI.

## Instalando y configurando DHCP y TFTP

Para que PXE arranque, necesitaremos un servidor DHCP y TFTP ejecutándose. En este caso, usaremos **dnsmasq**.

```
sudo apt install dnsmasq -y
```

Crearemos un nuevo archivo de configuración para dnsmasq. Renombramos el original `/etc/dnsmasq.conf` a `/etc/dnsmasq.conf.backup` de la siguiente manera:

```
sudo mv -v /etc/dnsmasq.conf /etc/dnsmasq.conf.backup
```

Creamos un archivo vacío:

```
sudo nano /etc/dnsmasq.conf
```

Y añadimos el siguiente contenido: 

```bash
interface=enp0s3
bind-interfaces
domain=glampinghub.local
dhcp-range=192.168.1.181,192.168.1.190,255.255.255.0,12h
dhcp-option=option:router,192.168.1.1
dhcp-option=option:dns-server,1.1.1.1
dhcp-option=option:dns-server,8.8.8.8
enable-tftp
tftp-root=/pxeboot

# boot config for BIOS systems
dhcp-match=set:bios-x86,option:client-arch,0
dhcp-boot=tag:bios-x86,firmware/ipxe.pxe

# boot config for UEFI systems
dhcp-match=set:efi-x86_64,option:client-arch,7
dhcp-match=set:efi-x86_64,option:client-arch,9
dhcp-boot=tag:efi-x86_64,firmware/ipxe.efi
```

Para que los cambios tengan efecto, reiniciamos el servidor de **dnsmasq**:

```
sudo systemctl restart dnsmasq
```

Para comprobar que el servicio está ejecutándose, podemos comprobarlo de la siguiente manera:

```
sudo systemctl status dnsmasq
```


```bash
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/lib/systemd/system/dnsmasq.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2022-11-07 07:53:18 UTC; 3h 41min ago
    Process: 697 ExecStartPre=/etc/init.d/dnsmasq checkconfig (code=exited, status=0/SUCCESS)
    Process: 743 ExecStart=/etc/init.d/dnsmasq systemd-exec (code=exited, status=0/SUCCESS)
    Process: 768 ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf (code=exited, status=0/SUCCESS)
   Main PID: 766 (dnsmasq)
      Tasks: 1 (limit: 4576)
     Memory: 3.5M
        CPU: 35ms
     CGroup: /system.slice/dnsmasq.service
             └─766 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=.,20326,8,2,e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d

nov 07 07:53:18 ubuntu2204pxe dnsmasq[766]: read /etc/hosts - 7 addresses
nov 07 07:53:18 ubuntu2204pxe systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
nov 07 07:58:56 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPDISCOVER(enp0s3) 08:00:27:37:b9:ec
nov 07 07:58:56 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPOFFER(enp0s3) 192.168.1.123 08:00:27:37:b9:ec
nov 07 07:58:56 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPDISCOVER(enp0s3) 08:00:27:37:b9:ec
nov 07 07:58:56 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPOFFER(enp0s3) 192.168.1.123 08:00:27:37:b9:ec
nov 07 07:59:14 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPDISCOVER(enp0s3) 08:00:27:37:b9:ec
nov 07 07:59:14 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPOFFER(enp0s3) 192.168.1.123 08:00:27:37:b9:ec
nov 07 07:59:15 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPDISCOVER(enp0s3) 08:00:27:37:b9:ec
nov 07 07:59:15 ubuntu2204pxe dnsmasq-dhcp[766]: DHCPOFFER(enp0s3) 192.168.1.123 08:00:27:37:b9:ec
```

## Instalando y configurando el servidor NFS

Ubuntu 22.04 usa **casper** para arrancar la instalación modo "live". **casper** soporta arranque PXE solo a través del protocolo **NFS**. Por ello, para arrancar Ubuntu Desktop 22.04 a través de PXE, necesitaremos un servidor NFS que sea accesible por toda la red local.

Para instalar NFS en el servidor, ejecutamos:

```
sudo apt install nfs-kernel-server
```

Abrimos el archivo de configuración de NFS `/etc/exports` de la siguiente manera:

```
sudo nano /etc/exports
```

Y añadimos la siguiente línea al final:

```
/pxeboot           *(ro,sync,no_wdelay,insecure_locks,no_root_squash,insecure,no_subtree_check)
```

Para efectuar los cambios y compartir la nueva configuración para que sea visible, ejecuta:

```
sudo exportfs -av
```

Obteniendo el siguiente resultado por pantalla: 

```
user@serverpxe:$ sudo exportfs -av
*:/pxeboot
```

## Configurando iPXE

<aside>
⚠️ La imagen ISO que descargaremos será de servidor, convirtiéndola en una versión de escritorio a través del autoinstalador de cloud-init como veremos en el último paso.
</aside>

Descargamos la imagen de Ubuntu Server 22.04 en nuestro directorio de `/home/$USER`. 

```
cd
```

```
wget https://releases.ubuntu.com/jammy/ubuntu-22.04.4-live-server-amd64.iso
```

Una vez descargado, montamos la imagen en el directorio `/mnt`:

```
sudo mount -o loop ubuntu-22.04.4-live-server-amd64.iso /mnt
```

Creamos un directorio dedicado a Ubuntu 22.04 en `/pxeboot/images/`:

```
sudo mkdir -pv /pxeboot/images/ubuntu-22.04
```

Para copiar los contenidos de la ISO, ejecutamos rsync de la siguiente manera:

```
sudo rsync -avz /mnt/ /pxeboot/images/ubuntu-22.04
```

Desmontamos la imagen de Ubuntu 22.04 LTS de '/mnt':

```
sudo umount /mnt
```

Creamos el archivo de configuración `/pxeboot/config/boot.ipxe`:

```
sudo nano /pxeboot/config/boot.ipxe
```

Y añadimos lo siguiente:

```bash
#!ipxe

set server_ip  192.168.1.180
set root_path  /pxeboot

menu Select an OS to boot
item ubuntu-22.04         Install Ubuntu 22.04 LTS
choose --default exit --timeout 10000 option && goto ${option}
:ubuntu-22.04

set os_root ubuntu-22.04
kernel tftp://${server_ip}/images/${os_root}/casper/vmlinuz
initrd tftp://${server_ip}/images/${os_root}/casper/initrd
imgargs vmlinuz initrd=initrd boot=casper netboot=nfs ip=dhcp nfsroot=${server_ip}:${root_path}/images/${os_root} ds=nocloud;s=/cdrom/nocloud/ autoinstall quiet splash ---
boot
```

El ejemplo de arriba es si montamos una sola imagen en el servidor. En el caso de que tengamos dos, por ejemplo Ubuntu 22.04 y Ubuntu 20.04, tendríamos un archivo similar a esto repitiendo los procesos de descarga, creación y montaje descritos arriba:

```bash
#!ipxe

set server_ip  192.168.1.180
set root_path  /pxeboot

menu Select an OS to boot
item ubuntu-20.04         Install Ubuntu 20.04 LTS
item ubuntu-22.04         Install Ubuntu 22.04 LTS
choose --default ubuntu-20.04 --timeout 10000 option && goto ${option}

:ubuntu-22.04
set os_root ubuntu-22.04
kernel tftp://${server_ip}/images/${os_root}/casper/vmlinuz
initrd tftp://${server_ip}/images/${os_root}/casper/initrd
imgargs vmlinuz initrd=initrd boot=casper maybe-ubiquity netboot=nfs ip=dhcp nfsroot=${server_ip}:${root_path}/images/${os_root} ds=nocloud;s=/cdrom/nocloud/ autoinstall quiet splash ---
boot

:ubuntu-20.04
set os_root ubuntu-20.04
kernel tftp://${server_ip}/images/${os_root}/casper/vmlinuz
initrd tftp://${server_ip}/images/${os_root}/casper/initrd
imgargs vmlinuz initrd=initrd boot=casper maybe-ubiquity netboot=nfs ip=dhcp nfsroot=${server_ip}:${root_path}/images/${os_root} ds=nocloud;s=/cdrom/nocloud/ autoinstall quiet splash ---
boot
```

Por comentar el menú de arriba, el parámetro `ds=nocloud;s=/cdrom/nocloud/` captará la configuración de cloud-init y `autoinstall` comenzará automáticamente la instalación. Eliminar este último es útil para cuando tenemos equipos que no descartan por defecto seguir eligiendo el arranque por red tras la instalación, entrando así en un bucle de instalaciones que no terminaría nunca. Si estamos con otra cosa y en un equipo ya está instalado y reiniciado, quitando `autoinstall` nos encontraremos con una pregunta sobre proceder con la instalación y la necesidad de pulsar "Yes" o "No". 

## Autoinstall con cloud-init

Hasta ahora, hemos instalado una versión fresca de Ubuntu tal como vienen en las ISO. ¿Y si queremos hacer una instalación personalizada? Como introducción a este tema, [cloud-init](https://cloud-init.io/) es un proyecto de Canonical, empresa responsable de Ubuntu, para hacer esas plantillas automatizadas. Es multiplataforma, por lo que funciona también con distribuciones como Fedora o Arch Linux.

```
cd /pxeboot/images
```

Accedemos, en este caso, a la imagen creada de Ubuntu 20.04:

```
cd ubuntu-20.04
```

y creamos la carpeta `nocloud` dentro de ella:

```
sudo mkdir nocloud && cd nocloud
```

Creando dos archivos nuevos:

```
sudo touch meta-data
```

meta-data se quedará así vacío, quedando el que nos interesa:

```
sudo nano user-data
```

En este archivo, copiaremos el contenido de este gist de GitHub donde podemos observar algunas personalizaciones:
- Instalación de software concreto.
- Nombre del host.
- Creación de perfiles concretos con una configuración especial, tal la creación del archivo de claves públicas o la clave.
- Sección interactiva, para añadir aquella que queramos desarrollar manualmente durante la instalación. 
- ¡y un largo etcétera! Te animo a que leas la [documentación sobre cloud-init](https://cloudinit.readthedocs.io/en/latest/topics/examples.html) al respecto.

{% gist https://gist.github.com/daniconil/111f64b8096029ff61d238469202d51e %}

El archivo, al estar escrito en formato YAML, es fácilmente legible comprendiendo cada caso. Por ejemplo, que al final de todo el proceso haga una actualización general de los repositorios y cuando arranquemos, tengamos una distribución flamante.

## Arrancando Ubuntu 22.04

Hecho todo lo anterior, arranca cualquier equipo con la opción por red y este detectará el servidor de arranque PXE presente en la red local.

¿Te ha sido difícil? Con este servidor configurado y con algunas pruebas previas, tienes un sistema de automatización para instalar imágenes en otros equipos muy interesante con el fin de no olvidar ningún detalle. ¡Ya puedes guardar la "checklist" en el cajón y anota todo en el cloud-init!