Dockerfile'da çoklu RUN'a karşılık tek zincirli RUN, hangisi daha iyi?


133

Dockerfile.1birden çok 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 onlara katılır:

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

Her biri RUNbir katman oluşturur, bu yüzden her zaman daha az katmanın daha iyi ve dolayısıyla daha iyi olduğunu varsaydım Dockerfile.2.

Bu, RUNbir önceki RUN(yani yum install nano && yum clean all) tarafından eklenen bir şeyi kaldırdığında açıkça doğrudur , ancak her birinin bir şey eklediğinde RUN, dikkate almamız gereken birkaç nokta vardır:

  1. Katmanların sadece öncekinin üzerine bir fark eklemesi gerekir, bu nedenle sonraki katman bir öncekine eklenen bir şeyi kaldırmazsa, her iki yöntem arasında çok fazla disk alanı tasarrufu avantajı olmamalıdır ...

  2. Katmanlar Docker Hub'dan paralel olarak çekilir, bu nedenle Dockerfile.1muhtemelen biraz daha büyük olmasına rağmen teorik olarak daha hızlı indirilir.

  3. 4. bir cümle (yani echo This is the D > d) ekleyip yerel olarak yeniden inşa ediyorsanız , Dockerfile.1önbellek sayesinde daha hızlı inşa edilir, ancak Dockerfile.24 komutun hepsini yeniden çalıştırmanız gerekir.

Öyleyse soru şu: Dockerfile yapmanın daha iyi bir yolu hangisidir?


1
Duruma ve görüntünün kullanımına bağlı olduğu için genel olarak yanıtlanamaz (boyut, indirme hızı veya bina hızı için optimize edin)
Henry

Yanıtlar:


101

Mümkün olduğunda, her zaman aynı dosyaları tek bir RUNsatırda silen komutlara sahip dosyalar oluşturan komutları birleştiririm . Bunun nedeni, her RUNsatırın görüntüye bir katman eklemesidir, çıktının tam anlamıyla görüntüleyebileceğiniz dosya sistemi değişiklikleridir.docker diff oluşturduğu geçici kapsayıcıda . Farklı bir katmanda oluşturulmuş bir dosyayı silerseniz, tüm birleşim dosya sistemi, dosya sistemi değişikliğini yeni bir katmanda kaydetmektir, dosya hala önceki katmanda bulunur ve ağ üzerinden gönderilir ve diskte depolanır. Dolayısıyla, kaynak kodunu indirirseniz, çıkartırsanız, bir ikili dosyada derlerseniz ve ardından tgz ve kaynak dosyalarını silerseniz, tüm bunların görüntü boyutunu küçültmek için gerçekten tek bir katmanda yapılmasını istersiniz.

Daha sonra, diğer görüntülerde yeniden kullanım potansiyeline ve beklenen önbelleğe alma kullanımına göre kişisel olarak katmanları ayırıyorum. Tümü aynı temel görüntüye sahip 4 görüntüm varsa (örn. Debian), ilk çalıştırma komutuna bu görüntülerin çoğuna ortak yardımcı programların bir koleksiyonunu çekebilirim, böylece diğer görüntüler önbelleğe alma işleminden yararlanır.

Dockerfile'da sipariş, görüntü önbelleğinin yeniden kullanımına bakıldığında önemlidir. Çok nadiren güncellenecek bileşenlere bakıyorum, muhtemelen yalnızca temel görüntü güncellendiğinde ve bunları Dockerfile'da üst sıralara yerleştirdiğinde. Dockerfile'ın sonuna doğru, hızlı çalışacak ve sık sık değişebilecek tüm komutları ekliyorum, örneğin, ana bilgisayara özel UID'ye sahip bir kullanıcı eklemek veya klasörler oluşturmak ve izinleri değiştirmek gibi. Kap aktif olarak geliştirilmekte olan yorumlanmış kod (örneğin JavaScript) içeriyorsa, bu mümkün olduğunca geç eklenir, böylece bir yeniden oluşturma yalnızca o tek değişikliği çalıştırır.

Bu değişiklik gruplarının her birinde, katmanları en aza indirmek için elimden geldiğince pekiştiriyorum. Yani 4 farklı kaynak kodu klasörü varsa, bunlar tek bir klasöre yerleştirilir, böylece tek bir komutla eklenebilir. Apt-get gibi bir şeyden yapılan tüm paket kurulumları, paket yöneticisi ek yükünü (güncelleme ve temizleme) en aza indirmek için mümkün olduğunda tek bir ÇALIŞTIRMA olarak birleştirilir.


Çok aşamalı derlemeler için güncelleme:

Çok aşamalı bir yapının son olmayan aşamalarında görüntü boyutunu küçültme konusunda daha az endişeleniyorum. Bu aşamalar etiketlenip diğer düğümlere gönderilmediğinde, her komutu ayrı bir RUNsatıra bölerek önbelleğin yeniden kullanılması olasılığını en üst düzeye çıkarabilirsiniz .

Ancak bu, katmanları ezmek için mükemmel bir çözüm değildir çünkü aşamalar arasında kopyaladığınız her şey dosyalardır ve ortam değişkeni ayarları, giriş noktası ve komut gibi görüntü meta verilerinin geri kalanı değil. Ve bir linux dağıtımına paketleri kurduğunuzda, kitaplıklar ve diğer bağımlılıklar dosya sistemi boyunca dağılabilir ve bu da tüm bağımlılıkların bir kopyasını zorlaştırır.

Bu nedenle, bir CI / CD sunucusunda ikili dosyalar oluşturmanın yerine çok aşamalı yapıları kullanıyorum, böylece CI / CD sunucumun yalnızca çalıştırmak için docker buildaraçlara sahip olması ve bir jdk, nodejs, go ve kurulu diğer derleme araçları.


30

En iyi uygulamalarında listelenen resmi cevaplar (resmi görseller bunlara UYMALIDIR)

Katman sayısını en aza indirin

Dockerfile'ın okunabilirliği (ve dolayısıyla uzun vadeli sürdürülebilirliği) ile kullandığı katman sayısını en aza indirmek arasındaki dengeyi bulmanız gerekir. Kullandığınız katman sayısı konusunda stratejik ve dikkatli olun.

Docker 1.10'dan beri COPY, ADDve RUNifadeleri görüntünüze yeni bir katman ekler. Bu ifadeleri kullanırken dikkatli olun. Komutları tek bir RUNifadede birleştirmeyi deneyin . Bunu yalnızca okunabilirlik için gerekliyse ayırın.

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

Güncelleme: Docker'da çok aşamalı> 17.05

Çok aşamalı derlemelerle FROMDockerfile'ınızda birden çok deyim kullanabilirsiniz . Her FROMifade bir aşamadır ve kendi temel görüntüsüne sahip olabilir. Son aşamada, alpine gibi minimal bir temel görüntü kullanırsınız, önceki aşamalardan yapı eserlerini kopyalayıp çalışma zamanı gereksinimlerini yüklersiniz. Bu aşamanın sonucu, sizin imajınızdır. Yani, daha önce anlatıldığı gibi katmanlar için endişeleneceğiniz yer burasıdır.

Her zamanki gibi, docker çok aşamalı derlemelerde harika belgeler içerir . İşte hızlı bir alıntı:

Çok aşamalı derlemelerle, Dockerfile'ınızda birden çok FROM ifadesi kullanırsınız. Her bir FROM talimatı farklı bir temel kullanabilir ve her biri yapının yeni bir aşamasına başlar. Artefaktları bir aşamadan diğerine seçerek kopyalayabilir ve istemediğiniz her şeyi son görüntüde bırakabilirsiniz.

Bununla ilgili harika bir blog yazısı burada bulunabilir: https://blog.alexellis.io/mutli-stage-docker-builds/

Puanlarınıza cevap vermek için:

  1. Evet, katmanlar bir çeşit fark gibidir. Kesinlikle sıfır değişiklik varsa katmanlar eklendiğini sanmıyorum. Sorun şu ki, 2. katmanda bir şey yükledikten / indirdikten sonra, onu 3. katmanda kaldıramazsınız. Yani bir katmana bir şey yazıldığında, görüntü boyutu artık kaldırılarak küçültülemez.

  2. Katmanlar paralel olarak çekilip potansiyel olarak daha hızlı hale getirilebilse de, her katman, dosyaları kaldırsalar bile şüphesiz görüntü boyutunu artırır.

  3. Evet, docker dosyanızı güncelliyorsanız önbelleğe alma yararlıdır. Ama tek yönde çalışıyor. 10 katmanınız varsa ve 6. katmanı değiştirirseniz, yine de 6. katmandan her şeyi yeniden oluşturmanız gerekir. Bu nedenle, oluşturma sürecini hızlandırması çok sık değildir, ancak görüntünüzün boyutunu gereksiz yere artıracağı garanti edilir.


Bu cevabı güncellememi hatırlattığı için @Mohan'a teşekkürler .


1
Bu artık modası geçmiş - aşağıdaki yanıta bakın.
Mohan

1
@Mohan hatırlatma için teşekkürler! Kullanıcılara yardımcı olmak için gönderiyi güncelledim.
Menzo Wijmenga

19

Görünüşe göre yukarıdaki cevaplar güncelliğini yitirmiş. Belgelerin notu:

Docker 17.05'ten ve daha da fazlası, Docker 1.10'dan önce, görüntünüzdeki katman sayısını en aza indirmek önemliydi. Aşağıdaki iyileştirmeler bu ihtiyacı azaltmıştır:

[...]

Docker 17.05 ve üzeri, çok aşamalı yapılar için destek ekler, bu da yalnızca ihtiyacınız olan yapıları son görüntüye kopyalamanıza olanak tanır. Bu, son görüntünün boyutunu artırmadan ara oluşturma aşamalarınıza araçları ve hata ayıklama bilgilerini eklemenize olanak tanır.

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

ve

Bu örneğin, görüntüde ek bir katman oluşturmaktan kaçınmak için ayrıca Bash && operatörünü kullanarak iki RUN komutunu yapay olarak sıkıştırdığına dikkat edin. Bu, başarısızlığa meyillidir ve sürdürülmesi zordur.

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

En iyi uygulama, çok aşamalı yapıları kullanmaya ve sayfaları Dockerfileokunabilir tutmaya dönüşmüş görünüyor .


Çok aşamalı yapılar dengeyi korumak için iyi bir seçenek gibi görünse de, bu sorunun asıl çözümü, docker image build --squashseçenek deneyselliğin dışına çıktığında gelecektir .
Yajo

2
@Yajo - squashGeçmiş deneysel olma konusunda şüpheliyim . Birçok hile var ve yalnızca çok aşamalı yapılardan önce mantıklıydı. Çok aşamalı yapılarda yalnızca son aşamayı optimize etmeniz gerekir ki bu çok kolaydır.
Menzo Wijmenga

1
@Yajo Bunu genişletmek için, yalnızca son aşamadaki katmanlar son görüntünün boyutunda herhangi bir fark yaratır. Bu nedenle, tüm oluşturucu gubbinlerinizi daha önceki aşamalara yerleştirirseniz ve son aşamaya sahipseniz, yalnızca paketleri yükleyip önceki aşamalardan dosyalar arasında kopyalama yaparsanız, her şey güzel çalışır ve squash gerekmez.
Mohan

3

Görüntü katmanlarınıza ne eklediğinize bağlıdır.

Kilit nokta, olabildiğince çok katmanı paylaşmaktır:

Kötü örnek:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

İyi örnek:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Başka bir öneri de silme, yalnızca ekleme / yükleme eylemiyle aynı katmanda gerçekleşirse çok kullanışlı değildir.


Bu 2 gerçekten RUN yum install big-packageönbellekten paylaşır mı?
Yajo

Evet, aynı tabandan başladıkları sürece aynı katmanı paylaşacaklardı.
Ondra Žižka
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.