Как избежать переустановки пакетов при создании образа Docker для проектов Python?

128

Мой Dockerfile похож на

FROM my/base

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

ENTRYPOINT ["run_server"]

Каждый раз, когда я создаю новый образ, необходимо переустанавливать зависимости, что может быть очень медленным в моем регионе.

Я думаю, что один из способов установки cacheпакетов - это переопределить my/baseобраз более новыми изображениями, например:

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

Поэтому в следующий раз, когда я буду использовать этот Dockerfile, в моем / base уже установлено несколько пакетов.

Но у этого решения есть две проблемы:

  1. Не всегда возможно переопределить базовое изображение
  2. Базовое изображение становится все больше и больше по мере наложения на него новых изображений.

Итак, какое лучшее решение я мог бы использовать для решения этой проблемы?

РЕДАКТИРОВАТЬ##:

Некоторая информация о докере на моей машине:

  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
Сатору
источник
Вы удаляете промежуточное изображение после завершения создания изображения?
Regan
Конечно, нет, но это не имеет значения, потому что, когда я перестраиваю образ, я все еще использую оригиналmy/base
satoru

Ответы:

139

Попробуйте создать Dockerfile, который выглядит примерно так:

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 будет использовать кеш во время установки pip до тех пор, пока вы не внесете никаких изменений в requirements.txtфайл, независимо от того, .были ли изменены другие файлы кода в . Вот вам пример.


Вот простая Hello, World!программа:

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

Результат сборки докера:

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

Изменим run.py:

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

Попробуйте построить снова, результат ниже:

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

Как вы можете видеть выше, на этот раз докер использует кеш во время сборки. Теперь давайте обновим requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Ниже приведен результат сборки докера:

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

Обратите внимание, как докер не использовал кеш во время установки pip. Если не работает, проверьте версию докера.

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
источник
2
Кажется, это не работает, потому что всякий раз, когда докер видит ADDинструкцию, кеш становится недействительным.
satoru
1
Я не уверен, почему это не работает. Но в файле requirements.txt нет никаких изменений (<src> включен ADD ./requirements.txt /srv/requirements.txt), тогда докер должен использовать кеш. См. Добавление секции в документе Dockerfile.
nacyot
16
Да, он будет использовать кеш, если файл requirements.txt не изменится. Но если файл requirements.txt изменится, то все требования будут загружены. Есть ли способ смонтировать том кеша pip в контейнер докеров для загрузки из кеша?
Jitu
7
Ключ к этому ответу заключается в том, что вы добавляете файл requirements.txt ( ADD requirements.txt /srvперед запуском pip ( RUN pip install -r requirements.txt) и все остальные файлы после запуска pip. Таким образом, они должны быть в следующем порядке: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
engelen 01
2
Обратите внимание, что это не работает при использовании COPY вместо ADD
veuncent
29

Чтобы минимизировать сетевую активность, вы можете указать pipкаталог кеша на вашем хост-компьютере.

Запустите свой док-контейнер с привязкой к каталогу pip-кеша вашего хоста, смонтированным в каталоге pip-кеша вашего контейнера. docker runкоманда должна выглядеть так:

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

Затем в вашем Dockerfile установите свои требования как часть ENTRYPOINTоператора (или CMDоператора), а не как RUNкоманду. Это важно, потому что (как указано в комментариях) монтирование недоступно во время построения образа (когда RUNвыполняются операторы). Файл Docker должен выглядеть так:

FROM my/base

ADD . /srv

ENTRYPOINT ["sh", "-c", "pip install -r requirements.txt && python setup.py install && run_server"]
Якуб Кукул
источник
4
Не то, что OP искал в своем варианте использования, но если вы создаете сервер сборки, это отличная идея
oden
2
Это похоже на рецепт проблем, особенно предложение указать кеш хоста по умолчанию. Вы потенциально смешиваете пакеты, специфичные для архитектуры.
Джакомо Лакава,
@GiacomoLacava, спасибо, это очень хороший момент. Я скорректировал свой ответ и удалил часть, в которой предлагалось повторно использовать каталог кеша хостов.
Якуб Кукул,
24

Насколько я понимаю, на этот вопрос уже есть популярные ответы. Но есть более новый способ кэширования файлов для менеджеров пакетов. Я думаю, что это может быть хорошим ответом в будущем, когда BuildKit станет более стандартным.

Начиная с Docker 18.09 имеется экспериментальная поддержка BuildKit . BuildKit добавляет поддержку некоторых новых функций в Dockerfile, включая экспериментальную поддержку монтирования внешних томов по RUNшагам. Это позволяет нам создавать кеши для таких вещей, как $HOME/.cache/pip/.

В requirements.txtкачестве примера мы будем использовать следующий файл:

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

Типичный пример Python Dockerfileможет выглядеть так:

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

Если BuildKit включен с помощью DOCKER_BUILDKITпеременной окружения, мы можем построить некэшированный pipшаг примерно за 65 секунд:

$ 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

Теперь давайте добавим экспериментальный заголовок и изменим RUNшаг для кеширования пакетов 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

Давай, сделай еще одну сборку сейчас. На это должно уйти столько же времени. Но на этот раз он кэширует пакеты Python в нашем новом монтировании кеша:

$ 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

Около 60 секунд. Подобно нашей первой сборке.

Внесите небольшие изменения в requirements.txt(например, добавив новую строку между двумя пакетами), чтобы принудительно сделать кеш недействительным и запустить снова:

$ 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

Всего около 16 секунд!

Мы получаем это ускорение, потому что мы больше не загружаем все пакеты Python. Они были кэшированы менеджером пакетов ( pipв данном случае) и сохранены в монтированном томе кеша. Установка тома предоставляется на этапе запуска, чтобы pipможно было повторно использовать наши уже загруженные пакеты. Это происходит вне кеширования уровня Docker .

Прирост должен быть намного лучше на больших requirements.txt .

Ноты:

  • Это экспериментальный синтаксис Dockerfile, и его следует рассматривать как таковой. Возможно, вы не захотите использовать это в производстве в данный момент.
  • Материал BuildKit не работает в Docker Compose или других инструментах, которые напрямую используют Docker API в данный момент. Теперь это поддерживается в Docker Compose с версии 1.25.0. См. Как включить BuildKit с помощью docker-compose?
  • На данный момент нет прямого интерфейса для управления кешем. Он очищается, когда вы делаете docker system prune -a.

Надеюсь, эти функции войдут в Docker для сборки, а BuildKit станет по умолчанию. Если / когда это произойдет, я постараюсь обновить этот ответ.

Энди Шинн
источник
Я могу подтвердить, что это решение работает очень хорошо. Моя сборка упала с минуты до 2,2 секунды. Спасибо @ andy-shinn.
Kwuite
2
Теперь также Docker-Compose: stackoverflow.com/questions/58592259/…
Rexcirus
Примечание: если вы используете SUDO для запуска докера, вам, вероятно, потребуется сделать: sudo DOCKER_BUILDKIT = 1 ...
Виниций М.
Я получаю эту ошибку: - не удалось решить с помощью внешнего интерфейса dockerfile.v0: не удалось создать определение LLB: строка ошибки синтаксического анализа Dockerfile 10: Неизвестный флаг: mount
Mayur Dangar
Звучит так, будто вы пропустили комментарий вверху Dockerfileили версия Docker слишком старая. Я бы создал новый вопрос со всей вашей отладочной информацией.
Энди Шинн,
-10

Я обнаружил, что лучше просто добавить каталог Python site-packages в качестве тома.

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

Таким образом, я могу просто установить новые библиотеки без необходимости полной перестройки.

РЕДАКТИРОВАТЬ : Не обращайте внимания на этот ответ, ответ jkukul выше работал у меня. Я намеревался кэшировать папку пакетов сайтов . Это выглядело бы примерно так:

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

Кеширование папки загрузки намного чище. Это также кеширует колеса, поэтому она правильно выполняет задачу.

jaywhy13
источник
2
И что происходит, когда вы пытаетесь собрать этот файл докеров на другом компьютере. Это не рациональное решение.
Аарон Макмиллин
Искренне сбитый с толку, это оказалось ошибкой, и я не был уверен, почему. Не могли бы вы подробнее рассказать.
jaywhy13
3
Ваш образ докера зависит от состояния хост-системы. Это сводит на нет большую часть полезности докера. В нем должно быть установлено все, что нужно образу. используйте Dockerfile для установки всех зависимостей. Если вы хотите избежать повторной загрузки пакетов каждый раз, когда вы создаете ответ из jkukul, нужно смонтировать кеш pip.
Аарон Макмиллин
2
Лампочка только что погасла, спасибо. На самом деле я пытался смонтировать каталог site-packages с виртуальной машины, а не с хоста. Довольно оплошность. Думаю, по духу я пытался сделать то же, что и предложил jkulkul. Спасибо за ясность!
jaywhy13
@AaronMcMillin На самом деле он не зависит от пути на хосте. Он монтирует пакеты сайтов в контейнере на анонимный том. Но все еще плохая идея
руохола