Jak uniknąć ponownej instalacji pakietów podczas tworzenia obrazu platformy Docker dla projektów w języku Python?


128

Mój plik Dockerfile jest podobny do

FROM my/base

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

ENTRYPOINT ["run_server"]

Za każdym razem, gdy tworzę nowy obraz, zależności muszą być ponownie instalowane, co może być bardzo powolne w moim regionie.

Jednym ze sposobów, w jaki myślę o cachepakietach, które zostały zainstalowane, jest zastąpienie my/baseobrazu nowszymi obrazami, takimi jak ten:

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

Więc następnym razem, gdy będę budować z tym plikiem Dockerfile, mój / base ma już zainstalowane pakiety.

Ale to rozwiązanie ma dwa problemy:

  1. Nie zawsze jest możliwe zastąpienie obrazu podstawowego
  2. Obraz podstawowy staje się coraz większy w miarę nakładania na niego nowszych obrazów

Więc jakie lepsze rozwiązanie mógłbym użyć do rozwiązania tego problemu?

EDYTOWAĆ##:

Niektóre informacje o dokerze na moim komputerze:

☁  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

Czy usuwasz obraz pośredni po zakończeniu tworzenia obrazu?
— Regan

Oczywiście, że nie, ale nie ma to znaczenia, ponieważ odbudowując obraz nadal my/base
— bazuję

Odpowiedzi:


139

Spróbuj zbudować plik Dockerfile, który wygląda mniej więcej tak:

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 będzie używał pamięci podręcznej podczas instalacji pip, o ile nie wprowadzisz żadnych zmian w requirements.txtpliku, niezależnie od tego, czy inne pliki kodu w witrynie .zostały zmienione, czy nie. Oto przykład.


Oto prosty Hello, World!program:

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

Dane wyjściowe kompilacji dockera:

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

Zmodyfikujmy run.py:

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

Spróbuj ponownie zbudować, poniżej jest wynik:

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

Jak widać powyżej, tym razem docker używa pamięci podręcznej podczas kompilacji. Teraz zaktualizujmy requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Poniżej znajduje się wynik kompilacji dockera:

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

Zwróć uwagę, że docker nie używał pamięci podręcznej podczas instalacji pip. Jeśli to nie zadziała, sprawdź wersję dockera.

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
Wydaje się, że to nie działa, ponieważ ilekroć docker widzi ADDinstrukcję, pamięć podręczna jest unieważniana.
— satoru

1
Nie wiem, dlaczego to nie działa. Ale nie ma żadnej zmiany w Requirements.txt (<src> on ADD ./requirements.txt /srv/requirements.txt), wtedy docker musi używać cache. Zobacz add seciton on Dockerfile document.
— nacyot

16
Tak, użyje pamięci podręcznej, jeśli require.txt nie ulegnie zmianie. Jeśli jednak plik Requirements.txt ulegnie zmianie, wszystkie wymagania zostaną pobrane. Czy istnieje sposób, w jaki mogę zamontować wolumin pamięci podręcznej pip w kontenerze Dockera, aby załadować go z pamięci podręcznej?
— Jitu

7
Kluczem do tej odpowiedzi jest dodanie Requirements.txt ( ADD requirements.txt /srvprzed uruchomieniem pip ( RUN pip install -r requirements.txt) i dodaniem wszystkich innych plików po uruchomieniu pip. Dlatego powinny one być w następującej kolejności: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
— engelen

2
Należy pamiętać, że to nie działa, gdy używasz KOPIUJ zamiast DODAJ
— veuncent

29

Aby zminimalizować aktywność sieciową, możesz wskazać pipkatalog pamięci podręcznej na komputerze głównym.

Uruchom kontener Docker z powiązaniem katalogu pamięci podręcznej pip hosta podłączonym do katalogu pamięci podręcznej pip kontenera. docker runpolecenie powinno wyglądać następująco:

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

Następnie w pliku Dockerfile zainstaluj wymagania jako część ENTRYPOINTinstrukcji (lub CMDinstrukcji), a nie jako RUNpolecenie. Jest to ważne, ponieważ (jak wskazano w komentarzach) mount nie jest dostępny podczas budowania obrazu (podczas RUNwykonywania instrukcji). Plik Dockera powinien wyglądać następująco:

FROM my/base

ADD . /srv

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

4
Nie to, czego szukał OP w swoim przypadku użycia, ale jeśli tworzysz serwer kompilacji, to świetny pomysł
— oden

2
Wydaje się, że jest to recepta na problemy, szczególnie sugestia wskazująca domyślną pamięć podręczną hosta. Potencjalnie mieszasz pakiety specyficzne dla arch.
— Giacomo Lacava

@GiacomoLacava dzięki, to bardzo dobra uwaga. Dostosowałem odpowiedź i usunąłem część, która sugerowała ponowne użycie katalogu pamięci podręcznej hostów.
— Jakub Kukul

24

Rozumiem, że to pytanie ma już kilka popularnych odpowiedzi. Ale jest nowszy sposób buforowania plików dla menedżerów pakietów. Myślę, że może to być dobra odpowiedź w przyszłości, gdy BuildKit stanie się bardziej standardowy.

Od Dockera 18.09 dostępna jest eksperymentalna obsługa BuildKit . BuildKit dodaje obsługę niektórych nowych funkcji w pliku Dockerfile, w tym eksperymentalną obsługę montażu woluminów zewnętrznych w RUNkrokach. To pozwala nam tworzyć pamięci podręczne dla rzeczy takich jak $HOME/.cache/pip/.

requirements.txtJako przykładu użyjemy następującego pliku:

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

Typowy przykład Pythona Dockerfilemoże wyglądać następująco:

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

Po włączeniu BuildKit przy użyciu DOCKER_BUILDKITzmiennej środowiskowej możemy zbudować niezbuforowany pipkrok w około 65 sekund:

$ 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

Teraz dodajmy eksperymentalny nagłówek i zmodyfikujmy RUNkrok, aby buforować pakiety Pythona:

# 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

Śmiało i zrób teraz kolejną kompilację. Powinno to zająć tyle samo czasu. Ale tym razem buforuje pakiety Pythona w naszym nowym montowaniu pamięci podręcznej:

$ 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

Około 60 sekund. Podobnie jak w naszej pierwszej kompilacji.

Wprowadź niewielką zmianę w requirements.txt(na przykład dodanie nowej linii między dwoma pakietami), aby wymusić unieważnienie pamięci podręcznej i uruchomić ponownie:

$ 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

Tylko około 16 sekund!

Otrzymujemy to przyspieszenie, ponieważ nie pobieramy już wszystkich pakietów Pythona. Zostały buforowane przez menedżera pakietów ( pipw tym przypadku) i przechowywane w woluminie pamięci podręcznej. Montaż woluminu jest dostarczany do kroku uruchamiania, dzięki czemu pipmożna ponownie wykorzystać nasze już pobrane pakiety. Dzieje się to poza buforowaniem warstwy Dockera .

Zyski powinny być znacznie lepsze na większych requirements.txt.

Uwagi:

  • To jest eksperymentalna składnia Dockerfile i tak powinna być traktowana. W tej chwili możesz nie chcieć tworzyć tego w wersji produkcyjnej.
  • Kompilacja BuildKit nie działa w Docker Compose ani w innych narzędziach, które w tej chwili bezpośrednio używają API Dockera. Jest to teraz obsługiwane w Docker Compose od 1.25.0. Zobacz, jak włączyć BuildKit za pomocą docker-compose?
  • W tej chwili nie ma bezpośredniego interfejsu do zarządzania pamięcią podręczną. Jest czyszczony, gdy wykonujesz docker system prune -a.

Miejmy nadzieję, że te funkcje zostaną wprowadzone do Dockera do budowania, a BuildKit stanie się domyślnym. Jeśli tak się stanie, spróbuję zaktualizować tę odpowiedź.


Mogę potwierdzić, że to rozwiązanie działa bardzo dobrze. Moja kompilacja spadła z ponad minuty do zaledwie 2,2 sekundy. Dzięki @ andy-shinn.
— Kwuite

2
Teraz także Docker-Compose: stackoverflow.com/questions/58592259/ ...
— Rexcirus,

Uwaga: jeśli używasz SUDO do uruchomienia dockera, prawdopodobnie musisz to zrobić: sudo DOCKER_BUILDKIT = 1 ...
— Vinícius M

Otrzymuję ten błąd: - nie udało się rozwiązać z frontend dockerfile.v0: nie udało się utworzyć definicji LLB: Dockerfile parse Error line 10: Unknown flag: mount
— Mayur Dangar

To brzmi tak, jakbyś przegapił komentarz u góry Dockerfilelub wersja Dockera jest za stara. Utworzyłbym nowe pytanie ze wszystkimi informacjami dotyczącymi debugowania.
— Andy Shinn

-10

Zauważyłem, że lepszym sposobem jest po prostu dodanie katalogu pakietów witryn Pythona jako woluminu.

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

W ten sposób mogę po prostu zainstalować nowe biblioteki za pomocą pip bez konieczności wykonywania pełnej przebudowy.

EDYCJA : Zignoruj tę odpowiedź, odpowiedź jkukul powyżej działała dla mnie. Moim zamiarem było buforowanie folderu pakietów witryn . To wyglądałoby bardziej jak:

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

Buforowanie folderu pobierania jest jednak znacznie czystsze. To również buforuje koła, więc poprawnie wykonuje zadanie.


2
A co się stanie, gdy spróbujesz zbudować ten plik dockerfile na innym komputerze. To nie jest trwałe rozwiązanie.
— Aaron McMillin

Naprawdę zdezorientowany, okazało się, że to buggy i nie byłem pewien dlaczego. Czy mógłbyś podać więcej szczegółów.
— jaywhy13

3
Obraz dockera zależy od stanu systemu hosta. To unieważnia większość użyteczności dockera. Wszystko, czego potrzebuje obraz, powinno być w nim zainstalowane. użyj pliku Dockerfile, aby zainstalować wszystkie zależności. Jeśli chcesz uniknąć ponownego pobierania pakietów za każdym razem, gdy tworzysz odpowiedź z jkukul, aby zamontować pamięć podręczną pip, najlepszym rozwiązaniem jest.
— Aaron McMillin

2
Żarówka właśnie się włączyła, dzięki. W rzeczywistości próbowałem zamontować katalog pakietów witryn z maszyny wirtualnej, a nie z hosta. Całkiem przeoczenie. Myślę, że w duchu starałem się zrobić to samo, co sugerował jkulkul. Dzięki za jasność!
— jaywhy13

@AaronMcMillin W rzeczywistości nie jest zależny od ścieżki na hoście. Montuje pakiety witryn w kontenerze do anonimowego tomu. Wciąż jednak zły pomysł
— ruohola
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.