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 uno 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 anterior RUN
(es decir yum install nano && yum clean all
), pero en los casos en que cada RUN
agrega algo, hay algunos puntos que debemos considerar:
Se supone que las capas solo deben agregar un diff sobre el anterior, por lo que si la capa posterior no elimina algo agregado en una anterior, no debería haber mucha ventaja de ahorro de espacio en disco entre ambos métodos ...
Las capas se extraen en paralelo desde Docker Hub, por lo que
Dockerfile.1
, aunque probablemente sea un poco más grande, en teoría se descargarían más rápido.Si se agrega una cuarta oración (es decir
echo This is the D > d
) y se reconstruye localmente,Dockerfile.1
se construiría más rápido gracias a la memoria caché, peroDockerfile.2
tendría que ejecutar los 4 comandos nuevamente.
Entonces, la pregunta: ¿Cuál es una mejor manera de hacer un Dockerfile?
fuente
Respuestas:
Cuando es posible, siempre combino comandos que crean archivos con comandos que eliminan esos mismos archivos en un solo
RUN
línea. Esto se debe a que cadaRUN
línea agrega una capa a la imagen, la salida es, literalmente, los cambios del sistema de archivos que puede verdocker diff
en el contenedor temporal que crea. Si elimina un archivo que se creó en una capa diferente, todo lo que hace el sistema de archivos de unión es registrar el cambio del sistema de archivos en una nueva capa, el archivo todavía existe en la capa anterior y se envía a través de la red y se almacena en el disco. Entonces, si descarga el código fuente, lo extrae, lo compila en un binario y luego elimina los archivos tgz y fuente al final, realmente desea que todo esto se haga en una sola capa para reducir el tamaño de la imagen.A continuación, dividí las capas personalmente en función de su potencial de reutilización en otras imágenes y el uso esperado del 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 al mirar la reutilización de la memoria caché de imágenes. Miro los componentes que se actualizarán muy raramente, posiblemente solo cuando la imagen base se actualice y los coloque en el Dockerfile. Hacia el final del Dockerfile, incluyo todos los comandos que se ejecutarán rápidamente y pueden cambiar con frecuencia, por ejemplo, agregar un usuario con un UID específico del host o crear carpetas y cambiar los permisos. 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, consolido lo mejor que puedo para minimizar las capas. Entonces, si hay 4 carpetas de código fuente diferentes, esas se colocan dentro de una sola carpeta para que se pueda agregar con un solo comando. Cualquier instalación de paquetes desde algo como apt-get se fusionan en un solo EJECUTAR cuando sea posible para 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 construcción de varias etapas. Cuando estas etapas no se etiquetan y envían a otros nodos, puede maximizar la probabilidad de reutilización de caché dividiendo cada comando en una
RUN
línea separada .Sin embargo, esta no es una solución perfecta para aplastar capas, ya que todo lo que copia entre las etapas son los archivos y no el resto de los metadatos de la imagen, como la configuración de las variables de entorno, el punto de entrada y el comando. Y cuando instala paquetes en una distribución de Linux, las bibliotecas y otras dependencias pueden estar dispersas por todo el sistema de archivos, lo que dificulta la copia de todas las dependencias.
Debido a esto, uso compilaciones de varias etapas como reemplazo para compilar 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.fuente
Respuesta oficial enumerada en sus mejores prácticas (las imágenes oficiales DEBEN adherirse a estas)
Desde docker 1.10
COPY
, las declaracionesADD
yRUN
agregan una nueva capa a su imagen. Tenga cuidado al usar estas declaraciones. Intenta combinar comandos en una solaRUN
declaración. Separe esto solo si es necesario para facilitar la lectura.Más información: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers
Actualización: Etapa múltiple en docker> 17.05
Con las compilaciones de varias etapas, puede usar varias
FROM
declaraciones en su Dockerfile. CadaFROM
declaración es una etapa y puede tener su propia imagen base. En la etapa final, utiliza una imagen base mínima como alpine, copie los artefactos de compilación de las etapas anteriores e instale los requisitos de tiempo de ejecución. El resultado final de esta etapa es tu imagen. Entonces 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:
Puede encontrar una gran publicación de blog sobre esto aquí: https://blog.alexellis.io/mutli-stage-docker-builds/
Para responder a tus puntos:
Sí, las capas son como diferencias. No creo que se agreguen capas si no hay absolutamente ningún cambio. El problema es que una vez que instala / descarga algo en la capa # 2, no puede eliminarlo en la capa # 3. Entonces, una vez que algo se escribe en una capa, el tamaño de la imagen ya no se puede reducir al eliminar eso.
Aunque las capas se pueden tirar en paralelo, lo que lo hace potencialmente más rápido, indudablemente cada capa aumenta el tamaño de la imagen, incluso si están eliminando archivos.
Sí, el almacenamiento en caché es útil si está actualizando su archivo acoplable. Pero funciona en una dirección. Si tiene 10 capas y cambia la capa # 6, aún tendrá que reconstruir todo desde la capa # 6- # 10. Por lo tanto, no es frecuente que acelere el proceso de creación, pero se garantiza que aumentará innecesariamente el tamaño de su imagen.
Gracias a @Mohan por recordarme que actualice esta respuesta.
fuente
Parece que las respuestas anteriores están desactualizadas. La nota de los documentos:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers
y
https://docs.docker.com/engine/userguide/eng-image/multistage-build/
La mejor práctica parece haber cambiado al uso de compilaciones de varias etapas y mantener la
Dockerfile
lectura legible.fuente
docker image build --squash
opción salga de la experimental.squash
pasar el experimento. Tiene muchos trucos y solo tenía sentido antes de las compilaciones de varias etapas. Con las compilaciones de etapas múltiples solo necesita optimizar la etapa final, lo cual es muy fácil.Depende de lo que incluya en sus capas de imagen.
El punto clave es compartir tantas capas como sea posible:
Mal ejemplo:
Dockerfile.1
Dockerfile.2
Buen ejemplo:
Dockerfile.1
Dockerfile.2
Otra sugerencia es que eliminar no es tan útil solo si ocurre en la misma capa que la acción de agregar / instalar.
fuente
RUN yum install big-package
caché de?