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

128

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, les dépendances doivent être réinstallées, ce qui peut être très lent dans ma région.

Une façon dont je pense aux cachepackages qui ont été installés est de remplacer l' my/baseimage par des images plus récentes comme celle-ci:

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

Donc, la prochaine fois que je construis avec ce Dockerfile, mon / base a déjà quelques packages installés.

Mais cette solution présente deux problèmes:

  1. Il n'est pas toujours possible de remplacer une image de base
  2. L'image de base devient de plus en plus grande à mesure que de nouvelles images y sont superposées

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

ÉDITER##:

Quelques informations sur le docker 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
satoru
la source
Supprimez-vous l'image intermédiaire une fois que vous avez terminé de créer votre image?
Regan
Bien sûr que non, mais ce n'est pas pertinent car lorsque je reconstruis une image, je me base toujours sur l'originalmy/base
satoru

Réponses:

139

Essayez de créer un Dockerfile qui ressemble à ceci:

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 utilisera le cache pendant l'installation de pip tant que vous n'apportez aucune modification au requirements.txt, indépendamment du fait que d'autres fichiers de code .aient été modifiés ou non. Voici un exemple.


Voici un Hello, World!programme simple :

$ 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 sortie de la construction 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

Modifions run.py:

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

Essayez de construire à nouveau, voici 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, cette fois, le docker utilise le cache pendant la construction. Maintenant, mettons à jour requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Voici la sortie de la construction 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

Remarquez comment docker n'a pas utilisé le cache lors de l'installation de pip. Si cela ne fonctionne pas, vérifiez la version de votre 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
la source
2
Cela ne semble pas fonctionner, car chaque fois que docker voit une ADDinstruction, le cache est invalidé.
satoru
1
Je ne sais pas pourquoi cela ne fonctionne pas. Mais il n'y a aucun changement sur requirements.txt (le <src> activé ADD ./requirements.txt /srv/requirements.txt), alors docker doit utiliser le cache. Voir ajouter une seciton sur le document Dockerfile.
nacyot
16
Oui, il utilisera le cache si requirements.txt ne change pas. Mais si le fichier requirements.txt change, toutes les exigences sont téléchargées. Existe-t-il un moyen de monter un volume de cache pip dans le conteneur Docker pour charger à partir du cache?
Jitu
7
La clé de cette réponse est que vous ajoutez requirements.txt ( ADD requirements.txt /srvavant d'exécuter pip ( RUN pip install -r requirements.txt), et ajoutez tous les autres fichiers après l' exécution de pip. Ainsi, ils doivent être dans l'ordre suivant: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
engelen
2
Veuillez noter que cela ne fonctionne pas lorsque vous utilisez COPY au lieu d'ADD
veuncent le
29

Pour minimiser l'activité réseau, vous pouvez pointer pipvers un répertoire de cache sur votre machine hôte.

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

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

Ensuite, dans votre Dockerfile, installez vos exigences en tant que partie d'une ENTRYPOINTinstruction (ou CMDinstruction) au lieu d'une RUNcommande. Ceci est important, car (comme indiqué dans les commentaires) le montage n'est pas disponible pendant la construction de l'image (lorsque les RUNinstructions 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"]
Jakub Kukul
la source
4
Pas ce que l'OP recherchait dans son cas d'utilisation, mais si vous créez un serveur de build, c'est une excellente idée
oden
2
Cela semble être une recette pour les problèmes, en particulier la suggestion de pointer vers le cache hôte par défaut. Vous mélangez potentiellement des packages spécifiques à l'archive.
Giacomo Lacava le
@GiacomoLacava merci, c'est un très bon point. J'ai ajusté ma réponse et supprimé la partie qui suggérait d'utiliser la réutilisation du répertoire de cache des hôtes.
Jakub Kukul le
24

Je comprends que cette question a déjà des réponses populaires. Mais il existe une nouvelle façon de mettre en cache des fichiers pour les gestionnaires de packages. 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 Dockerfile, y compris la 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 requirements.txtfichier 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 de Python Dockerfilepourrait ressembler à:

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 DOCKER_BUILDKITvariable d'environnement, nous pouvons créer l' pipétape non mise en cache en environ 65 secondes:

$ 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

Maintenant, ajoutons l'en-tête expérimental et modifions l' RUNétape 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 de 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. Similaire à notre première version.

Apportez une petite modification au requirements.txt(comme l'ajout d'une nouvelle ligne entre deux packages) pour forcer une invalidation du cache et réexécutez:

$ 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 packages Python. Ils ont été mis en cache par le gestionnaire de packages ( pipdans ce cas) et stockés dans un montage de volume de cache. Le montage du volume est fourni à l'étape d'exécution afin de pippouvoir réutiliser nos packages déjà téléchargés. Cela se produit en dehors de la mise en cache de la couche Docker .

Les gains devraient être bien meilleurs sur les plus gros requirements.txt.

Remarques:

  • Il s'agit d'une 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.
  • Les éléments BuildKit ne fonctionnent pas sous Docker Compose ou d'autres outils qui utilisent directement l'API Docker pour le moment. Il existe désormais une prise en charge pour cela dans Docker Compose à partir de la version 1.25.0. Voir Comment activer BuildKit avec docker-compose?
  • Il n'y a pas d'interface directe pour gérer le cache pour le moment. Il est purgé lorsque vous effectuez un docker system prune -a.

Espérons que ces fonctionnalités en feront un Docker pour la construction et BuildKit deviendra la valeur par défaut. Si / quand cela se produit, j'essaierai de mettre à jour cette réponse.

Andy Shinn
la source
Je peux confirmer que cette solution fonctionne très bien. Ma construction est passée de plus d'une minute à seulement 2,2 secondes. Merci @ andy-shinn.
Kwuite
2
Maintenant aussi Docker-Compose: stackoverflow.com/questions/58592259/…
Rexcirus
Remarque: Si vous utilisez SUDO pour exécuter docker, vous devez probablement faire: sudo DOCKER_BUILDKIT = 1 ...
Vinícius M
Je reçois cette erreur: - Échec de la résolution avec le frontend dockerfile.v0: Échec de la création de la définition LLB: Ligne d'erreur d'analyse Dockerfile 10: Indicateur inconnu: montage
Mayur Dangar
Cela donne l'impression que vous avez manqué le commentaire en haut de la Dockerfileou la version de Docker est trop ancienne. Je créerais une nouvelle question avec toutes vos informations de débogage.
Andy Shinn
-10

J'ai trouvé qu'un meilleur moyen consiste simplement à ajouter le répertoire des packages de site Python en tant que volume.

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

De cette façon, je peux simplement installer de nouvelles bibliothèques sans avoir à faire une reconstruction complète.

EDIT : Ne tenez pas compte de cette réponse, la réponse de jkukul ci-dessus a fonctionné pour moi. Mon intention était de mettre en cache le dossier site-packages . Cela aurait ressemblé à quelque chose de plus comme:

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

La mise en cache du dossier de téléchargement est cependant beaucoup plus propre. Cela met également en cache les roues, de sorte qu'il accomplit correctement la tâche.

jaywhy13
la source
2
Et que se passe-t-il lorsque vous essayez de créer ce fichier docker sur une autre machine. Ce n’est pas une solution durable.
Aaron McMillin
Vraiment confus, cela s'est avéré être un buggy et je ne savais pas vraiment pourquoi. Pourriez-vous donner plus de détails.
jaywhy13
3
Votre image docker dépend de l'état du système hôte. Cela annule la plupart de l'utilité de docker. Tout ce dont l'image a besoin doit y être installé. utilisez le Dockerfile pour installer toutes les dépendances. Si vous voulez éviter de télécharger à nouveau les packages à chaque fois que vous construisez la réponse de jkukul pour monter le cache pip, c'est la voie à suivre.
Aaron McMillin
2
L'ampoule vient de s'éteindre merci. J'essayais en fait de monter le répertoire site-packages à partir de la VM, pas de l'hôte. Tout un oubli. Je pense que dans l'esprit, j'essayais de faire la même chose que jkulkul a suggéré.Merci pour la clarté!
jaywhy13
@AaronMcMillin Il ne dépend en fait pas d'un chemin sur l'hôte. Il monte les packages de site dans le conteneur sur un volume anonyme. Encore une mauvaise idée
ruohola