Как избежать переустановки пакетов при создании образа Docker для проектов Python?
Мой 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, у меня уже будет несколько пакетов.
Но у этого решения есть две проблемы:
- Не всегда возможно переопределить базовое изображение
- Базовое изображение становится все больше и больше, поскольку на него накладываются новые изображения
Так какое лучшее решение я мог бы использовать, чтобы решить эту проблему?
РЕДАКТИРОВАТЬ##:
Немного информации о докере на моей машине:
☁ 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
6 ответов
Попробуйте собрать нижеприведенный 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"]
Если есть какие-то изменения на .
(ваш проект), пропустить докер pip install
строка с использованием кеша.
Docker работает только pip install
на сборке, когда вы редактируете файл require.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
Как вы можете видеть выше, докер использует сборочный кеш. И на этот раз я обновляю файл require.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
И докер не использует сборочный кеш. Если это не работает, проверьте версию вашего докера.
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
Насколько я понимаю, на этот вопрос уже есть популярные ответы. Но есть более новый способ кэширования файлов для менеджеров пакетов. Я думаю, что это может быть хорошим ответом в будущем, когда 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 станет по умолчанию. Если / когда это произойдет, я постараюсь обновить этот ответ.
Чтобы минимизировать сетевую активность, вы можете указать pip
в каталог кэша на вашем хост-компьютере.
Запустите ваш докер-контейнер с привязкой каталога pip-кеша вашего хоста, установленного в каталог pip-кэша вашего контейнера. docker run
Команда должна выглядеть так:
docker run -v $HOME/.cache/pip/:/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"]
Вероятно, будет лучше, если в качестве кэша будет использоваться каталог pip по умолчанию в хост-системе (например, $HOME/.cache/pip/
в Linux или $HOME/Library/Caches/pip/
на OSX), так же, как я предложил в примере docker run
команда.
pipenv install
по умолчанию пытается повторно заблокировать. Когда это происходит, кешированный слой сборки Docker не используется, потому что Pipfile.lock изменился. См. Документацию
Решением для этого является версия Pipfile.lock и использование
RUN pipenv sync
вместо.
Спасибо JFG Piñeiro.
Когда вы попытаетесь добиться этого с помощью GitLab CI/CD, вы можете следовать этому документу:https://docs.gitlab.com/ee/ci/docker/docker_layer_caching.html .
И если у вас это не работает, попробуйте явно установитьDOCKER_BUILDKIT
иBUILDKIT_INLINE_CACHE
variables:
DOCKER_BUILDKIT: '1'
....
script:
- docker build ........ BUILDKIT_INLINE_CACHE=1 .
Я обнаружил, что лучше всего добавить каталог сайтов-пакетов Python как том.
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
- /usr/local/lib/python2.7/site-packages/
Таким образом, я могу просто установить новые библиотеки без необходимости полной перестройки.
РЕДАКТИРОВАТЬ: Игнорировать этот ответ, JKukul ответ выше работал для меня. Моим намерением было кэширование папки site-packages. Это выглядело бы как-то так:
volumes:
- .:/code
- ./cached-packages:/usr/local/lib/python2.7/site-packages/
Кэширование папки загрузки намного чище, хотя. Это также кэширует колеса, поэтому оно правильно выполняет задачу.