Home Ciencia y Tecnología Cómo hacer que Docker se construya más rápido con el almacenamiento en...

Cómo hacer que Docker se construya más rápido con el almacenamiento en caché de la capa

29
0

Introducción

¿Alguna vez ha mirado su terminal, esperando una construcción de Docker y se ha preguntado por qué un pequeño cambio de código desencadenó una recompilación de 10 minutos de todo su proyecto? ¿O por qué su imagen remaining es cientos de megabytes más grandes de lo que cree que debería ser? Estas no son peculiaridades de un sistema misterioso; Son los resultados predecibles de la mecánica comprensible. La diferencia entre un flujo de trabajo frustrantemente lento y un eficientemente rápido a menudo se cut back a comprender el motor de docker construct.

Este artículo es una guía para esa sala de máquinas. Desmystificaremos el proceso de construcción dominando tres pilares de eficiencia: el sistema de almacenamiento en caché, el arte del RUN comando y el papel del .dockerignore Archivo como el guardián de su construcción. Al remaining, no solo sabrás qué comandos para ejecutarse, pero por qué Funcionan, empoderándose para crear contenedores verdaderamente profesionales y optimizados. Este proyecto presenta una aplicación de IA easy que utiliza un modelo Bert para la clasificación de texto, y usaremos su DockerFile de nuestro layered_image Proyecto como un estudio de caso para ilustrar estos principios centrales.

La base: Capas Docker y el caché de compilación – el libro mayor inmutable

Think about su imagen de Docker no como un solo archivo monolítico, sino como una pila de cambios definidos con precisión, como un libro mayor inmutable donde cada transacción se registra en una nueva página. Esta es la esencia del sistema de archivos en capas de Docker. Cada instrucción en tu DockerfileFROM, COPY, RUN, CMDand so forth. generalmente crea una nueva capa. Esta capa no contiene una copia completa del sistema de archivos; en su lugar, registra solo el diferencias introducido por esa instrucción específica en comparación con la capa debajo de él. Si un RUN apt-get set up curl El comando agrega curlesa capa esencialmente cube “+ curl y sus dependencias”. Si un posterior COPY my_script.py /app/ Agrega un script, esa nueva capa cube “+ /app/my_script.py”.

Este enfoque en capas es ingenioso para la eficiencia. Cuando extrae una imagen, Docker solo descarga capas que aún no tiene. Cuando construye imágenes que comparten capas base comunes (como python:3.10-slim), esas capas base se almacenan una vez y se comparten.

Sobre la base de este sistema de archivos en capas es el Docker Construct Cache. Es la memoria de Docker de las operaciones pasadas. Cuando emite un docker construct comando, Docker pasa por su Dockerfile instrucción por instrucción. Para cada instrucción, verifica tres cosas:

  1. La instrucción exacta en sí (por ejemplo, COPY my_file.txt /dest/).
  2. El contenido de cualquier archivo involucrado en esa instrucción (por ejemplo, la suma de verificación de my_file.txt).
  3. La capa de imagen principal en la que se basa esta instrucción.

Si Docker encuentra una capa existente en su caché que se creó a partir del Exactamente la misma capa principal usando el Exactamente la misma instrucción con el Exactamente los mismos archivos de entradareutiliza esa capa almacenada en caché al instante. Este es un golpe de caché.

Sin embargo, si cualquier De estas condiciones cambian para EG si la instrucción es diferente, si el contenido de un archivo copiado ha cambiado, o si la capa principal es diferente (porque una instrucción anterior period una falla de caché), entonces Docker experimenta un busto de caché. Cuando se produce un busto de caché, Docker debe ejecutar esa instrucción desde cero, creando una nueva capa. Críticamente, Todas las instrucciones posteriores en el Dockerfile También se ejecutará desde ceroindependientemente de si podrían haber igualado el caché por su cuenta. El caché se invalida desde ese punto hacia abajo.

Esto lleva a la regla de oro del almacenamiento en caché: Las instrucciones de pedido desde menos frecuentemente cambiadas a más con mayor frecuencia. Piense en ello como organizar su escritorio: las cosas que rara vez tocas van en los cajones traseros; Las cosas que usas constantemente permanecen en la cima.

Experimento interactivo para sentir el caché:

  1. Primero, construya el capas en capas (que tiene un orden amigable para caché) usando un comando como time docker construct -t bert-classifier:layered -f layered_image/Dockerfile layered_image/. Para nosotros, esta construcción inicial tomó 23 segundos.
  2. Ahora, abierto layered_image/app/predictor.py Y hacer un cambio trivial, como agregar un comentario. Reconstruir la imagen: time docker construct -t bert-classifier:layered -f layered_image/Dockerfile layered_image/. La construcción debe completarse en menos de un segundo. ¿Por qué? Docker ve FROM, WORKDIR, COPY runtime_requirements.txt no han cambiado y reutilizan sus capas. Ve el RUN pip set up La instrucción es la misma y su entrada (runtime_requirements.txt) no ha cambiado su contenido, por lo que reutiliza la capa masiva creada por pip set up. Solo cuando llega COPY layered_image/app/ ./app/ ¿Detecta un cambio (su modificado predictor.py), por lo que reconstruye esa capa y posteriores. Si desea pruebas, continúe y agregue el —progress=plain Marcar hasta el remaining el comando de compilación. Docker CLI le mostrará las capas almacenadas en caché.
  3. A continuación, la prueba essential para comprender la invalidación de caché: edite su layered_image/Dockerfile. Mover la línea COPY layered_image/app/ ./app/ a antes el RUN pip set up ... línea. Hacer un cambio trivial más para layered_image/app/predictor.py y reconstruir. ¿Lo que sucede? ¡La construcción toma los 23 segundos completos nuevamente! El cambio a app/predictor.py Rotó el caché en el (ahora antes) COPY ./app/ paso. Porque el pip set up viene el paso después Este busto de caché, también se ve obligado a volver a correr desde cero, aunque runtime_requirements.txt no cambió.

Este experimento demuestra poderosamente cómo un busto de caché cae en cascada y por qué el orden de sus instrucciones de Dockerfile es primordial para un bucle de desarrollo rápido. Aquí está la estructura amigable con la caché que abogamos por nuestro layered_image proyecto:

# Cache-Pleasant Order (from layered_image/Dockerfile runtime stage)
FROM python:3.10-slim AS runtime
WORKDIR /app

# 1. Copy necessities first (adjustments much less usually than app code)
COPY layered_image/runtime_requirements.txt ./runtime_requirements.txt

# 2. Set up dependencies (gradual step, now cached if necessities.txt would not change)
RUN pip set up --no-cache-dir -r runtime_requirements.txt # (Full command proven later)

# 3. Copy app code final (adjustments most frequently)
COPY layered_image/app/ ./app/
COPY layered_image/sample_data/ ./sample_data/

CMD ["python", "app/predictor.py", "sample_data/sample_text.txt"]

El arte del RUN Comando: encadenamiento para capas microscópicas

La búsqueda de un eficiente Dockerfile Tiene un paralelo en el mundo físico: tratar de minimizar el volumen de una colección de artículos. Cada RUN El comando en su Dockerfile crea una nueva capa. Si descarga una herramienta, úsela y luego la elimina en separación RUN Comandos, eres como alguien poniendo un elemento en un cuadro, luego colocando un envoltorio vacío para ese elemento en otro cuadro en la parte superior. El elemento authentic todavía está allí, en la caja inferior, ocupando espacio, incluso si la caja superior cube “se ha ido”.

Específicamente, los archivos creados en una capa no pueden ser verdaderamente eliminado del tamaño normal de la imagen por un comando en una capa posterior. La capa posterior simplemente registra que esos archivos están “eliminados” o “ocultos”, pero los bits que comprenden esos archivos aún existen en las capas históricas de la imagen. Esto es como herramientas como dive a menudo informan como “espacio desperdiciado”.

Considere este antipatrón:

# Anti-Sample: Separate RUN instructions resulting in bloat
FROM python:3.10-slim
WORKDIR /app
COPY runtime_requirements.txt .
RUN pip set up --no-cache-dir -r runtime_requirements.txt  # Step 1: Set up
RUN pip cache purge                                         # Step 2: Cleanup try 1
RUN rm -rf /tmp/* /var/tmp/*                                # Step 3: Cleanup try 2
# ... (additional cleanup makes an attempt)

Si tuviera que construir una imagen y la ejecución docker historical past bert-classifier-layersobservarías la salida para cada RUN paso. La primera RUN pip set up... El paso mostraría una cantidad significativa de datos que se están escribiendo (aproximadamente 679 MB). El posterior RUN pip cache purge y RUN rm -rf /tmp/ Los pasos mostrarían muy pocos datos escritos para sus capas, tal vez solo unos pocos kilobytes. Esto es porque no Eliminar datos de la capa anterior de 679 MB; Solo están agregando capas nuevas y pequeñas en la parte superior que marcan esos archivos como se eliminan. La capa de 679 MB sigue siendo parte de la historia de la imagen.

docker historical past bert-classifier-layers                                                                
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
f09d44f97ab4   34 minutes in the past   CMD ["python" "app/predictor.py" "sample_dat…   0B        buildkit.dockerfile.v0
      34 minutes ago   COPY layered_image/sample_data/ ./sample_dat…   376B      buildkit.dockerfile.v0
      34 minutes ago   COPY layered_image/app/ ./app/ # buildkit       5.51kB    buildkit.dockerfile.v0
      34 minutes ago   RUN /bin/sh -c rm -rf /tmp/* /var/tmp/* &&  …   0B        buildkit.dockerfile.v0
      34 minutes ago   RUN /bin/sh -c pip cache purge # buildkit       6.21kB    buildkit.dockerfile.v0
      34 minutes ago   RUN /bin/sh -c pip install --no-cache-dir -r…   679MB     buildkit.dockerfile.v0
      34 minutes ago   COPY layered_image/runtime_requirements.txt …   141B      buildkit.dockerfile.v0
      3 hours ago      WORKDIR /app                                    0B        buildkit.dockerfile.v0
      11 days ago      CMD ["python3"]                                 0B        buildkit.dockerfile.v0
      11 days in the past      RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
      11 days in the past      RUN /bin/sh -c set -eux;   savedAptMark="$(a…   46.4MB    buildkit.dockerfile.v0
      11 days in the past      ENV PYTHON_SHA256=ae665bc678abd9ab6a6e1573d2…   0B        buildkit.dockerfile.v0
      11 days in the past      ENV PYTHON_VERSION=3.10.18                      0B        buildkit.dockerfile.v0
      11 days in the past      ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8…   0B        buildkit.dockerfile.v0
      11 days in the past      RUN /bin/sh -c set -eux;  apt-get replace;  a…   9.17MB    buildkit.dockerfile.v0
      11 days in the past      ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
      11 days in the past      ENV PATH=/usr/native/bin:/usr/native/sbin:/usr…   0B        buildkit.dockerfile.v0
      11 days in the past      # debian.sh --arch 'arm64' out/ 'bookworm' '…   97.2MB    debuerreotype 0.15

La solución es realizar todas las operaciones relacionadas, especialmente la creación y limpieza de archivos o herramientas temporales, dentro de un soltero RUN dominioencadenándolos con &&. Esto garantiza que solo existan artefactos temporales efímero durante la ejecución de ese sencillo RUN comando y se han ido antes de que la capa sea finalizada y comprometida.

Veamos la limpieza agresiva RUN comandar desde nuestro layered_image/Dockerfile.:

RUN pip set up --no-cache-dir -r runtime_requirements.txt && 
    pip cache purge && 
    rm -rf /tmp/* /var/tmp/* && 
    discover /usr/native/lib/python*/site-packages/ -name "*.pyc" -delete && 
    discover /usr/native/lib/python*/site-packages/ -name "__pycache__" -type d -exec rm -rf {} + || true

Este comando es un baile cuidadosamente coreografiado:

  1. pip set up --no-cache-dir -r runtime_requirements.txt: Instala paquetes de Python sin dejar archivos de rueda descargados en el caché HTTP de PIP.
  2. pip cache purge: Desafita explícitamente cualquier otro caché que PIP pueda mantener.
  3. rm -rf /tmp/* /var/tmp/: Elimina archivos de directorios temporales estándar.
  4. discover ... -name “.pyc" -delete: Elimina los archivos de código de byte Python compilados.
  5. discover ... -name “pycache" -type d -exec rm -rf {} +: Elimina el pycache directorios.
  6. || true: Asegura el RUN El comando tiene éxito incluso si discover No ubique ningún archivo (que pueda devolver un código de salida distinto de cero).

El impacto (exhibido con docker historical past)

Con este sencillo, encadenado RUN comando, la capa resultante para nuestro layered_image proyecto es 572MB. Si estos pasos estaban desencadenados, el inicial pip set up crearía una capa de aproximadamente 679 MB. El docker historical past El comando reflejaría esto:

docker historical past bert-classifier-layers                                                                
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
17d0319094f4   2 minutes in the past   CMD ["python" "app/predictor.py" "sample_dat…   0B        buildkit.dockerfile.v0
      2 minutes ago   COPY layered_image/sample_data/ ./sample_dat…   376B      buildkit.dockerfile.v0
      2 minutes ago   COPY layered_image/app/ ./app/ # buildkit       5.51kB    buildkit.dockerfile.v0
      2 minutes ago   RUN /bin/sh -c pip install --no-cache-dir -r…   572MB     buildkit.dockerfile.v0
      2 minutes ago   COPY layered_image/runtime_requirements.txt …   141B      buildkit.dockerfile.v0
      3 hours ago     WORKDIR /app                                    0B        buildkit.dockerfile.v0
      11 days ago     CMD ["python3"]                                 0B        buildkit.dockerfile.v0
      11 days in the past     RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
      11 days in the past     RUN /bin/sh -c set -eux;   savedAptMark="$(a…   46.4MB    buildkit.dockerfile.v0
      11 days in the past     ENV PYTHON_SHA256=ae665bc678abd9ab6a6e1573d2…   0B        buildkit.dockerfile.v0
      11 days in the past     ENV PYTHON_VERSION=3.10.18                      0B        buildkit.dockerfile.v0
      11 days in the past     ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8…   0B        buildkit.dockerfile.v0
      11 days in the past     RUN /bin/sh -c set -eux;  apt-get replace;  a…   9.17MB    buildkit.dockerfile.v0
      11 days in the past     ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
      11 days in the past     ENV PATH=/usr/native/bin:/usr/native/sbin:/usr…   0B        buildkit.dockerfile.v0
      11 days in the past     # debian.sh --arch 'arm64' out/ 'bookworm' '…   97.2MB    debuerreotype 0.15

Esta comparación directa en el tamaño de la capa demuestra un ahorro de 107MB simplemente estructurando la limpieza correctamente dentro de la misma RUN instrucción.

The Gatekeeper: Mastering .dockerignore

Nuestras preocupaciones finales principales: el comienzo del proceso de construcción. Cuando ejecutas docker construct .el . (o cualquier ruta que especifique) outline el “contexto de compilación”. Docker empaqueta meticulosamente todo dentro de este camino (respetando el .dockerignore Archivo, por supuesto) en un archivo y lo transmite al Docker Daemon. El demonio luego desempaquera este contexto y lo usa como la única fuente de archivos locales para cualquier COPY o ADD instrucciones en su Dockerfile. No tiene acceso a nada en su sistema de archivos fuera de este contexto.

El problema, particularmente para los proyectos de IA, es que nuestros directorios de proyectos a menudo son tesoros tesoros de archivos completamente irrelevantes para la imagen remaining de tiempo de ejecución: conjuntos de datos locales, puntos de management de modelos, cuadernos Jupyter, entornos virtuales de Python y toda .git historia. Enviar un contexto de múltiples gigabytes no es solo lento (especialmente si su demonio es remoto, como en muchos sistemas CI), también es una preocupación de seguridad y limpieza. Te arriesgas accidentalmente COPYInformación confidencial o artefactos de desarrollo en su imagen.

El .dockerignore El archivo es su vigilante Gatekeeper. Es un archivo de texto easy, colocado en la raíz de su contexto de compilación, que usa patrones (muy similares .gitignore) para especificar qué archivos y directorios deben ser excluido Desde el contexto antes de que haya empaquetado y enviado al demonio.

Un completo .dockerignore Para un proyecto de IA puede verse así:

# .dockerignore
# Python digital environments
.venv/
env/
venv/

# Python caches and compiled recordsdata
__pycache__/
*.py[cod] # .pyc, .pyo, .pyd
*.egg-info/
dist/
construct/
*.so # Compiled shared objects, except explicitly wanted and copied

# IDE and OS particular
.vscode/
.thought/
*.swp
*.swo
.DS_Store
Thumbs.db

# Notebooks and exploratory artifacts
notebooks/
*.ipynb_checkpoints

# Check-related recordsdata (if not run contained in the container construct)
assessments/
.pytest_cache/
htmlcov/
.protection

# Giant information or mannequin recordsdata not supposed for baking into the picture
information/
fashions/
model_checkpoints/
*.pt
*.onnx
*.h5

# Log recordsdata
*.log

# Dockerfile itself (often not wanted to be COPIED into the picture)
# Dockerfile

# Model management (see notice beneath)
# .git
# .gitignore

Al definir meticulosamente qué ignorar, se asegura de que el contexto de compilación sea delgado. Esto acelera el paso inicial “Enviar el contexto de compilación al Docker Daemon …”, cut back la posibilidad de inclusión de datos accidentales y hace que su COPY . . Comandos más seguros y más predecibles.

Conclusión

En cierto sentido, un Dockerfile es solo otra herramienta. Sin embargo, al profundizar en su mecánica, entendiendo cómo Transforma sus instrucciones en una imagen, obtienes un management de artesano. Hemos visto que el orden deliberado de las instrucciones para honrar el caché de construcción puede convertir minutos de espera en segundos de acción. Hemos aprendido que el ingenioso encadenamiento dentro RUN Los comandos no se trata solo de sintaxis; Se trata de esculpir capas delgadas y eficientes. Y hemos reconocido el .dockerignore No presente como un detalle menor, sino como un tutor essential de la integridad y velocidad de nuestro proceso de construcción.

Estos principios, capas, almacenamiento en caché, encadenamiento y gestión del contexto, son fundamentales. Dominarlos es clave para ir más allá de simplemente crear imágenes de Docker para realmente ingeniería para la eficiencia, la velocidad y la limpieza, especialmente en el mundo exigente de la IA.

Tu turno

Ahora que comprende estas mecánicas, vuelva a visitar sus propios Dockerfiles. ¿Puedes reordenar capas para un mejor almacenamiento en caché? Puedes cadena RUN comandos para una limpieza más agresiva? Implementar un robusto .dockerignore. ¡Comparta sus hallazgos o preguntas en los comentarios a continuación!

fuente

LEAVE A REPLY

Please enter your comment!
Please enter your name here