Técnicas de reducción de imagen de Docker

¿Alguna vez se ha preguntado por qué el tamaño de un contenedor Docker que contiene una sola aplicación puede ser de alrededor de 400 MB? ¿O tal vez le preocupaba el tamaño bastante grande de la imagen de Docker que contenía un solo archivo binario de varias decenas de MB de tamaño?



El autor del artículo, cuya traducción publicamos hoy, quiere analizar los principales factores que afectan el tamaño de los contenedores Docker. Él, además, va a compartir recomendaciones sobre la reducción del tamaño de los contenedores.

Capas de imagen de Docker


Una imagen de un contenedor Docker es esencialmente una colección de archivos apilados uno encima del otro en varias capas. A partir de estos archivos se ensambla un contenedor de trabajo. Docker utiliza el sistema de archivos UnionFS , en el que los archivos se agrupan en capas. Una capa puede contener uno o varios archivos, las capas se superponen entre sí. Durante la ejecución del contenedor, los contenidos de las capas se combinan, como resultado, el usuario final del contenedor percibe los materiales "dispuestos" en capas como un único sistema de archivos.


Vista simplificada de UnionFS

El sistema de archivos resultante se presenta al usuario final utilizando alguna implementación de UnionFS (Docker admite muchas implementaciones similares a través de controladores de almacenamiento enchufables). El tamaño total de los archivos recibidos por el usuario final es igual a la suma de los tamaños de los archivos en las capas. Cuando Docker crea un contenedor basado en la imagen, usa todas las capas de solo lectura de la imagen, agregando una capa delgada en la parte superior de estas capas que admite lectura y escritura. Es esta capa la que le permite modificar archivos en un contenedor en ejecución.


El contenedor en ejecución contiene una capa de lectura y escritura ubicada en la parte superior de las capas de solo lectura

¿Qué sucede si un archivo se elimina en la Layer 4 contenedor presentado esquemáticamente arriba? Aunque este archivo no estará disponible en el sistema de archivos que ve el usuario, de hecho, el tamaño de este archivo seguirá siendo uno de los componentes del tamaño del contenedor, ya que este archivo permanecerá en una de las capas de solo lectura.

Es bastante simple comenzar a construir la imagen con un pequeño archivo ejecutable de la aplicación y llegar a una imagen muy grande. A continuación veremos varios métodos para hacer que los contenedores sean lo más pequeños posible.

Preste atención a la ruta a la carpeta, en función de los materiales de los que se recopilan las imágenes.


¿Cuál es la forma más común de ensamblar imágenes de Docker? Aparentemente, así:

 docker build . 

El punto en este comando le dice a Docker que consideramos que el directorio de trabajo actual es la raíz del sistema de archivos utilizado en el proceso de ensamblaje de imágenes.

Para comprender mejor lo que sucede después de ejecutar el comando anterior, vale la pena recordar que construir una imagen Docker es un proceso cliente-servidor. La interfaz de línea de comandos de Docker (cliente), a la que le damos el docker build Docker, utiliza el motor de Docker (servidor) para compilar la imagen del contenedor. Para limitar el acceso al sistema de archivos base del cliente, el sistema de ensamblaje de imágenes necesita saber dónde se encuentra la raíz del sistema de archivos virtual. Es allí donde las instrucciones del archivo Dockerfile buscan recursos de archivos que eventualmente pueden terminar en la imagen que se está ensamblando.

Imagine un lugar donde generalmente se coloca un Dockerfile . Este es probablemente el directorio raíz del proyecto? Si hay un Dockerfile en la raíz del proyecto, que es utilizado por el docker build para construir la imagen, entonces resulta que todos los archivos del proyecto pueden entrar en la imagen. Esto puede llevar al hecho de que miles de archivos basura de muchos megabytes de tamaño pueden entrar en el contexto del ensamblaje de la imagen. Si usa ligeramente los comandos ADD y COPY en el Dockerfile , entonces todos los archivos del proyecto pueden formar parte de la imagen finalizada. Muy a menudo, quienes recopilan imágenes no necesitan esto, ya que la imagen final generalmente debe incluir solo algunos archivos seleccionados.

Asegúrese siempre de que el comando de docker build ruta correcta y que no haya comandos en el Dockerfile que agreguen archivos innecesarios a la imagen. Si por alguna razón necesita hacer que el proyecto arraigue el contexto de compilación, puede incluir selectivamente archivos en él y excluirlos usando .dockerignore .

Optimizar capas de imagen


El número máximo de capas que puede tener una imagen es 127 (dado el soporte para ese número de capas utilizadas por el controlador del almacén de datos). Esta limitación, si es absolutamente necesaria, se puede relajar, pero con este enfoque se reduce la gama de sistemas en los que se pueden recopilar esas imágenes. El punto es que el motor Docker debe ejecutarse en un sistema cuyo núcleo se modifica en consecuencia.

Como se mencionó en la sección anterior, debido al hecho de que UnionFS se usa al ensamblar imágenes, los archivos que caen en una determinada capa permanecen allí incluso si se eliminaron de las capas superpuestas. Vamos a resolverlo usando el Dockerfile experimental:

 FROM alpine RUN wget http://xcal1.vodafone.co.uk/10MB.zip -P /tmp RUN rm /tmp/10MB.zip 

Ensamblemos la imagen:


Montaje de una imagen experimental en la que hay espacio utilizado irracionalmente

Explore la imagen usando buceo :


El indicador de rendimiento de la imagen es del 34%.

El indicador de eficiencia de imagen del 34% indica que una cantidad considerable de espacio de imagen se usa irracionalmente. Esto lleva a un aumento en el tiempo de arranque de la imagen, a un desperdicio innecesario de recursos de red, a un tiempo de inicio más lento del contenedor.

¿Cómo deshacerse de este problema? Consideremos varias opciones.

▍ Fusionar resultados de trabajo en equipo


¿Alguna vez ha visto Dockerfile contienen directivas RUN muy largas en las que se combinan muchos comandos de shell con && ? Esta es la fusión de los resultados de los equipos.

Con este método, creamos, en base a los resultados de un solo equipo largo, solo una capa. Dado que no habrá capas en la imagen que contengan archivos eliminados en las siguientes capas, la imagen final no incluirá dichos "archivos fantasma". Considere esto como un ejemplo, llevando el Dockerfile anterior a este estado:

 FROM alpine RUN wget http://xcal1.vodafone.co.uk/10MB.zip -P /tmp && rm /tmp/10MB.zip 

Después de eso, analizamos la imagen:


La fusión de equipos le permitió crear una imagen 100% optimizada

La aplicación de esta técnica para optimizar el tamaño de las imágenes en la práctica es que después de terminar de trabajar en el archivo Dockerfile , debe analizarlo y descubrir si la combinación de comandos se puede utilizar para reducir la cantidad de espacio desperdiciado.

▍Aplicar la opción --squash


En los casos en que use Dockerfile otras personas que no desea o no puede cambiar, una alternativa a la combinación de comandos puede ser ensamblar una imagen usando la opción --squash .

Las versiones modernas de Docker (que comienzan con 1.13) le permiten reunir todas las capas en una sola, eliminando así los "recursos fantasmas". En este caso, puede usar el Dockerfile original no modificado, que contiene muchos comandos separados. Pero necesita construir la imagen usando la opción --squash :

 docker build --squash . 

La imagen resultante también resulta estar 100% optimizada:


El uso de la opción --squash durante el ensamblaje permitió crear una imagen que estaba 100% optimizada

Aquí puedes prestar atención a un detalle interesante. Es decir, en Dockerfile se creó una capa para agregar un archivo y otra capa para eliminar este archivo. La opción --squash es lo suficientemente inteligente como para comprender que en este escenario no es necesario crear capas adicionales (en la imagen final solo 9ccd9… capa de 9ccd9… partir de la imagen base que usamos). En general, para esto podemos poner --squash una ventaja adicional. Es cierto que con --squash , debe tener en cuenta que esto puede interferir con el uso de capas en caché.

Como resultado, se recomienda tener en cuenta el hecho de que al trabajar con el Dockerfile otra persona que no le gustaría cambiar, puede minimizar la cantidad de espacio de imagen utilizado de forma irracional al recopilar imágenes utilizando la opción --squash . Para analizar la imagen terminada, puede usar la herramienta de buceo .

Eliminar cachés y archivos temporales


Al contener aplicaciones, a menudo surge una situación en la que necesita colocar herramientas, bibliotecas y utilidades adicionales en la imagen con ellas. Esto se hace usando administradores de paquetes como apk , yum , apt .

Los administradores de paquetes se esfuerzan por ahorrar tiempo al usuario y no cargar su conexión de red una vez más al instalar paquetes. Por lo tanto, almacenan en caché los datos descargados. Para que el tamaño de la imagen final de Docker sea lo más pequeño posible, no necesitamos almacenar cachés del administrador de paquetes en esta imagen. Después de todo, si alguna vez necesitamos otra imagen, siempre podemos reconstruirla usando el Dockerfile actualizado.

Para eliminar los cachés creados por los tres administradores de paquetes populares mencionados anteriormente, al final de un comando agregado (es decir, uno que se ejecuta para crear una capa), puede agregar lo siguiente:

 APK: ... && rm -rf /etc/apk/cache YUM: ... && rm -rf /var/cache/yum APT: ... && rm -rf /var/cache/apt 

Como resultado, se recomienda que antes de completar el trabajo en el Dockerfile agregue Dockerfile que eliminen los cachés de los administradores de paquetes utilizados para construir la imagen. Lo mismo se aplica a los archivos temporales que no afectan el funcionamiento correcto del contenedor.

Elige tu imagen base con cuidado


Cada Dockerfile comienza con una directiva FROM . Aquí es donde establecemos la imagen básica sobre la base de la cual se creará nuestra imagen.

Esto es lo que dice la documentación de Docker al respecto: “La instrucción FROM inicializa una nueva fase de creación y configura la imagen base para las instrucciones que siguen. Como resultado, un Dockerfile correctamente compuesto debería comenzar con una instrucción FROM . Una imagen puede ser cualquier imagen viable. Es más fácil comenzar a armar su propia imagen, tomando como base una imagen de un repositorio público ".

Obviamente, hay muchas imágenes básicas, cada una de las cuales tiene sus propias características y capacidades. La selección correcta de una imagen básica que contiene exactamente lo que necesita la aplicación, ni más ni menos, tiene un gran impacto en el tamaño de la imagen final.

Como es de esperar, los tamaños de las imágenes base populares varían enormemente:


Tamaños de imágenes básicas populares de docker

Entonces, la contenedorización de la aplicación usando la imagen base de Ubuntu 19.10 conducirá al hecho de que el tamaño de la imagen, además del tamaño de la aplicación, se agregará otros 73 MB. Si recopilamos la misma imagen sobre la base de la imagen de Alpine 3.10.3 , obtendremos un "aditivo" solo en la cantidad de 6 MB. Dado que Docker almacena en caché las capas de imagen, los recursos de red se gastan en cargar una imagen solo cuando el contenedor se inicia por primera vez de la manera apropiada (en otras palabras, cuando la imagen se carga por primera vez). Pero el tamaño de la imagen en sí no se reduce a partir de esto.

Aquí puede llegar a la siguiente conclusión (completamente lógica): "Entonces, ¡siempre usaré Alpine!". Pero, desafortunadamente, en el mundo del desarrollo de software, no todo es tan simple.

¿Quizás los desarrolladores de Alpine Linux descubrieron algún ingrediente secreto que Ubuntu o Debian todavía no pueden encontrar? No El hecho es que para crear una imagen de Docker, cuyo tamaño es un orden de magnitud menor que el tamaño de la imagen de la misma Debian, los desarrolladores de Alpine tuvieron que tomar algunas decisiones sobre lo que debe incluirse en la imagen y lo que no es necesario. Antes de llamar a Alpine la imagen base que siempre usará, debe preguntar si tiene todo lo que necesita. Además, aunque Alpine tiene un administrador de paquetes, es posible que el paquete específico que se utiliza en su entorno de trabajo basado, por ejemplo, en Ubuntu, no esté disponible en Alpine. O bien, no un paquete, sino la versión deseada del paquete. Estas son las compensaciones que debe tener en cuenta antes de elegir y probar la imagen básica que mejor se adapte a su proyecto.

Y, por último, si realmente necesita una de las imágenes básicas más grandes, puede usar la herramienta para minimizar el tamaño de la imagen. Por ejemplo, una herramienta gratuita de código abierto DockerSlim . Esto reducirá el tamaño de la imagen terminada.

Al final, podemos decir que el uso de una imagen básica cuidadosamente seleccionada es extremadamente importante para crear sus propias imágenes compactas. Evalúe las necesidades de su proyecto y seleccione una imagen que contenga lo que necesita y, al mismo tiempo, tenga dimensiones aceptables para usted.

Considere crear una imagen que no tenga una imagen básica.


Si su aplicación puede ejecutarse sin un entorno adicional proporcionado de manera básica, puede decidir no usar una imagen básica. Por supuesto, dado que la instrucción FROM debe estar presente en el Dockerfile , no puede prescindir de ella. Además, debe señalar algún tipo de imagen. ¿Qué imagen usar en tal situación?

Una apariencia de rasguño podría ser útil aquí. A partir de su descripción, puede descubrir que está especialmente vacío y diseñado para crear imágenes, si habla el lenguaje Dockerfile , FROM scratch , es decir, desde cero. Esta imagen es especialmente útil cuando se crean imágenes básicas (como imágenes de debian y busybox) o imágenes extremadamente mínimas (aquellas que contienen un solo archivo binario y lo que se requiere para su funcionamiento, por ejemplo, es algo así como hello-world). Usar esta imagen como la base de la imagen descrita por el Dockerfile es similar a usar una "operación vacía" en algún programa. La aplicación de una imagen scratch no creará una capa adicional en la imagen terminada.

Como resultado, si su aplicación es un ejecutable autónomo que puede funcionar por sí solo, la elección de la imagen básica de scratch le permitirá minimizar el tamaño del contenedor.

Usa compilaciones de varias etapas


Las construcciones de etapas múltiples han sido el centro de atención desde Docker 05/17. Era una oportunidad que se había esperado durante mucho tiempo. Permite a los creadores de imágenes abandonar sus propios scripts para crear imágenes e implementar todo lo que necesitan utilizando el conocido formato Dockerfile .

En términos generales, se puede pensar que un Dockerfile varias Dockerfile combina múltiples Dockerfile , o como un Dockerfile , que tiene varias instrucciones FROM .

Antes de la aparición de ensamblajes de varias etapas, si tuviera que crear un ensamblaje de su proyecto y distribuirlo en un contenedor utilizando el Dockerfile , entonces probablemente necesitaría realizar el proceso de ensamblaje, lo que conduciría a la aparición de un contenedor, como el que se muestra a continuación:


Cree y distribuya una aplicación sin utilizar la tecnología de creación de etapas múltiples

Aunque, desde un punto de vista técnico, todo se hizo correctamente, la imagen final y el contenedor resultante están llenos de capas creadas en el proceso de preparación de los materiales del proyecto. Y estas capas no son necesarias para formar el tiempo de ejecución del proyecto.

Los ensambles de varias etapas le permiten separar las fases de creación y preparación de los materiales del proyecto del entorno en el que se ejecuta el código del proyecto.


Ensamblaje en varias etapas, separación del proceso de creación y preparación de materiales del proyecto del entorno de ejecución.

Al mismo tiempo, un solo Dockerfile suficiente para describir el proceso completo de construcción del proyecto. Pero ahora puede copiar material de una etapa a otra y deshacerse de datos innecesarios.

Los ensamblajes de varias etapas le permiten crear ensamblajes multiplataforma que se pueden usar repetidamente sin usar sus propios scripts de ensamblaje escritos para un sistema operativo específico. El tamaño final de la imagen se puede minimizar debido a la posibilidad de inclusión selectiva de los materiales generados en las etapas anteriores del proceso de ensamblaje de la imagen.

Resumen


Crear imágenes de contenedor Docker es un proceso que los programadores modernos a menudo tienen que enfrentar. Existen muchos recursos para crear Dockerfile , y puede encontrar muchos ejemplos de dichos archivos en Internet. Pero no importa lo que use, al crear su propio Dockerfile siempre vale la pena considerar el tamaño de las imágenes resultantes.

Aquí observamos varias técnicas para minimizar el tamaño de las imágenes de Docker. Atención al contenido del Dockerfile , incluido solo lo que realmente necesita, elegir la imagen base correcta, usar la tecnología de construcción de etapas múltiples; todo esto puede ayudar a reducir seriamente el tamaño de las imágenes de Docker que cree.

PD: Lanzamos el mercado en el sitio web de RUVDS. En el mercado, la imagen de Docker se instala con un solo clic, puede verificar cómo funcionan los contenedores en VPS , 3 días para las pruebas se proporcionan de forma gratuita para todos los nuevos clientes.

Estimados lectores! ¿Cómo optimizas el tamaño de tus imágenes de Docker?

Source: https://habr.com/ru/post/485650/


All Articles