Ejecución múltiple vs. EJECUCIÓN en cadena única en Dockerfile, ¿ qué es mejor?


Dockerfile.1 ejecuta múltiples RUN:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 se une a ellos:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

Cada RUN crea una capa, por lo que siempre asumí que menos capas es mejor y por lo tanto Dockerfile.2 es mejor.

Esto es obviamente cierto cuando un RUN elimina algo agregado por un RUN anterior (es decir, yum install nano && yum clean all), pero en los casos en que cada RUN agrega algo, hay algunos puntos que debemos considerar: {[16]]}

  1. Se supone que las capas solo deben agregar una diferencia por encima de la anterior, por lo que si capa posterior no elimina algo añadido en una anterior, no debe haber mucha ventaja de ahorro de espacio en disco entre ambos métodos...

  2. Las capas se extraen en paralelo desde Docker Hub, por lo que Dockerfile.1, aunque probablemente sea un poco más grande, teóricamente se descargarían más rápido.

  3. Si se agrega una 4ª oración (es decir, echo This is the D > d) y se reconstruye localmente, Dockerfile.1 se compilaría más rápido gracias a la caché, pero Dockerfile.2 tendría que ejecutar los 4 comandos nuevo.

Entonces, la pregunta: ¿Cuál es una mejor manera de hacer un Dockerfile?

Author: Yajo, 2016-08-30

4 answers

Cuando es posible, siempre combino comandos que crean archivos con comandos que eliminan esos mismos archivos en una sola línea RUN. Esto se debe a que cada línea RUN agrega una capa a la imagen, la salida es literalmente los cambios del sistema de archivos que podría ver con docker diff en el contenedor temporal que crea. Si elimina un archivo que se creó en una capa diferente, todo lo que el sistema de archivos union hace es registrar el cambio del sistema de archivos en una capa anterior y se envía a través de la red y se almacena en el disco. Así que si descargas el código fuente, lo extraes, lo compilas en un binario y luego eliminas los archivos tgz y fuente al final, realmente quieres que todo esto se haga en una sola capa para reducir el tamaño de la imagen.

A continuación, personalmente dividí las capas en función de su potencial de reutilización en otras imágenes y el uso esperado de almacenamiento en caché. Si tengo 4 imágenes, todas con la misma imagen base (por ejemplo, debian), puedo extraer una colección de utilidades comunes para la mayoría de esas imágenes en el primer comando de ejecución para que las otras imágenes se beneficien del almacenamiento en caché.

El orden en el Dockerfile es importante cuando se busca la reutilización de la caché de imágenes. Observo cualquier componente que se actualizará muy raramente, posiblemente solo cuando la imagen base se actualice y los coloque en el archivo Dockerfile. Hacia el final del Dockerfile, incluyo cualquier comando que se ejecute rápidamente y puede cambiar con frecuencia, por ejemplo, agregar un usuario con un UID específico del host o crear carpetas y cambiar permiso. Si el contenedor incluye código interpretado (por ejemplo, JavaScript) que se está desarrollando activamente, se agrega lo más tarde posible para que una reconstrucción solo ejecute ese cambio único.

En cada uno de estos grupos de cambios, me consolidan lo mejor que puedo para minimizar las capas. Entonces, si hay 4 carpetas de código fuente diferentes, se colocan dentro de una sola carpeta para que se pueda agregar con un solo comando. Cualquier instalación de paquetes de algo como apt-get se fusiona en una sola EJECUCIÓN cuando sea posible minimizar la cantidad de sobrecarga del administrador de paquetes (actualización y limpieza).


Actualización para compilaciones de varias etapas:

Me preocupa mucho menos reducir el tamaño de la imagen en las etapas no finales de una compilación de varias etapas. Cuando estas etapas no están etiquetadas y enviadas a otros nodos, puede maximizar la probabilidad de una reutilización de caché dividiendo cada comando en una línea RUN separada.

Sin embargo, esta no es una solución perfecta para aplastar capas ya que todos se copian entre etapas los archivos, y no el resto de los metadatos de la imagen, como la configuración de la variable de entorno, el punto de entrada y el comando. Y cuando instala paquetes en una distribución linux, las bibliotecas y otras dependencias pueden estar dispersas por todo el sistema de archivos, dificultando una copia de todas las dependencias.

Debido a esto, uso compilaciones de varias etapas como reemplazo para la construcción de binarios en un servidor CI / CD, de modo que mi servidor CI / CD solo necesita tener las herramientas para ejecutarse docker build, y no tener un jdk, nodejs, go, y cualquier otra herramienta de compilación instalada.

 24
Author: BMitch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-17 13:14:10

Respuesta oficial listada en sus mejores prácticas (las imágenes oficiales DEBEN adherirse a estas)

Minimizar el número de capas

Necesitas encontrar el equilibrio entre legibilidad (y por lo tanto mantenibilidad a largo plazo) del Dockerfile y minimizando el número de capas que utiliza. Sea estratégico y cauteloso sobre el número de capas que utiliza.

Desde docker 1.10 el COPY, ADD y las sentencias RUN añaden una nueva capa a la imagen. Tenga cuidado cuando usando estas declaraciones. Intente combinar comandos en una sola instrucción RUN. Separe esto solo si es necesario para la legibilidad.

Más información: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

Actualización: Multi etapa en docker >17.05

Con compilaciones de varias etapas, puede usar varias instrucciones FROM en su archivo Dockerfile. Cada instrucción FROM es una etapa y puede tener su propia imagen base. En la etapa final utiliza una imagen base mínima como alpine, copia los artefactos de construcción de las etapas anteriores e instala los requisitos de tiempo de ejecución. El resultado final de esta etapa es su imagen. Así que aquí es donde te preocupas por las capas como se describió anteriormente.

Como de costumbre, docker tiene excelentes documentos en compilaciones de varias etapas. Aquí hay un extracto rápido:

Con compilaciones de varias etapas, utiliza varias sentencias FROM en su Dockerfile. Cada uno DE instrucción puede utilizar una base diferente, y cada uno de ellos comienza una nueva etapa de la construcción. Puede copiar selectivamente artefactos de una etapa a otra, dejando atrás todo lo que no quiero en la imagen final.

Una gran entrada de blog sobre esto se puede encontrar aquí: https://blog.alexellis.io/mutli-stage-docker-builds /

Para responder a sus puntos:

  1. Sí, las capas son como diffs. No creo que haya capas agregadas si hay absolutamente cero cambios. El problema es que una vez que instale / descargue algo en la capa #2, no puede eliminarlo en la capa #3. Así que una vez que algo se escribe en una capa, el tamaño de la imagen ya no se puede disminuir mediante la eliminación de que.

  2. Aunque las capas se pueden extraer en paralelo, lo que lo hace potencialmente más rápido, cada capa sin duda aumenta el tamaño de la imagen, incluso si están eliminando archivos.

  3. Sí, el almacenamiento en caché es útil si está actualizando su archivo docker. Pero funciona en una dirección. Si tiene 10 capas, y cambias la capa # 6, todavía tendrás que reconstruir todo desde la capa # 6 - #10. Por lo tanto, no es muy frecuente que acelere el proceso de construcción, pero está garantizado que aumentará innecesariamente el tamaño de su imagen.


Gracias a @Mohan por recordarme que actualice esta respuesta.

 14
Author: Menzo Wijmenga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-17 06:42:21

Parece que las respuestas anteriores están desactualizadas. Nota de la documentación:

Antes de Docker 17.05, e incluso más, antes de Docker 1.10, era importante minimizar el número de capas en la imagen. El las siguientes mejoras han mitigado esta necesidad:

[...]

Docker 17.05 y superior añaden soporte para compilaciones de varias etapas, que le permiten copiar solo los artefactos que necesita en la imagen final. Esto le permite incluir herramientas e información de depuración en su etapas de construcción intermedias sin aumentar el tamaño de la final imagen.

Https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

Y

Observe que este ejemplo también comprime artificialmente dos comandos de ejecución juntos usando el operador Bash&&, para evitar la creación de un capa en la imagen. Esto es propenso al fracaso y difícil de mantener.

Https://docs.docker.com/engine/userguide/eng-image/multistage-build /

Las mejores prácticas parecen haber cambiado a usar compilaciones multietapa y mantener los Dockerfile legibles.

 8
Author: Mohan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-17 09:30:21

Depende de lo que incluyas en tus capas de imagen.

El punto clave es compartir tantas capas como sea posible:

Mal ejemplo:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Buen ejemplo:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Otra sugerencia es eliminar no es tan útil solo si ocurre en la misma capa que la acción agregar/instalar.

 2
Author: xdays,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-30 09:35:41