Si desea reducir el tamaño de la imagen de la ventana acoplable , debe usar las mejores prácticas estándar para crear una imagen de Docker.
Este blog habla sobre diferentes técnicas de optimización que puede implementar rápidamente para crear la imagen acoplable más pequeña y mínima. . También veremos algunas de las mejores herramientas para la optimización de imágenes de Docker. .
Docker como motor de contenedor facilita tomar un fragmento de código y ejecutarlo dentro de un contenedor. Permite a los ingenieros recopilar todas las dependencias del código y los archivos en una única ubicación que se puede ejecutar en cualquier lugar, de forma bastante rápida y sencilla.
Todo el concepto de imágenes "ejecutar en cualquier lugar" comienza con un archivo de configuración simple llamado Dockerfile. Primero, agregamos todas las instrucciones de compilación, como las dependencias del código, los comandos y los detalles de la imagen base, en Dockerfile.
Necesidad de optimización de imágenes de Docker
Aunque el proceso de creación de Docker es sencillo, muchas organizaciones cometen el error de crear imágenes de Docker infladas. sin optimizar las imágenes del contenedor.
En el desarrollo de software típico, cada servicio tendrá múltiples versiones/lanzamientos, y cada versión requiere más dependencias, comandos y configuraciones. Esto presenta un desafío en la compilación de imágenes de Docker, como ahora:el mismo código requiere más tiempo y recursos para compilarse antes de que pueda enviarse como un contenedor.
He visto casos en los que la imagen de la aplicación inicial comenzaba con 350 MB y, con el tiempo, aumentó a más de 1,5 GB.
Además, al instalar bibliotecas no deseadas, aumentamos la posibilidad de un posible riesgo de seguridad al aumentar la superficie de ataque.
Por lo tanto, los ingenieros de DevOps deben optimizar las imágenes de la ventana acoplable para asegurarse de que la imagen de la ventana acoplable no se hinche después de las compilaciones de la aplicación o versiones futuras. No solo para entornos de producción, en cada etapa del proceso de CI/CD, debe optimizar sus imágenes acoplables.
Además, con herramientas de orquestación de contenedores como Kubernetes, es mejor tener imágenes de tamaño pequeño para reducir la transferencia de imágenes y el tiempo de implementación. .
¿Cómo reducir el tamaño de la imagen de Docker?
Si tomamos una imagen de contenedor de una aplicación típica, contiene una imagen base, Dependencias/Archivos/Configuraciones , y crudo (software no deseado).
Todo se reduce a la eficiencia con la que podemos administrar estos recursos dentro de la imagen del contenedor.
Veamos diferentes métodos establecidos para optimizar las imágenes de Docker. Además, proporcionamos ejemplos prácticos para comprender la optimización de imágenes de la ventana acoplable en tiempo real.
O utiliza los ejemplos proporcionados en el artículo o prueba las técnicas de optimización en Dockerfiles existentes.
Los siguientes son los métodos mediante los cuales podemos lograr la optimización de la imagen de la ventana acoplable.
- Uso de imágenes base mínimas/sin distribución
- Construcciones de varias etapas
- Minimizar el número de capas
- Comprender el almacenamiento en caché
- Uso de Dockerignore
- Mantener los datos de la aplicación en otro lugar
Archivos de ejercicios de Docker: Todo el código de la aplicación, los Dockerfiles y las configuraciones que se usan en este artículo están alojados en este repositorio de Github. Puedes clonarlo y seguir el tutorial.
Método 1:usar imágenes base mínimas
Su primer enfoque debe ser elegir la imagen base correcta con una huella mínima del sistema operativo.
Un ejemplo de ello son las imágenes de base alpina. Las imágenes alpinas pueden ser tan pequeñas como 5,59 MB . No es solo pequeño; también es muy seguro.
alpine latest c059bfaa849c 5.59MB
La imagen base de Nginx alpine tiene solo 22 MB.
De forma predeterminada, viene con el shell sh que ayuda a depurar el contenedor al adjuntarlo.
Puede reducir aún más el tamaño de la imagen base utilizando imágenes sin distribución. Es una versión simplificada del sistema operativo. Las imágenes base sin distribución están disponibles para java, nodejs, python, Rust, etc.
Las imágenes sin distribución son tan mínimas que no ni siquiera tienen un caparazón . Entonces, podría preguntarse, entonces, ¿cómo depuramos las aplicaciones? Tienen la versión de depuración de la misma imagen que viene con el cuadro ocupado para la depuración.
Además, la mayoría de las distribuciones ahora tienen sus imágenes base mínimas.
Nota: No puede usar directamente las imágenes base disponibles públicamente en entornos de proyecto. Debe obtener la aprobación del equipo de seguridad de la empresa para usar la imagen base. En algunas organizaciones, el propio equipo de seguridad publica imágenes base todos los meses después de las pruebas y el escaneo de seguridad. Esas imágenes estarían disponibles en el repositorio privado de la ventana acoplable de la organización común.
Método 2:usar compilaciones de varias etapas de Docker
El patrón de construcción de varias etapas se desarrolla a partir del concepto de patrón de construcción en el que usamos diferentes Dockerfiles para construir y empaquetar el código de la aplicación. Aunque este patrón ayuda a reducir el tamaño de la imagen, genera poca sobrecarga cuando se trata de crear canalizaciones.
En la construcción de varias etapas, obtenemos ventajas similares a las del patrón de construcción. Usamos imágenes intermedias (etapas de compilación) para compilar código, instalar dependencias y empaquetar archivos en este enfoque. La idea detrás de esto es eliminar capas no deseadas en la imagen.
Después de eso, solo los archivos de la aplicación necesarios para ejecutar la aplicación se copian en otra imagen con solo las bibliotecas requeridas, es decir, más ligeras para ejecutar la aplicación.
Veamos esto en acción, con la ayuda de un ejemplo práctico donde creamos una aplicación Nodejs simple y optimizamos su Dockerfile.
Primero, vamos a crear el código. Tendremos la siguiente estructura de carpetas.
├── Dockerfile1
├── Dockerfile2
├── env
├── index.js
└── package.json
Guarde lo siguiente como index.js
.
const dotenv=require('dotenv');
dotenv.config({ path: './env' });
dotenv.config();
const express=require("express");
const app=express();
app.get('/',(req,res)=>{
res.send(`Learning to Optimize Docker Images with DevOpsCube!`);
});
app.listen(process.env.PORT,(err)=>{
if(err){
console.log(`Error: ${err.message}`);
}else{
console.log(`Listening on port ${process.env.PORT}`);
}
}
)
Guarde lo siguiente como package.json
.
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^10.0.0",
"express": "^4.17.2"
}
}
Guarde la siguiente variable de puerto en un archivo llamado env
.
PORT=8080
Un Dockerfile
simple para esta aplicación le gustaría esto:guárdelo como Dockerfile1
.
FROM node:16
COPY . .
RUN npm installEXPOSE 3000
CMD [ "node", "index.js" ]
Veamos el espacio de almacenamiento que requiere construyéndolo.
docker build -t devopscube/node-app:1.0 --no-cache -f Dockerfile1 .
Después de que se complete la construcción. Verifiquemos su tamaño usando –
docker image ls
Esto es lo que obtenemos.
devopscube/node-app 1.0 b15397d01cca 22 seconds ago 910MB
Entonces el tamaño es 910MBs
.
Ahora, usemos este método para crear una compilación de varias etapas.
Usaremos node:16
como imagen base, es decir, la imagen para todas las instalaciones de módulos y dependencias, después de eso, moveremos el contenido a un mínimo y más ligero 'alpine
'imagen basada. El 'alpine
La imagen tiene las utilidades mínimas y, por lo tanto, es muy ligera.
Esta es una representación pictórica de una compilación de varias etapas de Docker.
Además, en un solo Dockerfile
, puede tener varias etapas con diferentes imágenes base. Por ejemplo, puede tener diferentes etapas para compilación, prueba, análisis estático y paquete con diferentes imágenes base.
Veamos cómo se vería el nuevo Dockerfile. Solo estamos copiando los archivos necesarios de la imagen base a la imagen principal.
Guarde lo siguiente como Dockerfile2
.
FROM node:16 as build
WORKDIR /app
COPY package.json index.js env ./
RUN npm install
FROM node:alpine as main
COPY --from=build /app /
EXPOSE 8080
CMD ["index.js"]
Veamos el espacio de almacenamiento que requiere construyéndolo.
docker build -t devopscube/node-app:2.0 --no-cache -f Dockerfile2 .
Después de que se complete la construcción. Comprobemos su tamaño usando
docker image ls
Esto es lo que obtenemos.
devopscube/node-app 2.0 fa6ae75da252 32 seconds ago 171MB
Entonces, el nuevo tamaño de imagen reducido es de 171 MB en comparación con la imagen con todas las dependencias.
¡Eso es una optimización de más del 80 %!
Sin embargo, si hubiéramos usado la misma imagen base que usamos en la etapa de construcción, no veríamos mucha diferencia.
Puede reducir aún más el tamaño de la imagen usando imágenes sin distribución . Aquí está el mismo Dockerfile
con un paso de compilación de varias etapas que usa la imagen distroless de google nodeJS en lugar de alpine.
FROM node:16 as build
WORKDIR /app
COPY package.json index.js env ./
RUN npm install
FROM gcr.io/distroless/nodejs
COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]
Si crea el Dockerfile anterior, su imagen tendrá 118 MB ,
devopscube/distroless-node 1.0 302990bc5e76 118MB
Método 3:minimizar el número de capas
Las imágenes de Docker funcionan de la siguiente manera:cada RUN, COPY, FROM
Las instrucciones de Dockerfile agregan una nueva capa y cada capa aumenta el tiempo de ejecución de la compilación y aumenta los requisitos de almacenamiento de la imagen.
Veamos esto en acción, con la ayuda de un ejemplo práctico:creemos una imagen de ubuntu con bibliotecas actualizadas y actualizadas, junto con algunos paquetes necesarios instalados, como vim, net-tools, dnsutils.
Un Dockerfile
para lograr esto sería lo siguiente:Guardar esto como Dockerfile3
.
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
También deseamos analizar el tiempo de compilación de esta imagen.
El demonio Docker tiene una capacidad incorporada para mostrar el tiempo de ejecución total que está tomando un Dockerfile.
Para habilitar esta función, siga los siguientes pasos:
- Cree un
daemon.json
archivo con el siguiente contenido en/etc/docker/
{
"experimental": true
}
2. Ejecute el siguiente comando para habilitar la función.
export DOCKER_BUILDKIT=1
Construyámoslo y veamos el almacenamiento y el tiempo de construcción.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
Mostraría los tiempos de ejecución en la terminal.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
[+] Building 117.1s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
.
.
.
.
=> => writing image sha256:9601bcac010062c656dacacbc7c554b8ba552c7174f32fdcbd24ff9c7482a805 0.0s
=> => naming to docker.io/devopscube/optimize:3.0 0.0s
real 1m57.219s
user 0m1.062s
sys 0m0.911s
Una vez completada la compilación, el tiempo de ejecución llega a ser 117,1 segundos .
Comprobemos su tamaño usando
docker image ls
Esto es lo que obtenemos.
devopscube/optimize 3.0 9601bcac0100 About a minute ago 227MB
Entonces el tamaño es de 227 MB .
Combinemos los comandos EJECUTAR en una sola capa y guárdelo como Dockerfile4.
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && apt-get upgrade -y && apt-get install --no-install-recommends vim net-tools dnsutils -y
En el comando EJECUTAR anterior, hemos usado --no-install-recommends
marca para deshabilitar los paquetes recomendados. Se recomienda siempre que utilice install
en tus Dockerfiles
Veamos el almacenamiento y el tiempo de construcción necesarios para construirlo.
time docker build -t devopscube/optimize:4.0 --no-cache -f Dockerfile4 .
Mostraría los tiempos de ejecución en la terminal.
time docker build -t devopscube/optimize:0.4 --no-cache -f Dockerfile4 .
[+] Building 91.7s (6/6) FINISHED
=> [internal] load build definition from Dockerfile2 0.4s
.
.
.
=> => naming to docker.io/devopscube/optimize:4.0 0.0s
real 1m31.874s
user 0m0.884s
sys 0m0.679s
Una vez completada la compilación, el tiempo de ejecución llega a ser de 91,7 segundos.
Comprobemos su tamaño usando
docker image ls
Esto es lo que obtenemos.
devopscube/optimize 4.0 37d746b976e3 42 seconds ago 216MB
Entonces, el tamaño es de 216 MB.
Con esta técnica de optimización, el tiempo de ejecución se redujo de 117,1 s a 91,7 s y el tamaño de almacenamiento se redujo de 227 MB a 216 MB.
Método 4:comprender el almacenamiento en caché
A menudo, la misma imagen debe reconstruirse una y otra vez con ligeras modificaciones en el código.
Docker ayuda en estos casos al almacenar el caché de cada capa de una compilación, con la esperanza de que pueda ser útil en el futuro.
Debido a este concepto, se recomienda agregar las líneas que se usan para instalar dependencias y paquetes antes dentro del Dockerfile, antes de los comandos COPY.
La razón detrás de esto es que la ventana acoplable podría almacenar en caché la imagen con las dependencias requeridas, y este caché se puede usar en las siguientes compilaciones cuando se modifica el código.
Por ejemplo, echemos un vistazo a los siguientes dos Dockerfiles.
Dockerfile5
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
COPY . .
Dockerfile6
FROM ubuntu:latest
COPY . .
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
Docker podría utilizar mejor la función de caché con Dockerfile6 que con Dockerfile5 debido a la mejor ubicación del comando COPY.
Método 5:Usar Dockerignore
Como regla general, solo los archivos necesarios deben copiarse sobre la imagen de la ventana acoplable.
Docker puede ignorar los archivos presentes en el directorio de trabajo si está configurado en .dockerignore
archivo.
Esta función debe tenerse en cuenta al optimizar la imagen de la ventana acoplable.
Método 6:Mantener los datos de la aplicación en otro lugar
El almacenamiento de datos de la aplicación en la imagen aumentará innecesariamente el tamaño de las imágenes.
Se recomienda encarecidamente utilizar la función de volumen de los tiempos de ejecución del contenedor para mantener la imagen separada de los datos.
Herramientas de optimización de imágenes de Docker
Las siguientes son algunas de las herramientas de código abierto que lo ayudarán a optimizar
- Bucear :es una herramienta de exploración de imágenes que lo ayuda a descubrir capas en las imágenes de contenedores Docker y OCI. Con Dive, puede encontrar formas de optimizar sus imágenes de Docker. Consulte el repositorio de Dive Github para obtener más detalles.
- Docker Delgado: Le ayuda a optimizar la seguridad y el tamaño de sus imágenes de Docker. Consulte el repositorio de Docker Slim Github para obtener más detalles.
Seguiré agregando herramientas a esta lista.