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

Resuelto satoru asked hace 10 años • 6 respuestas

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 creo una nueva imagen, es necesario reinstalar las dependencias, lo que podría ser muy lento en mi región.

Una forma en que pienso en los cachepaquetes que se han instalado es anular la my/baseimagen 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, mi/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 utilizar 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
satoru avatar Aug 14 '14 17:08 satoru
Aceptado

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 requirements.txt, independientemente de si .se cambiaron otros archivos de código o no. He aquí un ejemplo.


Aquí tienes un Hello, World!programa sencillo:

$ 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")

El resultado de la compilación de 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

Modifiquemos run.py:

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

Intente compilar nuevamente, 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 el 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 Docker:

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 Docker no usó el caché durante la instalación de pip. Si no funciona, verifique su versión de Docker.

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
nacyot avatar Aug 14 '2014 12:08 nacyot

Entiendo que esta pregunta ya tiene algunas respuestas populares. Pero existe 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 BuildKit . BuildKit agrega soporte para algunas funciones nuevas en Dockerfile, incluido el soporte experimental para montar volúmenes externos en RUNpasos. Esto nos permite crear cachés para cosas como $HOME/.cache/pip/.

Usaremos el siguiente requirements.txtarchivo 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 de Python Dockerfilepodría verse así:

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 la DOCKER_BUILDKITvariable de entorno, podemos construir el pippaso sin caché en aproximadamente 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 RUNpaso 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 llevar la misma cantidad de tiempo. Pero esta vez se están 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 construcción.

Realice un pequeño cambio 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

¡Sólo unos 16 segundos!

Obtenemos esta aceleración porque ya no descargamos todos los paquetes de Python. El administrador de paquetes los almacenó en caché ( pipen este caso) y se almacenaron en un montaje de volumen de caché. El montaje del volumen se proporciona en el paso de ejecución para que pippodamos reutilizar nuestros paquetes ya descargados. Esto sucede fuera del almacenamiento en caché de cualquier 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 quieras construir con esto en producción en este momento.
  • El material de BuildKit no funciona en 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 se habilita BuildKit con Docker-Compose?
  • Por el momento no existe ninguna interfaz directa para gestionar el caché. Se elimina cuando haces un docker system prune -a.

Con suerte, estas funciones se incluirán en Docker para la compilación y BuildKit se convertirá en el valor predeterminado. Si eso sucede, intentaré actualizar esta respuesta.

Andy Shinn avatar Jul 31 '2019 02:07 Andy Shinn

Para minimizar la actividad de la red, puede apuntar pipa un directorio de caché en su máquina host.

Ejecute su contenedor acoplable con el enlace del directorio de caché pip de su host montado en el directorio de caché pip de su contenedor. docker runEl 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 ENTRYPOINTuna declaración (o CMDdeclaración) en lugar de como un RUNcomando. Esto es importante porque (como se señaló en los comentarios) el montaje no está disponible durante la creación de la imagen (cuando RUNse ejecutan las declaraciones). 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"]
Jakub Kukul avatar Oct 29 '2016 15:10 Jakub Kukul
pipenv install

de forma predeterminada intenta volver a bloquearse. 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.

Franco Milanese avatar Jun 18 '2021 23:06 Franco Milanese