¿Cómo evitar la reinstalación de paquetes al crear una imagen de Docker para proyectos de Python?

16 minutos de lectura

avatar de usuario
satoru

Mi Dockerfile es algo así como

FROM my/base

ADD . /srv
RUN pip install -r requirements.txt
RUN python setup.py install

ENTRYPOINT ["run_server"]

Cada vez que construyo una nueva imagen, las dependencias deben reinstalarse, lo que podría ser muy lento en mi región.

Una forma en la que pienso cache paquetes que se han instalado es anular el my/base imagen con imágenes más nuevas como esta:

docker build -t new_image_1 .
docker tag new_image_1 my/base

Entonces, la próxima vez que construya con este Dockerfile, my/base ya tiene algunos paquetes instalados.

Pero esta solución tiene dos problemas:

  1. No siempre es posible anular una imagen base
  2. La imagen base crece cada vez más a medida que se superponen imágenes más nuevas.

Entonces, ¿qué mejor solución podría usar para resolver este problema?

EDITAR:

Alguna información sobre la ventana acoplable en mi máquina:

☁  test  docker version
Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.2.1
Git commit (client): d84a070
Server version: 1.1.2
Server API version: 1.13
Go version (server): go1.2.1
Git commit (server): d84a070
☁  test  docker info
Containers: 0
Images: 56
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 56
Execution Driver: native-0.2
Kernel Version: 3.13.0-29-generic
WARNING: No swap limit support

  • ¿Elimina la imagen intermedia después de terminar de construir su imagen?

    – Regan

    14 de agosto de 2014 a las 10:52

  • Por supuesto que no, pero esto es irrelevante porque cuando reconstruyo una imagen, todavía me baso en el original. my/base

    – satoru

    15 de agosto de 2014 a las 1:38

avatar de usuario
nacyot

Intente crear un Dockerfile que se parezca a esto:

FROM my/base

WORKDIR /srv
ADD ./requirements.txt /srv/requirements.txt
RUN pip install -r requirements.txt
ADD . /srv
RUN python setup.py install

ENTRYPOINT ["run_server"]

Docker usará el caché durante la instalación de pip siempre que no realice ningún cambio en el requirements.txtindependientemente del hecho de que otros archivos de código en . se cambiaron o no. Aquí hay un ejemplo.


Aquí hay un sencillo Hello, World! programa:

$ tree
.
├── Dockerfile
├── requirements.txt
└── run.py   

0 directories, 3 file

# Dockerfile

FROM dockerfile/python
WORKDIR /srv
ADD ./requirements.txt /srv/requirements.txt
RUN pip install -r requirements.txt
ADD . /srv
CMD python /srv/run.py

# requirements.txt
pytest==2.3.4

# run.py
print("Hello, World")

La salida de la compilación docker:

Step 1 : WORKDIR /srv
---> Running in 22d725d22e10
---> 55768a00fd94
Removing intermediate container 22d725d22e10
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> 968a7c3a4483
Removing intermediate container 5f4e01f290fd
Step 3 : RUN pip install -r requirements.txt
---> Running in 08188205e92b
Downloading/unpacking pytest==2.3.4 (from -r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/pytest/setup.py) egg_info for package pytest
....
Cleaning up...
---> bf5c154b87c9
Removing intermediate container 08188205e92b
Step 4 : ADD . /srv
---> 3002a3a67e72
Removing intermediate container 83defd1851d0
Step 5 : CMD python /srv/run.py
---> Running in 11e69b887341
---> 5c0e7e3726d6
Removing intermediate container 11e69b887341
Successfully built 5c0e7e3726d6

vamos a modificar run.py:

# run.py
print("Hello, Python")

Intente construir de nuevo, a continuación se muestra el resultado:

Sending build context to Docker daemon  5.12 kB
Sending build context to Docker daemon 
Step 0 : FROM dockerfile/python
---> f86d6993fc7b
Step 1 : WORKDIR /srv
---> Using cache
---> 55768a00fd94
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> Using cache
---> 968a7c3a4483
Step 3 : RUN pip install -r requirements.txt
---> Using cache
---> bf5c154b87c9
Step 4 : ADD . /srv
---> 9cc7508034d6
Removing intermediate container 0d7cf71eb05e
Step 5 : CMD python /srv/run.py
---> Running in f25c21135010
---> 4ffab7bc66c7
Removing intermediate container f25c21135010
Successfully built 4ffab7bc66c7

Como puede ver arriba, esta vez, Docker usa caché durante la compilación. Ahora, actualicemos requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

A continuación se muestra el resultado de la compilación de la ventana acoplable:

Sending build context to Docker daemon  5.12 kB
Sending build context to Docker daemon 
Step 0 : FROM dockerfile/python
---> f86d6993fc7b
Step 1 : WORKDIR /srv
---> Using cache
---> 55768a00fd94
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> b6c19f0643b5
Removing intermediate container a4d9cb37dff0
Step 3 : RUN pip install -r requirements.txt
---> Running in 4b7a85a64c33
Downloading/unpacking pytest==2.3.4 (from -r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/pytest/setup.py) egg_info for package pytest

Downloading/unpacking ipython (from -r requirements.txt (line 2))
Downloading/unpacking py>=1.4.12 (from pytest==2.3.4->-r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/py/setup.py) egg_info for package py

Installing collected packages: pytest, ipython, py
  Running setup.py install for pytest

Installing py.test script to /usr/local/bin
Installing py.test-2.7 script to /usr/local/bin
  Running setup.py install for py

Successfully installed pytest ipython py
Cleaning up...
---> 23a1af3df8ed
Removing intermediate container 4b7a85a64c33
Step 4 : ADD . /srv
---> d8ae270eca35
Removing intermediate container 7f003ebc3179
Step 5 : CMD python /srv/run.py
---> Running in 510359cf9e12
---> e42fc9121a77
Removing intermediate container 510359cf9e12
Successfully built e42fc9121a77

Observe cómo la ventana acoplable no usó el caché durante la instalación de pip. Si no funciona, verifique la versión de su ventana acoplable.

Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.2.1
Git commit (client): d84a070
Server version: 1.1.2
Server API version: 1.13
Go version (server): go1.2.1
Git commit (server): d84a070

  • Esto no parece funcionar, porque cada vez que Docker ve un ADD instrucción, la memoria caché se invalida.

    – satoru

    15 de agosto de 2014 a las 2:27

  • No estoy seguro de por qué no funciona. Pero no hay ningún cambio en los requisitos.txt (el en ADD ./requirements.txt /srv/requirements.txt), Docker debe usar caché. Ver añadir sección en el documento Dockerfile.

    – nacyot

    15 de agosto de 2014 a las 8:26


  • Sí, usará el caché si los requisitos.txt no cambian. Pero si los requisitos.txt cambian, se descargan todos los requisitos. ¿Hay alguna forma de montar un volumen de caché de pip en el contenedor acoplable para cargar desde el caché?

    – Jitu

    30 de diciembre de 2015 a las 8:29

  • La clave de esta respuesta es que agrega requisitos.txt (ADD requirements.txt /srv antes de ejecutar pip (RUN pip install -r requirements.txt), y agregue todos los demás archivos después pipa corriente. Por lo tanto, deben estar en el siguiente orden: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; (3) ADD . /srv

    – engelen

    1 de septiembre de 2017 a las 14:49


  • Tenga en cuenta que esto no funciona cuando se usa COPY en lugar de ADD

    – veuncent

    28/09/2017 a las 21:14

avatar de usuario
andy shinn

Entiendo que esta pregunta ya tiene algunas respuestas populares. Pero hay una forma más nueva de almacenar archivos en caché para los administradores de paquetes. Creo que podría ser una buena respuesta en el futuro cuando BuildKit se vuelva más estándar.

A partir de Docker 18.09 hay soporte experimental para Kit de construcción. BuildKit agrega soporte para algunas funciones nuevas en Dockerfile, que incluyen soporte experimental para el montaje de volúmenes externos dentro RUN pasos. Esto nos permite crear cachés para cosas como $HOME/.cache/pip/.

Usaremos lo siguiente requirements.txt archivo como ejemplo:

Click==7.0
Django==2.2.3
django-appconf==1.0.3
django-compressor==2.3
django-debug-toolbar==2.0
django-filter==2.2.0
django-reversion==3.0.4
django-rq==2.1.0
pytz==2019.1
rcssmin==1.0.6
redis==3.3.4
rjsmin==1.1.0
rq==1.1.0
six==1.12.0
sqlparse==0.3.0

Un ejemplo típico Python Dockerfile podría verse como:

FROM python:3.7
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/
RUN pip install -r requirements.txt
COPY . /usr/src/app

Con BuildKit habilitado usando el DOCKER_BUILDKIT variable de entorno podemos construir el no almacenado en caché pip paso en unos 65 segundos:

$ export DOCKER_BUILDKIT=1
$ docker build -t test .
[+] Building 65.6s (10/10) FINISHED                                                                                                                                             
 => [internal] load .dockerignore                                                                                                                                          0.0s
 => => transferring context: 2B                                                                                                                                            0.0s
 => [internal] load build definition from Dockerfile                                                                                                                       0.0s
 => => transferring dockerfile: 120B                                                                                                                                       0.0s
 => [internal] load metadata for docker.io/library/python:3.7                                                                                                              0.5s
 => CACHED [1/4] FROM docker.io/library/python:3.7@sha256:6eaf19442c358afc24834a6b17a3728a45c129de7703d8583392a138ecbdb092                                                 0.0s
 => [internal] load build context                                                                                                                                          0.6s
 => => transferring context: 899.99kB                                                                                                                                      0.6s
 => CACHED [internal] helper image for file operations                                                                                                                     0.0s
 => [2/4] COPY requirements.txt /usr/src/app/                                                                                                                              0.5s
 => [3/4] RUN pip install -r requirements.txt                                                                                                                             61.3s
 => [4/4] COPY . /usr/src/app                                                                                                                                              1.3s
 => exporting to image                                                                                                                                                     1.2s
 => => exporting layers                                                                                                                                                    1.2s
 => => writing image sha256:d66a2720e81530029bf1c2cb98fb3aee0cffc2f4ea2aa2a0760a30fb718d7f83                                                                               0.0s
 => => naming to docker.io/library/test                                                                                                                                    0.0s

Ahora, agreguemos el encabezado experimental y modifiquemos el RUN paso para almacenar en caché los paquetes de Python:

# syntax=docker/dockerfile:experimental

FROM python:3.7
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
COPY . /usr/src/app

Continúe y haga otra compilación ahora. Debería tomar la misma cantidad de tiempo. Pero esta vez está almacenando en caché los paquetes de Python en nuestro nuevo montaje de caché:

$ docker build -t pythontest .
[+] Building 60.3s (14/14) FINISHED                                                                                                                                             
 => [internal] load build definition from Dockerfile                                                                                                                       0.0s
 => => transferring dockerfile: 120B                                                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                                                          0.0s
 => => transferring context: 2B                                                                                                                                            0.0s
 => resolve image config for docker.io/docker/dockerfile:experimental                                                                                                      0.5s
 => CACHED docker-image://docker.io/docker/dockerfile:experimental@sha256:9022e911101f01b2854c7a4b2c77f524b998891941da55208e71c0335e6e82c3                                 0.0s
 => [internal] load .dockerignore                                                                                                                                          0.0s
 => [internal] load build definition from Dockerfile                                                                                                                       0.0s
 => => transferring dockerfile: 120B                                                                                                                                       0.0s
 => [internal] load metadata for docker.io/library/python:3.7                                                                                                              0.5s
 => CACHED [1/4] FROM docker.io/library/python:3.7@sha256:6eaf19442c358afc24834a6b17a3728a45c129de7703d8583392a138ecbdb092                                                 0.0s
 => [internal] load build context                                                                                                                                          0.7s
 => => transferring context: 899.99kB                                                                                                                                      0.6s
 => CACHED [internal] helper image for file operations                                                                                                                     0.0s
 => [2/4] COPY requirements.txt /usr/src/app/                                                                                                                              0.6s
 => [3/4] RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt                                                                                  53.3s
 => [4/4] COPY . /usr/src/app                                                                                                                                              2.6s
 => exporting to image                                                                                                                                                     1.2s
 => => exporting layers                                                                                                                                                    1.2s
 => => writing image sha256:0b035548712c1c9e1c80d4a86169c5c1f9e94437e124ea09e90aea82f45c2afc                                                                               0.0s
 => => naming to docker.io/library/test                                                                                                                                    0.0s

Unos 60 segundos. Similar a nuestra primera compilación.

Haz un pequeño cambio en el requirements.txt (como agregar una nueva línea entre dos paquetes) para forzar una invalidación de caché y ejecutar nuevamente:

$ docker build -t pythontest .
[+] Building 15.9s (14/14) FINISHED                                                                                                                                             
 => [internal] load build definition from Dockerfile                                                                                                                       0.0s
 => => transferring dockerfile: 120B                                                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                                                          0.0s
 => => transferring context: 2B                                                                                                                                            0.0s
 => resolve image config for docker.io/docker/dockerfile:experimental                                                                                                      1.1s
 => CACHED docker-image://docker.io/docker/dockerfile:experimental@sha256:9022e911101f01b2854c7a4b2c77f524b998891941da55208e71c0335e6e82c3                                 0.0s
 => [internal] load build definition from Dockerfile                                                                                                                       0.0s
 => => transferring dockerfile: 120B                                                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                                                          0.0s
 => [internal] load metadata for docker.io/library/python:3.7                                                                                                              0.5s
 => CACHED [1/4] FROM docker.io/library/python:3.7@sha256:6eaf19442c358afc24834a6b17a3728a45c129de7703d8583392a138ecbdb092                                                 0.0s
 => CACHED [internal] helper image for file operations                                                                                                                     0.0s
 => [internal] load build context                                                                                                                                          0.7s
 => => transferring context: 899.99kB                                                                                                                                      0.7s
 => [2/4] COPY requirements.txt /usr/src/app/                                                                                                                              0.6s
 => [3/4] RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt                                                                                   8.8s
 => [4/4] COPY . /usr/src/app                                                                                                                                              2.1s
 => exporting to image                                                                                                                                                     1.1s
 => => exporting layers                                                                                                                                                    1.1s
 => => writing image sha256:fc84cd45482a70e8de48bfd6489e5421532c2dd02aaa3e1e49a290a3dfb9df7c                                                                               0.0s
 => => naming to docker.io/library/test                                                                                                                                    0.0s

¡Solo unos 16 segundos!

Estamos obteniendo esta aceleración porque ya no estamos descargando todos los paquetes de Python. Fueron almacenados en caché por el administrador de paquetes (pip en este caso) y almacenado en un montaje de volumen de caché. El montaje de volumen se proporciona al paso de ejecución para que pip puede reutilizar nuestros paquetes ya descargados. Esto sucede fuera de cualquier almacenamiento en caché de la capa de Docker.

Las ganancias deberían ser mucho mejores en los más grandes requirements.txt.

Notas:

  • Esta es una sintaxis experimental de Dockerfile y debe tratarse como tal. Es posible que no desee construir con esto en producción en este momento.
  • El material de BuildKit no funciona con Docker Compose u otras herramientas que usan directamente la API de Docker en este momento. Ahora hay soporte para esto en Docker Compose a partir de 1.25.0. Consulte ¿Cómo habilita BuildKit con docker-compose?
  • No hay ninguna interfaz directa para administrar el caché en este momento. Se purga cuando haces un docker system prune -a.

Con suerte, estas características llegarán a Docker para la construcción y BuildKit se convertirá en el predeterminado. Si / cuando eso suceda, intentaré actualizar esta respuesta.

  • Puedo confirmar que esta solución funciona muy bien. Mi compilación se redujo de más de un minuto a solo 2,2 segundos. Gracias @andy-shinn.

    – Kwuite

    9 de noviembre de 2019 a las 13:33

  • Ahora también Docker-Compose: stackoverflow.com/questions/58592259/…

    – Rexciro

    3 dic 2019 a las 21:30

  • Nota: si está utilizando SUDO para ejecutar la ventana acoplable, probablemente deba hacer: sudo DOCKER_BUILDKIT=1 …

    – Vinícius M

    12 de junio de 2020 a las 22:23

  • Recibo este error: no se pudo resolver con la interfaz dockerfile.v0: no se pudo crear la definición de LLB: línea de error de análisis de Dockerfile 10: indicador desconocido: montar

    – Mayur Dangar

    18 de junio de 2020 a las 17:10


  • Parece que te perdiste el comentario en la parte superior de la Dockerfile o la versión de Docker es demasiado antigua. Crearía una nueva pregunta con toda su información de depuración.

    –Andy Shinn

    20 de junio de 2020 a las 1:06

avatar de usuario
jakub kukul

Para minimizar la actividad de la red, puede señalar pip a un directorio de caché en su máquina host.

Ejecute su contenedor docker con el enlace del directorio de caché de pip de su host montado en el directorio de caché de pip de su contenedor. docker run El comando debería verse así:

docker run -v $HOME/.cache/pip-docker/:/root/.cache/pip image_1

Luego, en su Dockerfile, instale sus requisitos como parte de ENTRYPOINT declaración (o CMD declaración) en lugar de como una RUN dominio. Esto es importante porque (como se señaló en los comentarios) la montura no está disponible durante la creación de la imagen (cuando RUN se ejecutan las sentencias). El archivo Docker debería verse así:

FROM my/base

ADD . /srv

ENTRYPOINT ["sh", "-c", "pip install -r requirements.txt && python setup.py install && run_server"]

  • No es lo que buscaba el OP en su caso de uso, pero si está creando un servidor de compilación, esta es una gran idea

    – oden

    2 de enero de 2018 a las 6:07

  • Esto parece una receta para los problemas, particularmente la sugerencia de apuntar al caché de host predeterminado. Potencialmente, está mezclando paquetes específicos de arquitectura.

    – Giacomo Lacava

    4 de abril de 2020 a las 0:52

  • @GiacomoLacava gracias, ese es un muy buen punto. Ajusté mi respuesta y eliminé la parte que sugería usar la reutilización del directorio de caché de los hosts.

    – Jakub Kukul

    6 de abril de 2020 a las 15:38

avatar de usuario
franco milanés

pipenv install

por defecto intenta volver a bloquear. Cuando lo hace, la capa almacenada en caché de la compilación de Docker no se usa porque Pipfile.lock ha cambiado. ver los documentos

Una solución para esto es versionar Pipfile.lock y usar

RUN pipenv sync

en cambio.

Gracias a JFG Piñeiro.

avatar de usuario
jayporque13

Descubrí que una mejor manera es simplemente agregar el directorio de paquetes del sitio de Python como un volumen.

services:
    web:
        build: .
        command: python manage.py runserver 0.0.0.0:8000
        volumes:
            - .:/code
            -  /usr/local/lib/python2.7/site-packages/

De esta manera, solo puedo instalar nuevas bibliotecas sin tener que hacer una reconstrucción completa.

EDITAR: Ignora esta respuesta, de jkukul La respuesta anterior funcionó para mí. Mi intención era almacenar en caché el paquetes de sitio carpeta. Eso se habría visto algo más como:

volumes:
   - .:/code
   - ./cached-packages:/usr/local/lib/python2.7/site-packages/

Sin embargo, almacenar en caché la carpeta de descarga es mucho más limpio. Eso también almacena en caché las ruedas, por lo que logra la tarea correctamente.

  • ¿Y qué sucede cuando intenta construir este dockerfile en una máquina diferente? Esta no es una solución sostenible.

    – Aaron Mc Millin

    18/01/2017 a las 20:55

  • Genuinamente confundido, esto resultó ser un error y no estaba seguro de por qué. ¿Podrías dar algunos detalles más?

    – jay por qué13

    20/01/2017 a las 16:30

  • Su imagen acoplable depende del estado del sistema host. Esto anula la mayor parte de la utilidad de docker. Todo lo que la imagen necesita debe instalarse en él. use el Dockerfile para instalar todas las dependencias. Si desea evitar volver a descargar los paquetes cada vez que crea la respuesta de jkukul para montar el caché de pip es el camino a seguir.

    – Aaron Mc Millin

    20 de enero de 2017 a las 18:57

  • La bombilla se apagó gracias. De hecho, estaba tratando de montar el directorio de paquetes del sitio desde la máquina virtual, no desde el host. Todo un descuido. Creo que en espíritu estaba tratando de hacer lo mismo que sugirió jkulkul. ¡Gracias por la claridad!

    – jay por qué13

    22 de enero de 2017 a las 16:55


  • @AaronMcMillin En realidad, no depende de una ruta en el host. Está montando los paquetes del sitio en el contenedor en un volumen anónimo. Aunque sigue siendo una mala idea

    – ruohola

    20 de abril de 2020 a las 23:02


  • ¿Y qué sucede cuando intenta construir este dockerfile en una máquina diferente? Esta no es una solución sostenible.

    – Aaron Mc Millin

    18/01/2017 a las 20:55

  • Genuinamente confundido, esto resultó ser un error y no estaba seguro de por qué. ¿Podrías dar algunos detalles más?

    – jay por qué13

    20/01/2017 a las 16:30

  • Su imagen acoplable depende del estado del sistema host. Esto anula la mayor parte de la utilidad de docker. Todo lo que la imagen necesita debe instalarse en él. use el Dockerfile para instalar todas las dependencias. Si desea evitar volver a descargar los paquetes cada vez que crea la respuesta de jkukul para montar el caché de pip es el camino a seguir.

    – Aaron Mc Millin

    20 de enero de 2017 a las 18:57

  • La bombilla se apagó gracias. De hecho, estaba tratando de montar el directorio de paquetes del sitio desde la máquina virtual, no desde el host. Todo un descuido. Creo que en espíritu estaba tratando de hacer lo mismo que sugirió jkulkul. ¡Gracias por la claridad!

    – jay por qué13

    22 de enero de 2017 a las 16:55


  • @AaronMcMillin En realidad, no depende de una ruta en el host. Está montando los paquetes del sitio en el contenedor en un volumen anónimo. Aunque sigue siendo una mala idea

    – ruohola

    20 de abril de 2020 a las 23:02


¿Ha sido útil esta solución?