web-dev-qa-db-fra.com

Comment éviter de réinstaller des packages lors de la création d'une image Docker pour des projets Python?

Mon Dockerfile est quelque chose comme

FROM my/base

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

ENTRYPOINT ["run_server"]

Chaque fois que je construis une nouvelle image, des dépendances doivent être réinstallées, ce qui peut être très lent dans ma région.

Une façon dont je pense à cache paquets qui ont été installés est de remplacer le my/base image avec de nouvelles images comme celle-ci:

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

Donc, la prochaine fois que je compile avec ce fichier Dockerfile, des paquets sont déjà installés sur mon/base.

Mais cette solution a deux problèmes:

  1. Il n'est pas toujours possible de remplacer une image de base
  2. L'image de base grossit au fur et à mesure que les nouvelles images sont superposées

Alors, quelle meilleure solution pourrais-je utiliser pour résoudre ce problème?

MODIFIER##:

Quelques informations sur le menu fixe sur ma machine:

☁  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
97
satoru

Essayez de construire avec Dockerfile ci-dessous.

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"]

S'il y a des changements sur . _ (votre projet), docker skip pip install ligne en utilisant cache.

Docker ne lance que pip install sur la construction lorsque vous modifiez le fichier requirements.txt.


J'écris simple Hello, World! programme.

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

Ci-dessous est la sortie.

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

Je ne mets à jour que run.py et tente de construire à nouveau.

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

Ci-dessous est la sortie.

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

Comme vous pouvez le voir ci-dessus, docker utilise le cache de construction. Et je mets à jour les exigences.txt cette fois.

# requirements.txt

pytest==2.3.4
ipython

Ci-dessous est la sortie.

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

Et docker n'utilise pas le cache de construction. Si cela ne fonctionne pas, vérifiez votre version 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
113
nacyot

Pour minimiser l'activité du réseau, vous pouvez pointer pip vers un répertoire de cache sur votre ordinateur hôte.

Exécutez votre conteneur docker avec la liaison de répertoire de cache pip de votre hôte montée dans le répertoire de cache cache de votre conteneur. docker run La commande devrait ressembler à ceci:

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

Ensuite, dans votre fichier Docker, installez vos exigences en tant qu’élément de l’instruction ENTRYPOINT (ou de l’instruction CMD) au lieu d’une commande RUN. Ceci est important car (comme indiqué dans les commentaires), le montage n'est pas disponible pendant la création de l'image (lorsque les instructions RUN sont exécutées). Le fichier Docker devrait ressembler à ceci:

FROM my/base

ADD . /srv

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

Il est probablement préférable que le répertoire pip par défaut du système hôte soit utilisé comme cache (par exemple, $HOME/.cache/pip/ sur Linux ou $HOME/Library/Caches/pip/ sur OSX), comme je l’ai suggéré dans l’exemple docker run commande.

24
Jakub Kukul

Je comprends que cette question a déjà des réponses populaires. Mais il existe un moyen plus récent de mettre en cache les fichiers pour les gestionnaires de paquets. Je pense que cela pourrait être une bonne réponse à l'avenir lorsque BuildKit deviendra plus standard.

Depuis Docker 18.09, il existe un support expérimental pour BuildKit . BuildKit ajoute la prise en charge de certaines nouvelles fonctionnalités du fichier Dockerfile, notamment prise en charge expérimentale du montage de volumes externes en RUN étapes. Cela nous permet de créer des caches pour des choses comme $HOME/.cache/pip/.

Nous utiliserons le fichier requirements.txt Suivant comme exemple:

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 exemple typique Python Dockerfile pourrait ressembler à ceci:

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

Avec BuildKit activé à l'aide de la variable d'environnement DOCKER_BUILDKIT, Nous pouvons construire l'étape pip non mise en cache en environ 65 secondes:

$ DOCKER_BUILDKIT=1 $ time d 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

Ajoutons maintenant l'en-tête expérimental et modifions l'étape RUN pour mettre en cache les packages 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

Allez-y et faites une autre construction maintenant. Cela devrait prendre le même temps. Mais cette fois, il met en cache les packages Python dans notre nouveau montage en cache:

$ 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

Environ 60 secondes. Semblable à notre première construction.

Modifiez légèrement le paramètre requirements.txt (Par exemple, en ajoutant une nouvelle ligne entre deux packages) pour forcer l'invalidation de la mémoire cache et relancez l'exécution:

$ 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

Seulement environ 16 secondes!

Nous obtenons cette accélération car nous ne téléchargeons plus tous les Python paquets. Ils ont été mis en cache par le gestionnaire de paquets (pip dans ce cas) et stockés dans un montage de volume de cache Le montage du volume est fourni à l'étape d'exécution afin que pip puisse réutiliser nos packages déjà téléchargés. Ceci se produit en dehors de la mise en cache de la couche Docker .

Les gains devraient être bien meilleurs avec des valeurs plus grandes requirements.txt.

Remarques:

  • C'est la syntaxe expérimentale de Dockerfile et doit être traitée comme telle. Vous ne voudrez peut-être pas construire avec cela en production pour le moment.
  • Le programme BuildKit ne fonctionne pas sous Docker Compose ou d’autres outils qui utilisent directement l’API Docker pour le moment.
  • Il n'y a pas d'interface directe pour gérer le cache pour le moment. Il est purgé lorsque vous faites un docker system Prune -a.

Espérons que ces fonctionnalités seront intégrées à Docker pour la construction et que BuildKit devienne la valeur par défaut. Si/quand cela se produit, je vais essayer de mettre à jour cette réponse.

6
Andy Shinn