Hace unos meses, escribí un artículo sobre cómo acelerar la construcción de contenedores dentro de un contenedor. Ese artículo se concentró en la velocidad de extracción de imágenes de contenedores y las diferentes formas de rellenar previamente el almacén de imágenes, utilizando montajes de volumen del host y el concepto Buildah de "almacenes adicionales".
Buildah es una herramienta de línea de comandos para crear imágenes compatibles con Open Container Initiative (es decir, también compatibles con Docker y Kubernetes) de forma rápida y sencilla. Buildah es fácil de incorporar en secuencias de comandos y canalizaciones de compilación y, lo mejor de todo, no requiere un demonio de contenedor en ejecución para compilar su imagen.
Este artículo abordará un segundo problema con la velocidad de compilación cuando se usa dnf
/yum
Comandos dentro de contenedores. Tenga en cuenta que en este artículo usaré el nombre dnf
(que es el nombre ascendente) en lugar de lo que usan algunos descendentes (yum
) Estos comentarios se aplican tanto a dnf
y yum
.
Velocidad de descarga
¿Alguna vez notaste que a veces cuando ejecutas dnf -y update
o dnf -y install
por primera vez en mucho tiempo, el comando se detiene durante mucho tiempo antes de que comience a reducir los RPM? ¿Qué está pasando?
Lo primero que dnf
lo que hace es descargar enormes archivos de caché. Estos archivos están escritos en XML y contienen todos los paquetes del repositorio remoto, incluidos muchos datos sobre el paquete. Incluso contienen todas las rutas dentro del paquete. Estos datos son necesarios para que cuando pueda ejecutar algo como dnf -y install /usr/bin/httpd
y luego dnf
averigua el paquete a instalar. Muchos paquetes contienen comandos como (requires: /usr/bin/sendmail
) que aprovechan esta característica, lo que permite dnf
para sacar un paquete apropiado para satisfacer la necesidad.
Extraer estos enormes archivos y, lo que es más importante, procesarlos, puede llevar más de un minuto. Estos datos son utilizados por libsolv
, por lo que debe convertirse a solv
formato, que es lento. La velocidad no es un gran problema cuando solo hace esto periódicamente en su host, pero cuando se trata de crear contenedores, esto es mucho más importante.
Sintaxis del archivo Docker
Aunque Buildah le permite crear imágenes de contenedores directamente en el shell, la mayoría de las personas usan Dockerfiles y Containerfiles para crear contenedores y definir recetas de imágenes reproducibles. Buildah busca por defecto un Containerfile
y un Dockerfile
ahora. Cada uno comparte la misma sintaxis, así que usaré Containerfile
para el resto de este documento.
Algo común que hacer en un Containerfile
es usar una sintaxis como:
FROM ubi8
RUN dnf -y update; dnf -y install nginx; dnf -y clean all
…
RUN dnf -y install jboss; dnf -y clean all
Examinemos el dnf
líneas. El primer dnf
línea:
(dnf -y update; dnf -y install nginx; dnf -y clean all
):
- Actualiza todos los paquetes dentro del contenedor.
- Instala el paquete seleccionado
nginx
. - Limpia todo.
Desde el ubi8
la imagen probablemente se creó hace un tiempo y su /var/cache/dnf
el directorio probablemente no existe dentro de la imagen del contenedor, dnf
debe extraer el archivo de caché XML y procesarlo. Luego, dnf
instala los paquetes reales antes de dnf -y clean all
elimina todo el exceso de datos que los comandos anteriores colocaron en la imagen, como archivos de registro y caché.
Se recomienda a los usuarios ejecutar clean all
para mantener la imagen lo más pequeña posible. Cada RUN
El comando crea una nueva capa, e incluso si elimina el contenido en un RUN
posterior comando, la capa inicial contendrá todo el contenido. Esto significa que todos los que alguna vez extraigan su imagen terminarán extrayendo los registros y los archivos de caché. Ahora, si su Containerfile
contiene uno o más dnf
comandos, pagarás el precio una y otra vez. No solo eso, sino que cada vez que reconstruyas esta imagen, pagarás ese precio una vez más. Si está en un servidor de compilación, cada imagen de contenedor que cree descargará estos archivos XML una y otra vez, desperdiciando toneladas de recursos y tiempo.
Buildah con monturas superpuestas
Vimos el problema descrito anteriormente y pensamos que podríamos manejarlo de una mejor manera. ¿No podríamos simplemente extraer los datos XML al host, procesarlos en el host y montarlos en volumen en los contenedores? Tal vez podríamos configurar un trabajo cron o un systemd
temporizador que hace un dnf makecache
una vez por cada versión de los sistemas operativos para los que creará imágenes de contenedor? Puede ejecutar este trabajo una o varias veces al día en el host y luego hacer que todo el volumen de los constructores de contenedores monte las memorias caché adecuadas en los contenedores de compilación.
Bueno, Buildah admite directorios de montaje de volumen desde el host a los contenedores. Eso debería resolver el problema, y lo hace. PERO, los contenedores a menudo quieren escribir en este directorio, si tienen que actualizar el caché, por lo que el caché debe montarse en los contenedores de lectura/escritura. Esto provoca un gran agujero de seguridad. Imagine una situación en la que una compilación de contenedor hostil escribió contenido en esta memoria caché que leyó un compilador de contenedor posterior. Posiblemente podría engañar al segundo contenedor para que instale software pirateado. Necesitamos una solución en la que el contenido se monte en el contenedor, el contenedor no pueda escribirlo, pero aún así se puede escribir desde la perspectiva del contenedor. Esto es fundamentalmente lo que es un punto de montaje superpuesto.
El sistema de archivos de superposición se monta en un lower
directorio y luego adjunta un upper
directorio al merged
punto de montaje. Cuando un proceso escribe en un nuevo archivo en merged
directorio, el nuevo archivo se escribe en el upper
directorio. Cuando un proceso modifica un archivo existente en el lower
directorio, el kernel copia el archivo desde el lower
directorio al upper
directorio y permite que el proceso modifique el archivo en el upper
directorio.
Introdujimos el concepto de la montura Overlay en Buildah. Ahora puedes ejecutar compilaciones con
buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f /tmp/Containerfile /tmp
Dnf
dentro del contenedor aún verificará si hay contenido más nuevo en los repositorios, y bajará el contenido si existe. Pero si el contenido del host está actualizado, usará rápidamente el caché del host. Le recomendaría que actualice la memoria caché de los hosts al menos una vez al día.
Una característica adicional que agregamos para la montura Buildah Overlay es destruir la upper
directorio en cada RUN
directiva. Recuerde en nuestro ejemplo, usamos múltiples RUN
comandos que ejecutaban cada uno un dnf -y clean all
. El dnf -y clean all
comando hace que el upper
directorio para mostrar todo el contenido de la parte inferior como eliminado. Si el próximo dnf
el comando compartió la parte superior anterior, vería el caché como vacío y tendría que desplegar el almacén de datos XML y procesarlo. Quitar el upper
directorio significa que cada dnf
el comando volverá a ver el lower
directorio del host y continúe compartiendo la memoria caché de los hosts.
Diferencia de velocidad
Voy a crear un Containerfile
simple que contiene dos dnf
ejecutar comandos.
FROM fedora:31
RUN dnf -y install net-utils; dnf -y clean all
RUN dnf -y install iputils; dnf -y clean all
Ejecutando esto localmente en mi caja Fedora 31
# time -f "Elapsed Time: %E" buildah bud -f Containerfile .
STEP 1: FROM fedora:31
STEP 2: RUN dnf -y install procps-ng; dnf -y clean all
Fedora Modular 31 - x86_64 2.0 MB/s | 5.2 MB 00:02
Fedora Modular 31 - x86_64 - Updates 1.6 MB/s | 4.0 MB 00:02
Fedora 31 - x86_64 - Updates 4.2 MB/s | 19 MB 00:04
Fedora 31 - x86_64 1.8 MB/s | 71 MB 00:39
Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:55:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
procps-ng x86_64 3.3.15-6.fc31 fedora 326 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 326 k
Installed size: 966 k
Downloading Packages:
procps-ng-3.3.15-6.fc31.x86_64.rpm 375 kB/s | 326 kB 00:00
--------------------------------------------------------------------------------
Total 218 kB/s | 326 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1
Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1
Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1
Installed:
procps-ng-3.3.15-6.fc31.x86_64
Complete!
33 files removed
STEP 3: RUN dnf -y install iputils; dnf -y clean all
Fedora Modular 31 - x86_64 741 kB/s | 5.2 MB 00:07
Fedora Modular 31 - x86_64 - Updates 928 kB/s | 4.0 MB 00:04
Fedora 31 - x86_64 - Updates 3.8 MB/s | 19 MB 00:05
Fedora 31 - x86_64 7.9 MB/s | 71 MB 00:08
Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:57:13 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 252 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 141 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
33 files removed
STEP 4: COMMIT
Getting image source signatures
Copying blob ac0b803c5612 skipped: already exists
Copying blob 922380d685bc done
Copying config 566e2afbb4 done
Writing manifest to image destination
Storing signatures
566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb
566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb
Elapsed Time: 2:15.00
Esta ejecución tomó 2 minutos y 15 segundos para crear una nueva imagen de contenedor con los dos nuevos paquetes.
Ahora probemos esto con una montura superpuesta del host.
# dnf -y makecache
# time -f "Elapsed Time: %E" buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f Containerfile .
STEP 1: FROM fedora:31
STEP 2: RUN dnf -y install procps-ng; dnf -y clean all
Last metadata expiration check: 0:02:34 ago on Wed Feb 5 13:51:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
procps-ng x86_64 3.3.15-6.fc31 fedora 326 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 326 k
Installed size: 966 k
Downloading Packages:
procps-ng-3.3.15-6.fc31.x86_64.rpm 496 kB/s | 326 kB 00:00
--------------------------------------------------------------------------------
Total 245 kB/s | 326 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1
Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1
Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1
Installed:
procps-ng-3.3.15-6.fc31.x86_64
Complete!
285 files removed
STEP 3: RUN dnf -y install iputils; dnf -y clean all
Last metadata expiration check: 0:02:41 ago on Wed Feb 5 13:51:54 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 556 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 222 kB/s | 141 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
285 files removed
STEP 4: COMMIT
Getting image source signatures
Copying blob ac0b803c5612 skipped: already exists
Copying blob 524bb3b83d61 done
Copying config 0f82aa6064 done
Writing manifest to image destination
Storing signatures
0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518
0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518
Elapsed Time: 0.17.44
Con el montaje Overlay, pudimos construir una nueva imagen con los dos paquetes adicionales en 17 segundos en lugar de 2 minutos y 15 segundos. Eso es casi 8 veces más rápido para construir la misma imagen de contenedor.
Ahora bien, esto muestra que si crea imágenes en un sistema operativo host que tiene el dnf
metadatos almacenados previamente en caché puede acelerar la velocidad de instalación en una cantidad ENORME. Pero, ¿qué sucede si su sistema de compilación crea imágenes para otras versiones del sistema operativo? Supongamos que desea crear imágenes para Fedora 30 y Fedora 31. Nota:esto también funcionaría en un sistema RHEL8, donde es posible que desee crear imágenes RHEL7 y tal vez incluso RHEL6.Dnf
incluye una característica genial en la que puede especificar diferentes lanzamientos al extraer contenido, usando el --releasever
opción. Dnf
también le permite especificar directorios alternativos para colocar el cachedir, --setopt=cachedir
.
En el siguiente ejemplo, desplegaré dos cachés en el host y luego usaré Buildah en modo de línea de comandos.
# dnf -y makecache --releasever=31 --setopt=cachedir=/var/cache/dnf/31
# dnf -y makecache --releasever=30 --setopt=cachedir=/var/cache/dnf/30
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/31:/var/cache/dnf:O ${ctr31} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:41 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 192 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 107 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 0:06.85
# ctr30=$(buildah from fedora:30)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:47 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20180629-4.fc30 fedora 123 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 123 k
Installed size: 351 k
Downloading Packages:
iputils-20180629-4.fc30.x86_64.rpm 370 kB/s | 123 kB 00:00
--------------------------------------------------------------------------------
Total 138 kB/s | 123 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20180629-4.fc30.x86_64 1/1
Running scriptlet: iputils-20180629-4.fc30.x86_64 1/1
Verifying : iputils-20180629-4.fc30.x86_64 1/1
Installed:
iputils-20180629-4.fc30.x86_64
Complete!
Elapsed Time: 0:08.88
Como puede ver, pudimos ejecutar contenedores Buildah usando dnf
el caché de dos versiones diferentes de Fedora del mismo host de compilación y los contenedores para Fedora 31 tardaron más de 6 segundos y la compilación de Fedora 30 tardó más de 8 segundos.
Nota:Elegí un subdirectorio de /var/cache/dnf
para los archivos de caché, para asegurarse de que las etiquetas de SELinux fueran correctas. Simplemente ejecutando dnf clean all
no limpiará /var/cache/dnf/31
. Debería ejecutar dnf clean all --setopt=cachedir=/var/cache/dnf/31
para limpiar correctamente los archivos en caché del repositorio, pero algunos artefactos permanecerán de todos modos (claves gpg, directorios vacíos).
Ahora solo para ver cuánto tiempo llevaría ejecutar la compilación en Fedora 31 sin el montaje Overlay.
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run ${ctr31} dnf -y install iputils
Fedora Modular 31 - x86_64 1.2 MB/s | 5.2 MB 00:04
Fedora Modular 31 - x86_64 - Updates 875 kB/s | 4.0 MB 00:04
Fedora 31 - x86_64 - Updates 2.4 MB/s | 19 MB 00:07
Fedora 31 - x86_64 1.7 MB/s | 71 MB 00:41
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 279 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 129 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 1:29.85
En este caso, se tardó casi 1,5 minutos en ejecutar el mismo contenedor. Buildah con monturas superpuestas funcionó 14 veces más rápido.
Contenedores sin raíz
Todos mis ejemplos hasta ahora han estado ejecutando las compilaciones usando Containerfiles
como raíz. Pero también puede hacer esto con contenedores sin raíz. En los próximos ejemplos, haré que Buildah ejecute contenedores usando buildah run
sintaxis para demostrar el uso del caché.
Ejecutando
$ buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils
funciona bien. Siempre que el usuario pueda leer el /var/cache/dnf/30
directorio, el lower
Se puede leer el directorio. Pero debe confiar en algo en el host para actualizar el caché periódicamente.
Si los usuarios quieren, pueden incluso usar dnf
para crear el caché en su directorio de inicio.
$ dnf -y makecache --releasever=30 --setopt=cachedir=$HOME/dnfcache
$ chcon --reference /var/cache/dnf -R $HOME/dnfcache
$ ctr30=$(buildah from fedora:30)
$ buildah run -v $HOME/dnfcache:/var/cache/dnf:O ${ctr30} dnf -y install iputils
Tenga en cuenta que tuve que cambiar la etiqueta SELinux de $HOME/dnfcache
directorio para que SELinux permitiera que los contenedores leyeran el lower
directorio para el montaje Overlay.
Conclusión
Acelerar la construcción de contenedores requiere una comprensión de lo que sucede cuando instala paquetes. Almacenamiento previo en caché dnf
datos en el host y el uso de montajes superpuestos para montar la memoria caché en el contenedor con Buildah puede aumentar considerablemente la velocidad de las compilaciones y reducir la cantidad de recursos necesarios para admitir una granja de compilación.
Buildah es igual a simplicidad, pero también tiene algunas funciones excelentes, como Overlay mounts
y additional stores
que puede ayudarlo a acelerar la creación de imágenes de contenedores.
[ ¿Nuevo en contenedores? Descargue Containers Primer y aprenda los conceptos básicos de los contenedores de Linux. ]