Bagaimana cara menghindari penginstalan ulang paket saat membuat image Docker untuk proyek Python?

128

Dockerfile saya adalah seperti

FROM my/base

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

ENTRYPOINT ["run_server"]

Setiap kali saya membuat image baru, dependensi harus diinstal ulang, yang bisa sangat lambat di wilayah saya.

Salah satu cara saya memikirkan cachepaket yang telah diinstal adalah menimpa my/basegambar dengan gambar yang lebih baru seperti ini:

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

Jadi lain kali saya membangun dengan Dockerfile ini, / base saya sudah menginstal beberapa paket.

Tetapi solusi ini memiliki dua masalah:

  1. Tidak selalu mungkin untuk mengganti gambar dasar
  2. Gambar dasar semakin besar dan besar karena gambar yang lebih baru dilapisi di atasnya

Jadi solusi apa yang lebih baik yang dapat saya gunakan untuk mengatasi masalah ini?

EDIT ##:

Beberapa informasi tentang buruh pelabuhan di mesin saya:

  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
sumber
Apakah Anda menghapus gambar perantara setelah Anda selesai membangun gambar Anda?
Regan
Tentu saja tidak, tetapi ini tidak relevan karena ketika saya membangun kembali gambar, saya masih berdasarkan aslinyamy/base
satoru

Jawaban:

139

Coba buat Dockerfile yang terlihat seperti ini:

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 akan menggunakan cache selama instalasi pip selama Anda tidak membuat perubahan apa pun pada requirements.txt, terlepas dari fakta apakah file kode lain di .diubah atau tidak. Berikut contohnya.


Berikut Hello, World!program sederhana :

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

Output dari build buruh pelabuhan:

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

Mari memodifikasi run.py:

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

Coba buat lagi, berikut ini hasilnya:

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

Seperti yang Anda lihat di atas, kali ini pekerja galangan kapal menggunakan cache selama proses pembuatan. Sekarang, mari perbarui requirements.txt:

# requirements.txt

pytest==2.3.4
ipython

Di bawah ini adalah output dari build buruh pelabuhan:

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

Perhatikan bagaimana buruh pelabuhan tidak menggunakan cache selama instalasi pip. Jika tidak berhasil, periksa versi buruh pelabuhan Anda.

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.dll
sumber
2
Ini sepertinya tidak berhasil, karena setiap kali buruh pelabuhan melihat ADDinstruksi, cache tidak valid.
satoru
1
Saya tidak yakin mengapa itu tidak berhasil. Tapi tidak ada perubahan apapun pada requirement.txt (<src> on ADD ./requirements.txt /srv/requirements.txt), maka buruh pelabuhan harus menggunakan cache. Lihat menambahkan keamanan pada dokumen Dockerfile.
nacyot
16
Ya itu akan menggunakan cache jika Requirement.txt tidak berubah. Tetapi jika requirement.txt berubah maka semua persyaratan diunduh. Apakah ada cara untuk memasang volume cache pip ke kontainer buruh pelabuhan untuk memuat dari cache?
Jitu
7
Kunci dari jawaban ini adalah Anda menambahkan persyaratan.txt ( ADD requirements.txt /srvsebelum Anda menjalankan pip ( RUN pip install -r requirements.txt), dan menambahkan semua file lain setelah menjalankan pip. Jadi, mereka harus berada dalam urutan berikut: (1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; ( 3)ADD . /srv
engelen
2
Harap dicatat bahwa ini tidak bekerja saat menggunakan COPY daripada ADD
veuncent
29

Untuk meminimalkan aktivitas jaringan, Anda dapat mengarahkan pipke direktori cache di mesin host Anda.

Jalankan container docker Anda dengan bind direktori pip cache host Anda yang dipasang ke direktori pip cache container Anda. docker runperintah akan terlihat seperti ini:

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

Kemudian di Dockerfile Anda, instal persyaratan Anda sebagai bagian dari ENTRYPOINTpernyataan (atau CMDpernyataan), bukan sebagai RUNperintah. Ini penting, karena (seperti yang ditunjukkan dalam komentar) mount tidak tersedia selama pembuatan gambar (saat RUNpernyataan dijalankan). File Docker akan terlihat seperti ini:

FROM my/base

ADD . /srv

ENTRYPOINT ["sh", "-c", "pip install -r requirements.txt && python setup.py install && run_server"]
Jakub Kukul
sumber
4
Bukan yang dicari OP dalam kasus penggunaannya, tetapi jika Anda membuat server build, ini adalah ide yang bagus
oden
2
Ini sepertinya resep untuk masalah, terutama saran untuk menunjuk ke cache host default. Anda berpotensi mencampur paket khusus arch.
Giacomo Lacava
@GiacomoLacava terima kasih, itu poin yang sangat bagus. Saya menyesuaikan jawaban saya dan menghapus bagian yang menyarankan untuk menggunakan kembali direktori cache host.
Jakub Kukul
24

Saya mengerti pertanyaan ini sudah memiliki beberapa jawaban populer. Tetapi ada cara yang lebih baru untuk menyimpan file dalam cache untuk manajer paket. Saya rasa ini bisa menjadi jawaban yang bagus di masa depan ketika BuildKit menjadi lebih standar.

Mulai Docker 18.09 ada dukungan eksperimental untuk BuildKit . BuildKit menambahkan dukungan untuk beberapa fitur baru di Dockerfile termasuk dukungan eksperimental untuk memasang volume eksternal ke dalam beberapa RUNlangkah. Ini memungkinkan kami membuat cache untuk hal-hal seperti $HOME/.cache/pip/.

Kami akan menggunakan requirements.txtfile berikut sebagai contoh:

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

Contoh tipikal Python Dockerfilemungkin terlihat seperti:

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

Dengan BuildKit diaktifkan menggunakan DOCKER_BUILDKITvariabel lingkungan, kita dapat membuat piplangkah yang tidak di -cache dalam waktu sekitar 65 detik:

$ 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

Sekarang, mari kita tambahkan tajuk eksperimental dan modifikasi RUNlangkah untuk menyimpan paket Python ke cache:

# 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

Silakan dan lakukan build lain sekarang. Ini harus memakan waktu yang sama. Tapi kali ini cache paket Python di cache mount baru kami:

$ 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

Sekitar 60 detik. Mirip dengan build pertama kami.

Buat perubahan kecil pada requirements.txt(seperti menambahkan baris baru di antara dua paket) untuk memaksa pembatalan cache dan dijalankan lagi:

$ 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

Hanya sekitar 16 detik!

Kami mendapatkan percepatan ini karena kami tidak lagi mengunduh semua paket Python. Mereka di-cache oleh manajer paket ( pipdalam hal ini) dan disimpan dalam mount volume cache. Volume mount disediakan untuk langkah run sehingga pipdapat menggunakan kembali paket yang sudah kami unduh. Ini terjadi di luar cache lapisan Docker .

Keuntungannya harus jauh lebih baik pada yang lebih besar requirements.txt.

Catatan:

  • Ini adalah sintaks Dockerfile eksperimental dan harus diperlakukan seperti itu. Anda mungkin tidak ingin membangun dengan ini dalam produksi saat ini.
  • Item BuildKit tidak berfungsi di bawah Docker Compose atau alat lain yang secara langsung menggunakan API Docker saat ini. Sekarang ada dukungan untuk ini di Docker Compose mulai 1.25.0. Lihat Bagaimana Anda mengaktifkan BuildKit dengan docker-compose?
  • Tidak ada antarmuka langsung untuk mengelola cache saat ini. Itu dihapus ketika Anda melakukan a docker system prune -a.

Mudah-mudahan, fitur-fitur ini akan membuatnya menjadi Docker untuk dibangun dan BuildKit akan menjadi defaultnya. Jika / ketika itu terjadi, saya akan mencoba memperbarui jawaban ini.

Andy Shinn
sumber
Saya dapat mengonfirmasi bahwa solusi ini berfungsi dengan sangat baik. Build saya turun dari lebih dari satu menit menjadi hanya 2,2 detik. Terima kasih @ andy-shinn.
Kwuite
2
Sekarang juga Docker-Compose: stackoverflow.com/questions/58592259/…
Rexcirus
Catatan: Jika Anda menggunakan SUDO untuk menjalankan buruh pelabuhan, Anda mungkin perlu melakukan: sudo DOCKER_BUILDKIT = 1 ...
Vinícius M
Saya mendapatkan Kesalahan ini: - gagal menyelesaikan dengan frontend dockerfile.v0: gagal membuat definisi LLB: Baris kesalahan parse Dockerfile 10: Bendera tidak dikenal: mount
Mayur Dangar
Sepertinya Anda melewatkan komentar di bagian atas Dockerfileatau versi Docker terlalu lama. Saya akan membuat pertanyaan baru dengan semua informasi debugging Anda.
Andy Shinn
-10

Saya menemukan bahwa cara yang lebih baik adalah dengan menambahkan direktori paket situs Python sebagai volume.

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

Dengan cara ini saya bisa menginstal pustaka baru tanpa harus melakukan pembangunan ulang penuh.

EDIT : Abaikan jawaban ini, jawaban jkukul di atas berhasil untuk saya. Maksud saya adalah untuk meng-cache folder paket situs . Itu akan terlihat seperti:

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

Caching folder unduhan jauh lebih bersih. Itu juga meng-cache roda, sehingga dapat menyelesaikan tugas dengan benar.

jaywhy13
sumber
2
Dan apa yang terjadi saat Anda mencoba membangun file dok ini di komputer yang berbeda. Ini bukan solusi yang berkelanjutan.
Aaron McMillin
Benar-benar bingung, ini ternyata buggy dan saya tidak begitu yakin mengapa. Bisakah Anda memberikan lebih banyak detail.
jaywhy13
3
Gambar buruh pelabuhan Anda tergantung pada status dari sistem host. Ini membatalkan sebagian besar utilitas buruh pelabuhan. Semua yang dibutuhkan gambar harus dipasang di dalamnya. gunakan Dockerfile untuk menginstal semua dependensi. Jika Anda ingin menghindari mengunduh ulang paket setiap kali Anda membangun jawaban dari jkukul untuk memasang cache pip adalah caranya.
Aaron McMillin
2
Bola lampu baru saja berbunyi terima kasih. Saya sebenarnya mencoba memasang direktori paket situs dari VM, bukan host. Pengawasan yang cukup. Saya pikir dengan semangat saya mencoba melakukan hal yang sama seperti yang disarankan jkulkul. Terima kasih atas kejelasannya!
jaywhy13
@AaronMcMillin Dia sebenarnya tidak tergantung pada jalur host. Dia memasang paket situs di wadah ke volume anonim. Masih ide yang buruk
ruohola