Esta pagina se ve mejor con JavaScript habilitado

Poudriere: Servidor de paquetes binarios en FreeBSD

 ·  🎃 kr0m

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:


Para poder generar binarios mediante Poudriere tendremos que instalar Portmaster, Nginx y ccache:

pkg install poudriere portmaster nginx 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:

mkdir -p /usr/local/etc/ssl/{keys,certs}
chmod 0600 /usr/local/etc/ssl/keys

Generamos la private key:

openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096

Generamos el certificado que será incluído en la firma de los paquetes:

openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.cert

Si nuestro servidor no soporta ZFS:

vi /usr/local/etc/poudriere.conf

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 -p /var/cache/ccache
mkdir /usr/ports/distfiles

Mi configuración entera queda del siguiente modo:

egrep -v '^($|[[:space:]]*#|;)' /usr/local/etc/poudriere.conf

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:

poudriere jail -c -j freebsd_12-1x64 -v 12.1-RELEASE

NOTA: No se recomienda el uso de . en el nombre de la jail ya que puede dar problemas con ciertas herramientas.

poudriere jail -l

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:

poudriere ports -c -p latest -m git+https -B main

NOTA: Podemos tener varias jails con distintas versiones de SO y versiones de los ports.

poudriere ports -l

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.

pkg autoremove

Sacamos la lista de los paquetes que instalamos explícitamente sin dependencias:

pkg prime-origins > /root/port-list

Copiamos la lista al servidor Poudriere.

scp SERVER:/root/port-list /usr/local/etc/poudriere.d/port-list

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:

vi /usr/local/etc/poudriere.d/port-list

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:

vi /usr/local/etc/poudriere.d/freebsd_12-1x64-make.conf

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.

poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

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:

poudriere options -c -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

Si solo queremos reconfigurar las opciones de uno de los ports lo mas sencillo es borrar la configuración asociada:

rm /usr/local/etc/poudriere.d/freebsd_12-1x64-latest-options/www_nginx/options
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 jail -u -j freebsd_12-1x64
poudriere ports -u -p latest

Comenzamos la compilación en una sesión de screen:

screen -S PoudriereCompilation
poudriere bulk -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

Obtenemos información del proceso de compilación:

CTRL-t

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:

sysrc nginx_enable="yes"

Mediante la configuración de Nginx tendremos acceso a la interfaz de Poudriere(/), a los logs(/data) y a los paquetes(/packages).

vi /usr/local/etc/nginx/nginx.conf

#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.

vi /usr/local/etc/nginx/mime.types

text/plain                          txt log;

Chequeamos la configuración:

service nginx configtest

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:

service nginx start

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.

mkdir -p /usr/local/etc/pkg/repos

vi /usr/local/etc/pkg/repos/poudriere.conf

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,  
}
vi /usr/local/etc/pkg/repos/freebsd.conf
FreeBSD: {  
    enabled: no  
}

Volcamos el contenido de la pubkey poudriere.cert en la configuración del cliente.

ssh SERVIDOR_POUDRIERE
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:

vi /etc/hosts

192.168.69.4		poudriere.alfaexploit.com

mkdir -p /usr/local/etc/ssl/{keys,certs}

vi /usr/local/etc/ssl/certs/poudriere.cert

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:

pkg upgrade -f

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:

poudriere jail -u -j freebsd_12-1x64

Actualizamos el port tree:

poudriere ports -u -p latest

Compilamos:

screen -S PoudriereCompilation
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:

poudriere pkgclean -A -j freebsd_12-1x64 -p latest

Podemos ver el progreso de la compilación en la interfaz web:
http://poudriere.alfaexploit.com

Actualizamos en los servidores:

pkg upgrade

Para los mas perezosos dejo un script rápido:

vi updatePoudriere.sh

#!/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
chmod 700 updatePoudriere.sh

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 jail -d -j freebsd_12-1x64
poudriere ports -d -p latest
poudriere jail -c -j freebsd_12-1x64 -v 12.1-RELEASE
poudriere ports -c -p latest

Borrar la caché:

rm -rf /var/cache/ccache/*

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:

cd /usr/ports
make fetchindex
make search name=NAME

Ahora que conocemos el nombre del port ya podemos añadirlo a la lista:

vi /usr/local/etc/poudriere.d/port-list

CATEGORY/NAME

También podemos eliminar ports compilados anteriormente y recompilarlos, con el tiempo que ello implica:

poudriere bulk -c -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

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:

poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust

Si te ha gustado el artículo puedes invitarme a un RedBull aquí