Wie vermeide ich die Neuinstallation von Paketen beim Erstellen eines Docker-Images für Python-Projekte?

128

Mein Dockerfile ist so etwas wie

FROM my/base

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

ENTRYPOINT ["run_server"]

Jedes Mal, wenn ich ein neues Image erstelle, müssen Abhängigkeiten neu installiert werden, was in meiner Region sehr langsam sein kann.

Eine Möglichkeit cachefür installierte Pakete besteht darin, das my/baseImage mit neueren Images wie diesem zu überschreiben :

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

Wenn ich das nächste Mal mit dieser Docker-Datei baue, sind auf meiner / base bereits einige Pakete installiert.

Diese Lösung hat jedoch zwei Probleme:

  1. Es ist nicht immer möglich, ein Basisbild zu überschreiben
  2. Das Basisbild wird immer größer, wenn neuere Bilder darauf geschichtet werden

Welche bessere Lösung könnte ich zur Lösung dieses Problems verwenden?

BEARBEITEN##:

Einige Informationen zum Docker auf meinem Computer:

  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
quelle
Löschen Sie ein Zwischenbild, nachdem Sie das Bild fertig erstellt haben?
Regan
Natürlich nicht, aber das ist irrelevant, denn wenn ich ein Bild neu my/base
erstelle, stütze

Antworten:

139

Versuchen Sie, eine Docker-Datei zu erstellen, die ungefähr so ​​aussieht:

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 verwendet während der Pip-Installation den Cache, solange Sie keine Änderungen an der vornehmen requirements.txt, unabhängig davon, ob andere Codedateien bei .geändert wurden oder nicht. Hier ist ein Beispiel.


Hier ist ein einfaches Hello, World!Programm:

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

Die Ausgabe von 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

Lassen Sie uns ändern run.py:

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

Versuchen Sie erneut zu erstellen. Unten sehen Sie die Ausgabe:

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

Wie Sie oben sehen können, verwendet diesmal Docker während des Builds den Cache. Jetzt aktualisieren wir requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Unten ist die Ausgabe von 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

Beachten Sie, dass Docker während der Pip-Installation keinen Cache verwendet hat. Wenn es nicht funktioniert, überprüfen Sie Ihre 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
Nacyot
quelle
2
Dies scheint nicht zu funktionieren, da der ADDCache immer dann ungültig wird , wenn Docker eine Anweisung sieht .
Satoru
1
Ich bin mir nicht sicher, warum es nicht funktioniert. An der Datei require.txt (<src> on ADD ./requirements.txt /srv/requirements.txt) wird jedoch nichts geändert. Dann muss der Docker den Cache verwenden. Siehe Hinzufügen eines Abschnitts zum Dockerfile-Dokument.
Nacyot
16
Ja, der Cache wird verwendet, wenn sich die Datei "resources.txt" nicht ändert. Wenn sich jedoch die Datei "resources.txt" ändert, werden alle Anforderungen heruntergeladen. Gibt es eine Möglichkeit, ein Pip-Cache-Volume in den Docker-Container einzubinden, um es aus dem Cache zu laden?
Jitu
7
Der Schlüssel zu dieser Antwort ist, dass Sie require.txt hinzufügen ( ADD requirements.txt /srvbevor Sie pip ( RUN pip install -r requirements.txt) ausführen , und alle anderen Dateien nach dem Ausführen von pip hinzufügen . Daher sollten sie in der folgenden Reihenfolge vorliegen: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
Engelen
2
Bitte beachten Sie, dass dies nicht funktioniert, wenn Sie COPY anstelle von ADD verwenden
veuncent
29

Um die Netzwerkaktivität zu minimieren, können Sie pipauf ein Cache-Verzeichnis auf Ihrem Host-Computer verweisen .

Führen Sie Ihren Docker-Container mit der Pip-Cache-Verzeichnisbindung Ihres Hosts aus, die in das Pip-Cache-Verzeichnis Ihres Containers eingebunden ist. docker runBefehl sollte so aussehen:

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

Installieren Sie dann in Ihrer Docker-Datei Ihre Anforderungen als Teil einer ENTRYPOINTAnweisung (oder CMDAnweisung) anstatt als RUNBefehl. Dies ist wichtig, da (wie in den Kommentaren erwähnt) der Mount während der Image-Erstellung (wenn RUNAnweisungen ausgeführt werden) nicht verfügbar ist . Die Docker-Datei sollte folgendermaßen aussehen:

FROM my/base

ADD . /srv

ENTRYPOINT ["sh", "-c", "pip install -r requirements.txt && python setup.py install && run_server"]
Jakub Kukul
quelle
4
Nicht das, wonach das OP in seinem Anwendungsfall gesucht hat, aber wenn Sie einen Build-Server erstellen, ist dies eine großartige Idee
oden
2
Dies scheint ein Rezept für Probleme zu sein, insbesondere der Vorschlag, auf den Standard-Host-Cache zu verweisen. Sie mischen möglicherweise bogenspezifische Pakete.
Giacomo Lacava
@ GiacomoLacava danke, das ist ein sehr guter Punkt. Ich habe meine Antwort angepasst und den Teil entfernt, der vorgeschlagen hat, das Cache-Verzeichnis des Hosts wiederzuverwenden.
Jakub Kukul
24

Ich verstehe, dass diese Frage bereits einige populäre Antworten hat. Es gibt jedoch eine neuere Möglichkeit, Dateien für Paketmanager zwischenzuspeichern. Ich denke, es könnte in Zukunft eine gute Antwort sein, wenn BuildKit mehr Standard wird.

Ab Docker 18.09 gibt es experimentelle Unterstützung für BuildKit . BuildKit bietet Unterstützung für einige neue Funktionen in der Docker-Datei, einschließlich experimenteller Unterstützung für das Bereitstellen externer Volumes in RUNSchritten. Dies ermöglicht es uns, Caches für Dinge wie zu erstellen $HOME/.cache/pip/.

Wir werden die folgende requirements.txtDatei als Beispiel verwenden:

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

Ein typisches Beispiel für Python Dockerfilekönnte aussehen:

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

Wenn BuildKit mithilfe der DOCKER_BUILDKITUmgebungsvariablen aktiviert ist, können wir den nicht zwischengespeicherten pipSchritt in ca. 65 Sekunden erstellen :

$ 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

Fügen wir nun den experimentellen Header hinzu und ändern den RUNSchritt zum Zwischenspeichern der Python-Pakete:

# 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

Mach jetzt einen weiteren Build. Es sollte genauso lange dauern. Diesmal werden jedoch die Python-Pakete in unserem neuen Cache-Mount zwischengespeichert:

$ 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

Über 60 Sekunden. Ähnlich wie bei unserem ersten Build.

Nehmen Sie eine kleine Änderung an vor requirements.txt(z. B. Hinzufügen einer neuen Zeile zwischen zwei Paketen), um eine Cache-Ungültigmachung zu erzwingen und erneut auszuführen:

$ 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

Nur ca. 16 Sekunden!

Wir bekommen diese Beschleunigung, weil wir nicht mehr alle Python-Pakete herunterladen. Sie wurden ( pipin diesem Fall) vom Paketmanager zwischengespeichert und in einem Cache-Volume-Mount gespeichert. Die Volume-Bereitstellung wird für den Ausführungsschritt bereitgestellt, damit pipunsere bereits heruntergeladenen Pakete wiederverwendet werden können. Dies geschieht außerhalb des Docker-Layer-Caching .

Die Gewinne sollten bei größeren viel besser sein requirements.txt.

Anmerkungen:

  • Dies ist eine experimentelle Dockerfile-Syntax und sollte als solche behandelt werden. Möglicherweise möchten Sie im Moment nicht damit in der Produktion bauen.
  • Das BuildKit-Material funktioniert derzeit nicht unter Docker Compose oder anderen Tools, die die Docker-API direkt verwenden. Dies wird jetzt in Docker Compose ab 1.25.0 unterstützt. Siehe Wie aktivieren Sie BuildKit mit Docker-Compose?
  • Derzeit gibt es keine direkte Schnittstelle für die Verwaltung des Caches. Es wird gelöscht, wenn Sie a docker system prune -a.

Hoffentlich schaffen es diese Funktionen in Docker zum Erstellen und BuildKit wird zum Standard. In diesem Fall werde ich versuchen, diese Antwort zu aktualisieren.

Andy Shinn
quelle
Ich kann bestätigen, dass diese Lösung sehr gut funktioniert. Mein Build ging von über einer Minute auf nur 2,2 Sekunden zurück. Danke @ andy-shinn.
Kwuite
2
Jetzt auch Docker-Compose: stackoverflow.com/questions/58592259/…
Rexcirus
Hinweis: Wenn Sie SUDO zum Ausführen von Docker verwenden, müssen Sie wahrscheinlich Folgendes tun: sudo DOCKER_BUILDKIT = 1 ...
Vinícius M
Ich erhalte diesen Fehler: - Fehler beim Lösen mit Frontend dockerfile.v0: Fehler beim Erstellen der LLB-Definition: Fehler beim Analysieren der Docker-Datei: Fehlerzeile 10: Unbekanntes Flag: mount
Mayur Dangar
Das klingt so, als hätten Sie den Kommentar oben in Dockerfileder Docker-Version verpasst oder die Docker-Version ist zu alt. Ich würde eine neue Frage mit all Ihren Debugging-Informationen erstellen.
Andy Shinn
-10

Ich fand, dass ein besserer Weg darin besteht, einfach das Python-Site-Packages-Verzeichnis als Volume hinzuzufügen.

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

Auf diese Weise kann ich einfach neue Bibliotheken installieren, ohne eine vollständige Neuerstellung durchführen zu müssen.

EDIT : Ignorieren Sie diese Antwort, Jkukuls Antwort oben hat für mich funktioniert. Meine Absicht war es, den Site-Packages- Ordner zwischenzuspeichern. Das hätte eher so ausgesehen:

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

Das Zwischenspeichern des Download-Ordners ist jedoch viel sauberer. Dadurch werden auch die Räder zwischengespeichert, sodass die Aufgabe ordnungsgemäß ausgeführt wird.

jaywhy13
quelle
2
Und was passiert, wenn Sie versuchen, diese Docker-Datei auf einem anderen Computer zu erstellen? Dies ist keine nachhaltige Lösung.
Aaron McMillin
Wirklich verwirrt, stellte sich heraus, dass dies fehlerhaft war und ich war mir nicht sicher warum. Könnten Sie etwas mehr Details geben.
Jaywhy13
3
Ihr Docker-Image hängt vom Status des Hostsystems ab. Dies macht den größten Teil des Nutzens von Docker ungültig. Alles, was das Image benötigt, sollte darin installiert sein. Verwenden Sie die Docker-Datei, um alle Abhängigkeiten zu installieren. Wenn Sie vermeiden möchten, dass die Pakete jedes Mal neu heruntergeladen werden, wenn Sie die Antwort von jkukul zum Mounten des Pip-Cache erstellen, ist dies der richtige Weg.
Aaron McMillin
2
Glühbirne ging gerade aus, danke. Ich habe tatsächlich versucht, das Site-Packages-Verzeichnis von der VM aus zu mounten, nicht vom Host. Ein ziemliches Versehen. Ich denke im Geiste habe ich versucht, das Gleiche zu tun, wie es Jkulkul vorgeschlagen hat. Danke für die Klarheit!
Jaywhy13
@AaronMcMillin Er ist eigentlich nicht abhängig von einem Pfad auf dem Host. Er mountet die Site-Pakete im Container auf ein anonymes Volume. Trotzdem eine schlechte Idee
Ruohola