Beberapa RUN vs. RUN berantai tunggal di Dockerfile, apa yang lebih baik?

132

Dockerfile.1mengeksekusi banyak RUN:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 bergabung dengan mereka:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

Masing-masing RUNmembuat layer, jadi saya selalu berasumsi bahwa lebih sedikit layer yang lebih baik dan karenanya Dockerfile.2lebih baik.

Ini jelas benar ketika sebuah RUNmenghapus sesuatu yang ditambahkan oleh sebelumnya RUN(yaitu yum install nano && yum clean all), tetapi dalam kasus di mana setiap RUNmenambahkan sesuatu, ada beberapa poin yang perlu kita pertimbangkan:

  1. Layers seharusnya hanya menambahkan diff di atas yang sebelumnya, jadi jika lapisan kemudian tidak menghapus sesuatu yang ditambahkan sebelumnya, seharusnya tidak ada banyak ruang menghemat ruang antara kedua metode ...

  2. Lapisan ditarik secara paralel dari Docker Hub, jadi Dockerfile.1, meskipun mungkin sedikit lebih besar, secara teoritis akan diunduh lebih cepat.

  3. Jika menambahkan kalimat ke-4 (yaitu echo This is the D > d) dan pembangunan kembali secara lokal, Dockerfile.1akan membangun lebih cepat berkat cache, tetapi Dockerfile.2harus menjalankan semua 4 perintah lagi.

Jadi, pertanyaannya: Mana cara yang lebih baik untuk melakukan Dockerfile?

Yajo
sumber
1
Tidak dapat dijawab secara umum karena tergantung pada situasi dan penggunaan gambar (mengoptimalkan ukuran, kecepatan unduh, atau kecepatan bangunan)
Henry

Jawaban:

99

Jika memungkinkan, saya selalu menggabungkan perintah yang membuat file dengan perintah yang menghapus file yang sama menjadi satu RUNbaris. Ini karena setiap RUNbaris menambahkan lapisan pada gambar, hasilnya secara harfiah perubahan sistem file yang dapat Anda lihat docker diffpada wadah sementara yang dibuatnya. Jika Anda menghapus file yang dibuat di lapisan yang berbeda, yang dilakukan oleh sistem file gabungan adalah mendaftarkan perubahan sistem file di lapisan baru, file tersebut masih ada di lapisan sebelumnya dan dikirimkan melalui jaringan dan disimpan dalam disk. Jadi jika Anda mengunduh kode sumber, mengekstraknya, mengompilasinya menjadi biner, dan kemudian menghapus file tgz dan sumber pada akhirnya, Anda benar-benar ingin semua ini dilakukan dalam satu lapisan untuk mengurangi ukuran gambar.

Selanjutnya, saya pribadi membagi lapisan berdasarkan potensi mereka untuk digunakan kembali dalam gambar lain dan penggunaan caching yang diharapkan. Jika saya memiliki 4 gambar, semua dengan gambar dasar yang sama (misalnya debian), saya dapat menarik kumpulan utilitas umum ke sebagian besar gambar tersebut ke dalam perintah jalankan pertama sehingga gambar lain mendapat manfaat dari caching.

Urutan di Dockerfile penting ketika melihat penggunaan kembali cache gambar. Saya melihat setiap komponen yang akan memperbarui sangat jarang, mungkin hanya ketika gambar dasar diperbarui dan menempatkan mereka di Dockerfile. Menjelang akhir Dockerfile, saya menyertakan perintah yang akan berjalan cepat dan dapat sering berubah, misalnya menambahkan pengguna dengan host spesifik UID atau membuat folder dan mengubah izin. Jika wadah menyertakan kode yang ditafsirkan (misalnya JavaScript) yang sedang dikembangkan secara aktif, yang akan ditambahkan selambat mungkin sehingga pembangunan kembali hanya menjalankan perubahan tunggal itu.

Di masing-masing kelompok perubahan ini, saya melakukan konsolidasi sebaik mungkin untuk meminimalkan lapisan. Jadi jika ada 4 folder kode sumber yang berbeda, mereka ditempatkan di dalam satu folder sehingga dapat ditambahkan dengan satu perintah. Setiap paket yang diinstal dari sesuatu seperti apt-get digabung ke dalam RUN tunggal jika mungkin untuk meminimalkan jumlah overhead paket manajer (memperbarui dan membersihkan).


Pembaruan untuk bangunan multi-tahap:

Saya kurang khawatir tentang mengurangi ukuran gambar pada tahap non-final dari bangunan multi-tahap. Ketika tahap-tahap ini tidak ditandai dan dikirim ke node lain, Anda dapat memaksimalkan kemungkinan penggunaan kembali cache dengan memisahkan setiap perintah ke RUNbaris yang terpisah .

Namun, ini bukan solusi sempurna untuk menekan lapisan karena semua yang Anda salin di antara tahapan adalah file, dan bukan seluruh meta-data gambar seperti pengaturan variabel lingkungan, titik masuk, dan perintah. Dan ketika Anda menginstal paket dalam distribusi linux, perpustakaan dan dependensi lainnya dapat tersebar di seluruh sistem file, membuat salinan semua dependensi menjadi sulit.

Karena itu, saya menggunakan multi-stage builds sebagai pengganti untuk membangun binari pada server CI / CD, sehingga server CI / CD saya hanya perlu menjalankan tooling docker build, dan tidak memiliki jdk, nodejs, go, dan alat kompilasi lain yang diinstal.

BMitch
sumber
30

Jawaban resmi tercantum dalam praktik terbaik mereka (gambar resmi HARUS mematuhi ini)

Minimalkan jumlah layer

Anda perlu menemukan keseimbangan antara keterbacaan (dan dengan demikian pemeliharaan jangka panjang) dari Dockerfile dan meminimalkan jumlah lapisan yang digunakannya. Jadilah strategis dan berhati-hati dengan jumlah lapisan yang Anda gunakan.

Sejak docker 1.10 COPY, ADDdan RUNpernyataan menambahkan layer baru ke gambar Anda. Berhati-hatilah saat menggunakan pernyataan ini. Cobalah untuk menggabungkan perintah menjadi satu RUNpernyataan. Pisahkan ini hanya jika diperlukan agar mudah dibaca.

Info lebih lanjut: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

Pembaruan: Multi stage di buruh pelabuhan> 17.05

Dengan pembuatan multi-tahap Anda dapat menggunakan beberapa FROMpernyataan di Dockerfile Anda. Setiap FROMpernyataan adalah sebuah panggung dan dapat memiliki gambar dasarnya sendiri. Pada tahap akhir Anda menggunakan gambar dasar minimal seperti alpine, salin artefak bangunan dari tahap sebelumnya dan instal persyaratan runtime. Hasil akhir dari tahap ini adalah gambar Anda. Jadi di sinilah Anda khawatir tentang lapisan seperti yang dijelaskan sebelumnya.

Seperti biasa, buruh pelabuhan memiliki dokumen hebat tentang pembuatan multi-tahap. Berikut kutipan singkatnya:

Dengan pembuatan multi-tahap, Anda menggunakan beberapa pernyataan FROM di Dockerfile Anda. Setiap instruksi FROM dapat menggunakan basis yang berbeda, dan masing-masing dari mereka memulai tahap baru pembangunan. Anda dapat secara selektif menyalin artefak dari satu tahap ke tahap lainnya, meninggalkan semua yang tidak Anda inginkan dalam gambar akhir.

Posting blog yang hebat tentang ini dapat ditemukan di sini: https://blog.alexellis.io/mutli-stage-docker-builds/

Untuk menjawab poin Anda:

  1. Ya, lapisan adalah semacam diff. Saya tidak berpikir ada lapisan yang ditambahkan jika tidak ada perubahan sama sekali. Masalahnya adalah bahwa sekali Anda menginstal / mengunduh sesuatu di lapisan # 2, Anda tidak dapat menghapusnya di lapisan # 3. Jadi begitu sesuatu dituliskan dalam layer, ukuran gambar tidak dapat dikurangi lagi dengan menghapusnya.

  2. Meskipun lapisan dapat ditarik secara paralel, sehingga berpotensi lebih cepat, setiap lapisan tidak diragukan lagi meningkatkan ukuran gambar, bahkan jika mereka menghapus file.

  3. Ya, caching berguna jika Anda memperbarui file buruh pelabuhan Anda. Tapi itu bekerja dalam satu arah. Jika Anda memiliki 10 layer, dan Anda mengubah layer # 6, Anda masih harus membangun kembali semuanya dari layer # 6- # 10. Jadi tidak terlalu sering mempercepat proses build, tetapi dijamin tidak perlu meningkatkan ukuran gambar Anda.


Terima kasih kepada @Mohan yang telah mengingatkan saya untuk memperbarui jawaban ini.

Menzo Wijmenga
sumber
1
Ini sekarang ketinggalan jaman - lihat jawaban di bawah.
Mohan
1
@Mohan terima kasih atas pengingatnya! Saya memperbarui pos untuk membantu pengguna.
Menzo Wijmenga
19

Sepertinya jawaban di atas sudah ketinggalan zaman. Catatan dokumen:

Sebelum Docker 17.05, dan bahkan lebih, sebelum Docker 1.10, penting untuk meminimalkan jumlah lapisan pada gambar Anda. Perbaikan berikut telah memitigasi kebutuhan ini:

[...]

Docker 17.05 dan yang lebih tinggi menambahkan dukungan untuk pembuatan multi-tahap, yang memungkinkan Anda untuk menyalin hanya artefak yang Anda butuhkan ke dalam gambar akhir. Ini memungkinkan Anda untuk memasukkan alat dan informasi debug dalam tahap pembuatan lanjutan Anda tanpa menambah ukuran gambar akhir.

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

dan

Perhatikan bahwa contoh ini juga secara kompres mengompres dua perintah RUN bersama-sama menggunakan operator Bash &&, untuk menghindari membuat lapisan tambahan pada gambar. Ini rawan kegagalan dan sulit dipertahankan.

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

Praktik terbaik tampaknya telah berubah menggunakan multistage build dan menjaga agar tetap Dockerfiledapat dibaca.

Mohan
sumber
Meskipun multistage build tampaknya merupakan opsi yang baik untuk menjaga keseimbangan, perbaikan sebenarnya untuk pertanyaan ini akan muncul ketika docker image build --squashopsi berjalan di luar eksperimental.
Yajo
2
@Yajo - Saya ragu squashmendapatkan hasil eksperimen. Ini memiliki banyak trik dan hanya masuk akal sebelum multi-stage dibangun. Dengan multi stage build, Anda hanya perlu mengoptimalkan tahap akhir yang sangat mudah.
Menzo Wijmenga
1
@Yajo Untuk memperluasnya, hanya lapisan pada tahap terakhir yang membuat perbedaan pada ukuran gambar akhir. Jadi jika Anda meletakkan semua gubbin pembangun Anda di tahap sebelumnya dan memiliki tahap akhir hanya menginstal paket dan menyalin seluruh file dari tahap sebelumnya, semuanya bekerja dengan indah dan squash tidak diperlukan.
Mohan
3

Itu tergantung pada apa yang Anda masukkan dalam lapisan gambar Anda.

Poin kuncinya adalah membagikan sebanyak mungkin layer:

Contoh Buruk:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Contoh yang baik:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Saran lain adalah menghapus tidak begitu berguna hanya jika itu terjadi pada lapisan yang sama dengan tindakan menambahkan / menginstal.

xdays
sumber
Apakah 2 ini benar-benar berbagi RUN yum install big-packagedari cache?
Yajo
Ya, mereka akan berbagi lapisan yang sama, asalkan mereka mulai dari pangkalan yang sama.
Ondra Žižka