Come evitare di reinstallare i pacchetti durante la creazione di un'immagine Docker per progetti Python?


128

Il mio Dockerfile è qualcosa di simile

FROM my/base

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

ENTRYPOINT ["run_server"]

Ogni volta che creo una nuova immagine, le dipendenze devono essere reinstallate, il che potrebbe essere molto lento nella mia regione.

Un modo in cui penso ai cachepacchetti che sono stati installati è sovrascrivere l' my/baseimmagine con immagini più recenti come questa:

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

Quindi la prossima volta che creo con questo Dockerfile, il mio / base ha già alcuni pacchetti installati.

Ma questa soluzione ha due problemi:

  1. Non è sempre possibile sovrascrivere un'immagine di base
  2. L'immagine di base diventa sempre più grande man mano che le immagini più nuove vengono sovrapposte

Quindi quale soluzione migliore potrei usare per risolvere questo problema?

MODIFICARE##:

Alcune informazioni sulla finestra mobile sulla mia macchina:

  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

Elimini l'immagine intermedia dopo aver finito di costruire la tua immagine?
Regan

Certo che no, ma questo è irrilevante perché quando ricostruisco un'immagine, mi baso ancora sull'originalemy/base
satoru

Risposte:


139

Prova a creare un Dockerfile simile a questo:

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 utilizzerà la cache durante l'installazione di pip finché non si apportano modifiche a requirements.txt, indipendentemente dal fatto che gli altri file di codice in siano .stati modificati o meno. Ecco un esempio.


Ecco un semplice Hello, World!programma:

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

L'output di docker build:

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

Modifichiamo run.py:

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

Prova a costruire di nuovo, di seguito è riportato l'output:

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

Come puoi vedere sopra, questa volta docker usa la cache durante la compilazione. Ora, aggiorniamo requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Di seguito è riportato l'output di docker build:

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

Si noti come docker non ha utilizzato la cache durante l'installazione di pip. Se non funziona, controlla la tua versione 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

2
Questo non sembra funzionare, perché ogni volta che docker vede ADDun'istruzione, la cache viene invalidata.
satoru

1
Non sono sicuro del motivo per cui non funziona. Ma non ci sono modifiche su requirements.txt (<src> ADD ./requirements.txt /srv/requirements.txtattivo), quindi docker deve usare la cache. Vedi aggiungi seciton sul documento Dockerfile.
nacyot

16
Sì, utilizzerà la cache se requirements.txt non cambia. Ma se il file requirements.txt cambia, vengono scaricati tutti i requisiti. C'è un modo per montare un volume della cache pip nel contenitore docker per caricarlo dalla cache?
Jitu

7
La chiave di questa risposta è che aggiungi requirements.txt ( ADD requirements.txt /srvprima di eseguire pip ( RUN pip install -r requirements.txt) e aggiungi tutti gli altri file dopo aver eseguito pip. Pertanto, dovrebbero essere nel seguente ordine: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
engelen

2
Si prega di notare che questo non funziona quando si utilizza COPY invece di ADD
veuncent

29

Per ridurre al minimo l'attività di rete, è possibile puntare pipa una directory della cache sulla macchina host.

Esegui il tuo contenitore docker con il bind della directory pip cache del tuo host montato nella directory pip cache del tuo contenitore. docker runil comando dovrebbe assomigliare a questo:

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

Quindi nel tuo Dockerfile installa i tuoi requisiti come parte dell'istruzione ENTRYPOINT(o CMDistruzione) invece che come RUNcomando. Questo è importante, perché (come sottolineato nei commenti) il montaggio non è disponibile durante la creazione dell'immagine (quando RUNvengono eseguite le istruzioni). Il file Docker dovrebbe essere simile a questo:

FROM my/base

ADD . /srv

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

4
Non è quello che l'OP stava cercando nel suo caso d'uso, ma se stai creando un server di compilazione questa è un'ottima idea
oden

2
Questa sembra una ricetta per i problemi, in particolare il suggerimento di puntare alla cache host predefinita. Stai potenzialmente mescolando pacchetti specifici per l'architettura.
Giacomo Lacava

@GiacomoLacava grazie, questo è un ottimo punto. Ho modificato la mia risposta e rimosso la parte che suggeriva di utilizzare il riutilizzo della directory della cache degli host.
Jakub Kukul

24

Capisco che questa domanda abbia già alcune risposte popolari. Ma c'è un modo più nuovo per memorizzare nella cache i file per i gestori di pacchetti. Penso che potrebbe essere una buona risposta in futuro quando BuildKit diventerà più standard.

A partire da Docker 18.09 è disponibile il supporto sperimentale per BuildKit . BuildKit aggiunge il supporto per alcune nuove funzionalità nel Dockerfile, incluso il supporto sperimentale per il montaggio di volumi esterni nei RUNpassaggi. Questo ci permette di creare cache per cose come $HOME/.cache/pip/.

Useremo il seguente requirements.txtfile come esempio:

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 tipico esempio di Python Dockerfilepotrebbe assomigliare a:

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 abilitato utilizzando la DOCKER_BUILDKITvariabile di ambiente possiamo creare il pippassaggio non memorizzato nella cache in circa 65 secondi:

$ 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

Ora, aggiungiamo l'intestazione sperimentale e modifichiamo il RUNpassaggio per memorizzare nella cache i pacchetti 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

Vai avanti e fai un'altra build ora. Dovrebbe volerci la stessa quantità di tempo. Ma questa volta sta memorizzando nella cache i pacchetti Python nel nostro nuovo montaggio 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

Circa 60 secondi. Simile alla nostra prima build.

Apporta una piccola modifica a requirements.txt(come l'aggiunta di una nuova riga tra due pacchetti) per forzare l'invalidazione della cache ed eseguire di nuovo:

$ 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 circa 16 secondi!

Stiamo ottenendo questa velocità perché non stiamo più scaricando tutti i pacchetti Python. Sono stati memorizzati nella cache dal gestore dei pacchetti ( pipin questo caso) e archiviati in un montaggio del volume della cache. Il montaggio del volume viene fornito alla fase di esecuzione in modo che pippossa riutilizzare i nostri pacchetti già scaricati. Ciò accade al di fuori di qualsiasi memorizzazione nella cache del livello Docker .

I guadagni dovrebbero essere molto migliori su grandi requirements.txt.

Appunti:

  • Questa è la sintassi Dockerfile sperimentale e dovrebbe essere trattata come tale. Potresti non voler costruire con questo in produzione al momento.
  • Le cose di BuildKit non funzionano con Docker Compose o altri strumenti che utilizzano direttamente l'API Docker al momento. Ora è disponibile il supporto per questo in Docker Compose a partire dalla versione 1.25.0. Vedi Come si abilita BuildKit con docker-compose?
  • Al momento non esiste alcuna interfaccia diretta per la gestione della cache. Viene eliminato quando si esegue un file docker system prune -a.

Si spera che queste funzionalità entrino in Docker per la creazione e BuildKit diventerà l'impostazione predefinita. Se / quando ciò accadrà, cercherò di aggiornare questa risposta.


Posso confermare che questa soluzione funziona molto bene. La mia build è passata da oltre un minuto a soli 2,2 secondi. Grazie @ andy-shinn.
Kwuite


Nota: se stai usando SUDO per eseguire docker, probabilmente devi fare: sudo DOCKER_BUILDKIT = 1 ...
Vinícius M

Ricevo questo errore: - Impossibile risolvere con il frontend dockerfile.v0: impossibile creare la definizione LLB: Dockerfile parse errore riga 10: Flag sconosciuto: mount
Mayur Dangar

Sembra che tu abbia perso il commento nella parte superiore del Dockerfileo la versione Docker è troppo vecchia. Creerei una nuova domanda con tutte le tue informazioni di debug.
Andy Shinn,

-10

Ho scoperto che un modo migliore è semplicemente aggiungere la directory dei pacchetti del sito Python come volume.

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

In questo modo posso semplicemente installare nuove librerie senza dover eseguire una ricostruzione completa.

EDIT : Ignora questa risposta, la risposta di jkukul sopra ha funzionato per me. Il mio intento era di memorizzare nella cache la cartella dei pacchetti del sito . Sarebbe stato qualcosa di più simile a:

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

La memorizzazione nella cache della cartella di download è tuttavia molto più pulita. Ciò memorizza anche le ruote, quindi esegue correttamente il compito.


2
E cosa succede quando si tenta di creare questo dockerfile su una macchina diversa. Questa non è una soluzione sostenibile.
Aaron McMillin

Sinceramente confuso, questo si è rivelato bug e non ero sicuro del perché. Potresti fornire qualche dettaglio in più.
jaywhy13

3
L'immagine della finestra mobile dipende dallo stato del sistema host. Ciò annulla la maggior parte dell'utilità di docker. Tutto ciò di cui l'immagine ha bisogno dovrebbe essere installato al suo interno. utilizzare il Dockerfile per installare tutte le dipendenze. Se vuoi evitare di scaricare nuovamente i pacchetti ogni volta che crei la risposta da jkukul per montare la cache pip è la strada da percorrere.
Aaron McMillin

2
La lampadina si è appena spenta, grazie. In realtà stavo cercando di montare la directory dei pacchetti del sito dalla VM, non dall'host. Piuttosto una svista. Penso che in spirito stavo cercando di fare la stessa cosa suggerita da jkulkul. Grazie per la chiarezza!
jaywhy13

@AaronMcMillin In realtà non dipende da un percorso sull'host. Sta montando i pacchetti del sito nel contenitore su un volume anonimo. Tuttavia, è ancora una cattiva idea
ruohola il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.