Todos conocemos las ventajas de utilizar binarios compilados a medida:
- Menor consumo de RAM
- Binarios mas pequeños
- Versiones mas modernas de software
- Menor superficie de ataque ya que se han eliminado todas las funcionalidades innecesarias del binario
El único inconveniente es el tiempo requerido para la compilación, pero y si dispusiésemos de un equipo con mayor potencia para realizar la compilación y utilizar estos binarios en nuestro servidor?
Precisamente Poudriere se ocupa de esta tarea, compila binarios a medida desde los ports para que sean utilizados por otros equipos.
Todas la información presente en este artículo ha sido recolectada de las siguientes fuentes:
- https://github.com/freebsd/poudriere/wiki
- https://www.freebsd.org/doc/handbook/ports-poudriere.html
- https://www.digitalocean.com/community/tutorials/how-to-set-up-a-poudriere-build-system-to-create-packages-for-your-freebsd-servers
- https://www.freebsd.org/cgi/man.cgi?query=poudriere&sektion=8&manpath=freebsd-release-ports
- https://hackmd.io/@dch/HkwIhv6x7
Para poder generar binarios mediante Poudriere tendremos que instalar Portmaster, Nginx y ccache:
Cuando construyamos paquetes con Poudriere los firmaremos con una private key, de este modo nos aseguraremos de que nadie pueda suplantar nuestro servidor de paquetes, creamos los directorios necesarios:
chmod 0600 /usr/local/etc/ssl/keys
Generamos la private key:
Generamos el certificado que será incluído en la firma de los paquetes:
Si nuestro servidor no soporta ZFS:
NO_ZFS=yes
En caso contrario indicamos el pool y el rootfs:
ZPOOL=zroot
NO_ZFS=no
ZROOTFS=/poudriere
Poudriere monta una jail donde compilará los ports, con el parámetro FREEBSD_HOST le indicamos de donde debe bajarse la imagen de la jail:
FREEBSD_HOST=ftp://ftp.freebsd.org
Indicamos donde debe generar los ficheros necesarios para su funcionamiento:
POUDRIERE_DATA=${BASEFS}/data
Indicamos si queremos utilizar RAM como sistema de ficheros, se pueden definir varios niveles de utilización, en mi caso tengo 32Gb de RAM así que lo dejo configurado en all:
USE_TMPFS=all
CHECK_CHANGED_OPTIONS indica si se deben recompilar los paquetes si las opciones de compilación han cambiado. CHECK_CHANGED_DEPS indica si se deben recompilar los paquetes si las dependencias han cambiado desde la última compilación.
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
Le indicamos a Poudriere donde está la key con la que debe firmar los paquetes:
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key
El directorio donde guardar los ficheros objeto cacheados de compilaciones previas:
CCACHE_DIR=/var/cache/ccache
Permitimos que cada job pueda utilizar todos los cores disponibles en el sistema:
ALLOW_MAKE_JOBS=yes
Finalmente indicamos la URL con la que los clientes tendrán acceso a los paquetes:
URL_BASE=http://poudriere.alfaexploit.com/
Creamos el directorio de cacheo de objetos y el de cacheo de ficheros descargados:
mkdir /usr/ports/distfiles
Mi configuración entera queda del siguiente modo:
ZPOOL=zroot
NO_ZFS=no
ZROOTFS=/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
POUDRIERE_DATA=${BASEFS}/data
USE_PORTLINT=no
USE_TMPFS=all
DISTFILES_CACHE=/usr/ports/distfiles
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key
CCACHE_DIR=/var/cache/ccache
ALLOW_MAKE_JOBS=yes
URL_BASE=http://poudriere.alfaexploit.com/
Si consideramos que Poudriere está consumiendo demasiados recursos podemos limitar el número de PARALLEL_JOBS, modificar el parámetro ALLOW_MAKE_JOBS o reconfigurar el parámetro USE_TMPFS.
Por ejemplo podríamos optar por compilar 8 ports y cada port que utilice 1 core:
PARALLEL_JOBS=8
ALLOW_MAKE_JOBS=no
O compilar 1 port y que utilice todos los cores del sistema:
PARALLEL_JOBS=1
ALLOW_MAKE_JOBS=yes
Ahora que ya tenemos Poudriere configurado debemos poner a punto nuestro entorno de compilación, como ya se ha indicado anteriormente Poudriere montará una jail para tal efecto, el único requisito es que la jail debe de ser una versión igual o inferior a la del equipo padre.
Creamos la jail indicando nombre y versión:
NOTA: No se recomienda el uso de . en el nombre de la jail ya que puede dar problemas con ciertas herramientas.
JAILNAME VERSION ARCH METHOD TIMESTAMP PATH
freebsd_12-1x64 12.1-RELEASE-p5 amd64 ftp 2020-06-05 20:24:50 /usr/local/poudriere/jails/freebsd_12-1x64
Instalamos el ports tree, mas adelante le indicaremos a la jail que lo utilice:
NOTA: Podemos tener varias jails con distintas versiones de SO y versiones de los ports.
PORTSTREE METHOD TIMESTAMP PATH
latest git+https 2020-06-05 21:05:18 /usr/local/poudriere/ports/latest
Generamos la lista de ports a compilar, la mejor manera de hacer esto es sacando la lista de paquetes instalados actualmente en los servidores que vayan a utilizar nuestro repositorio, antes de generar la lista hacemos un poco de limpieza para que la lista sea lo mas pequeña posible.
Sacamos la lista de los paquetes que instalamos explícitamente sin dependencias:
Copiamos la lista al servidor Poudriere.
Este proceso hay que hacerlo en todos los servidores y en el servidor de compilación unificar las lista.
Si fuese necesario añadimos ports adicionales a la lista:
NOTA: No hay que preocuparse por las dependencias ya que se compilarán de forma automática
Si queremos parametrizar el make.conf debemos indicarlo, en mi caso me interesa compilar todo lo relacionado con PHP con la versión7.4 y los binarios sin soporte para las X ni ipv6:
OPTIONS_UNSET+= X11 IPV6
DEFAULT_VERSIONS+= php=7.4
Le indicamos a Poudriere con que opciones debe compilar los ports, solo aparecerá el dialógo de configuración si no se ha configurada previamente en el fichero /usr/local/etc/poudriere.d/freebsd_12-1x64-latest-options/PORTNAME/options
una vez realizada la configuración ya no volverá a pedirla en posteriores compilaciones, es importante elegir el mínimo de opciones ya que así habrán menos dependencias y la compilación será más rápida.
La interfaz web de Poudriere nos muestra porque se está compilando cada port, podemos saber de quien es dependencia cada uno de ellos y así averiguar parámetros de configuración erróneos.
Si mas adelante queremos que se nos vuelva a preguntar por las opciones de compilación ejecutaremos el mismo comando pero con la opción -c:
Si solo queremos reconfigurar las opciones de uno de los ports lo mas sencillo es borrar la configuración asociada:
poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
NOTA: Si tenemos dudas sobre los parámetros correctos siempre podemos consultarlos en otro servidor donde esté instalado el binario desde los repositorios de FreeBSD, por ejemplo para php7.4: pkg info php74
Por fín podemos empezar a construir ports, pero antes nos aseguramos de tener la jail y el port tree actualizados.
poudriere ports -u -p latest
Comenzamos la compilación en una sesión de screen:
poudriere bulk -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
Obtenemos información del proceso de compilación:
load: 6.80 cmd: sh 96156 [nanslp] 669.81r 0.21u 0.84s 0% 3852k
[freebsd_12-1x64-latest] [2020-06-05_20h46m50s] [parallel_build:] Queued: 325 Built: 59 Failed: 0 Skipped: 0 Ignored: 0 Tobuild: 266 Time: 00:11:10
[01]: lang/tcl86 | tcl86-8.6.10 fetch (00:00:17 / 00:00:19)
[02]: devel/nspr | nspr-4.25 package (00:00:01 / 00:00:12)
[03]: lang/perl5.30 | perl5-5.30.3 build (00:02:45 / 00:03:26)
[04]: databases/db5 | db5-5.3.28_7 build (00:00:28 / 00:00:39)
[05]: dns/libidn2 | libidn2-2.3.0_1 fetch (00:00:02 / 00:00:04)
[06]: devel/swig30 | swig30-3.0.12_1 configure (00:00:02 / 00:00:26)
[07]: graphics/giflib | giflib-5.2.1 fetch (00:00:51 / 00:00:52)
[08]: devel/gettext-tools | gettext-tools-0.20.2 build (00:01:36 / 00:02:09)
Ahora que ya tenemos los binarios compilados tan solo queda servirlos mediante Nginx, este Nginx además servirá para consultar las estadísticas de compilación de Poudriere.
Habilitamos el servicio en el arranque:
Mediante la configuración de Nginx tendremos acceso a la interfaz de Poudriere(/), a los logs(/data) y a los paquetes(/packages).
#http context
. . .
server {
listen 80 default;
server_name poudriere.alfaexploit.com;
root /usr/local/share/poudriere/html;
location /data {
alias /usr/local/poudriere/data/logs/bulk;
autoindex on;
}
location /packages {
root /usr/local/poudriere/data;
autoindex on;
}
}
}
Para poder visualizar los logs directamente desde la interfaz web y no obligarnos a bajar el archivo tendremos que modificar el fichero mime.types de Nginx.
text/plain txt log;
Chequeamos la configuración:
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
Arrancamos Nginx:
Si tenemos algún fw configurado lo adaptamos y ya podremos acceder a la interfaz web de Poudriere.
http://poudriere.alfaexploit.com
Si queremos ver los paquetes debemos acceder a /packages:
http://poudriere.alfaexploit.com/packages
Tan solo queda la configuración del cliente, igual como no es nada recomendable mezclar paquetes binarios con ports compilados por las diferentes opciones de compilación que puedan tener, tampoco lo es mezclar paquetes de los repositorios oficiales de FreeBSD con repositorios Poudriere propios, para utilizar exclusivamente nuestro repositorio tan solo tendremos que omitir el parámetro de configuración priority y deshabilitar los oficiales.
poudriere: {
url: "http://poudriere.alfaexploit.com/packages/freebsd_12-1x64-latest/",
mirror_type: "http",
signature_type: "pubkey",
pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",
enabled: yes,
}
FreeBSD: {
enabled: no
}
Volcamos el contenido de la pubkey poudriere.cert en la configuración del cliente.
cat /usr/local/etc/ssl/certs/poudriere.cert
En el cliente:
Si el servidor poudriere es un servidor en nuestra red LAN modificamos el /etc/hosts del cliente, si se trata de un servidor público esta paso es innecesario:
192.168.69.4 poudriere.alfaexploit.com
Comprobamos manualmente que le cliente sea capaz de acceder a los paquetes:
<html>
<head><title>Index of /packages/</title></head>
<body>
<h1>Index of /packages/</h1><hr><pre><a href="../">../</a>
<a href="freebsd_12-1x64-latest/">freebsd_12-1x64-latest/</a> 05-Jun-2020 18:46 -
</pre><hr></body>
</html>
Forzamos la reinstalación de todos los paquetes pero esta vez desde nuestro repositorio, de este modo nos aseguramos de que no quede ningún paquete de los repositorios de FreeBSD:
Ya podemos instalar paquetes desde nuestro servidor de paquetes binarios ☺️
Cada vez que queramos actualizar nuestros servidores primero habrá que compilar los útlimos binarios en Poudriere, los pasos a seguir son los siguientes.
Actualizamos la jail de compilación:
Actualizamos el port tree:
Compilamos:
poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust lang/gcc10
poudriere bulk -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere pkgclean -y -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
** NOTA: ** Algunos ports como lang/rust o lang/gcc pueden dar problemas en compilaciones multi-hilo, por este motivo se compilan con un comando a parte indicando que solo utilicen un core.
El comando “poudriere pkgclean” eliminará paquetes antiguos que ya no están listados en /usr/local/etc/poudriere.d/port-list, en caso de querer regenerar todos los paquetes podemos eliminarlos con el siguiente comando:
Podemos ver el progreso de la compilación en la interfaz web:
http://poudriere.alfaexploit.com
Actualizamos en los servidores:
Para los mas perezosos dejo un script rápido:
#!/usr/local/bin/bash
poudriere jail -u -j freebsd_13-0x64
poudriere ports -u -p latest
poudriere options -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list
# Compile lang/rust devel/llvm and lang/gcc using only 1 core because these ports frecuently crash with parallel compilation:
# man poudriere-bulk
poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust lang/gcc10
poudriere bulk -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere pkgclean -y -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list
# poudriere distclean -a -p latest
# poudriere logclean -a
# poudriere pkgclean -A -j freebsd_13-0x64 -p latest
UPDATE
Si pasamos de una versión de FreeBSD a otra, quedarán jails antiguas, para eliminarlas procederemos del siguiente modo:
poudriere jail -d -j freebsd_12-1x64
poudriere distclean -a -p latest
poudriere logclean -a -j freebsd_12-1x64 -p latest
poudriere pkgclean -A -j freebsd_12-1x64 -p latest
rm -rf /usr/local/poudriere/data/packages/freebsd_12-2x64-latest/
rm -rf /usr/local/etc/poudriere.d/freebsd_12-2x64-latest-options
rm /usr/local/etc/poudriere.d/freebsd_12-2x64-make.conf
vi /usr/local/poudriere/data/logs/bulk/.data.json
TROUBLESHOOTING
Si tenemos problemas compilando podemos probar:
Crear de nuevo la jail y un nuevo ports tree.
poudriere ports -d -p latest
poudriere jail -c -j freebsd_12-1x64 -v 12.1-RELEASE
poudriere ports -c -p latest
Borrar la caché:
Añadir los paquetes a la lista manualmente, para conocer el nombre del port primero debemos realizar una búsqueda en el sistema de ports:
make fetchindex
make search name=NAME
Ahora que conocemos el nombre del port ya podemos añadirlo a la lista:
CATEGORY/NAME
También podemos eliminar ports compilados anteriormente y recompilarlos, con el tiempo que ello implica:
Si vemos que algún port da problemas como es el caso de rust.
dmesg:
pid 17034 (rustc), jid 18, uid 0, was killed: out of swap space
Logs poudriere:
RuntimeError: failed to run: /wrkdirs/usr/ports/lang/rust/work/rustc-1.53.0-src/build/bootstrap/debug/bootstrap build –jobs=8
Probaremos a compilar el port antes del resto de ports pero con un solo core: