İçeriğe geç

Docker Katman Cache ve İmaj Boyutu

Orta 10 dk Sık karşılaşılır

30 saniyede özet

Docker imajı üst üste dizilmiş katmanlardan oluşur ve bir katman değişirse üstündekilerin hepsi yeniden hazırlanır. Dockerfile'daki satırların sırası, build'in 10 saniye mi 100 saniye mi süreceğini bu yüzden belirler.

Tek bir Java dosyası değiştirdin ve paketleme dakikalarca sürdü. Bütün kütüphaneler yeniden indirildi — oysa kütüphane listesine hiç dokunmadın.

  1. Bayt: Bir satır değiştirdim, Docker yine bütün internet'i indiriyor!

  2. Sen: Kütüphane listesini mi değiştirdin?

  3. Bayt: Hayır, pom.xml'e dokunmadım bile. Sadece bir Java dosyası.

  4. Bayt: Kitap yığınının ortasından bir kitap çekmek gibi mi bu?

Dockerfile'da satırların sırası, neyin cache'te kalacağını belirler.
Adım adım oku
  1. Kötü sırada önce bütün kaynak kod kopyalanır, bağımlılıklar ondan sonra indirilir.
  2. Tek bir dosya değişince o kopyalama katmanı ve altındaki her katman yeniden hazırlanır; indirme de baştan yapılır.
  3. İyi sırada önce yalnızca bağımlılık listesi kopyalanır ve bağımlılıklar indirilir, kaynak kod en sona kalır.
  4. Kod değişince yalnızca son katman yeniden hazırlanır; yavaş indirme cache'te bekler.

Tek bir kural

Docker her talimat için bir katmanDockerfile'daki her talimatın ürettiği, yeniden kullanılabilen ara imaj. Bir katman değişince ondan sonraki tüm katmanlar yeniden kurulur.Katmanlar birikimlidir: bir katmanda indirip sonrakinde sildiğin dosya alt katmanda durmaya devam eder ve imaj boyutundan düşmez.Sözlükte gör → üretir ve bir katmanı yeniden kullanır ancak girdileri değişmemişse.

Kritik kısmı şu: bir katman geçersizleşirse, aynı aşamada altındaki her katman da geçersizleşir.

FROM ... ✓ cache
WORKDIR /app ✓ cache
COPY . . ✕ kaynak değişti
RUN ./mvnw package ✕ üstü ıskaladı → bu da ıskalar

Dördüncü satırın girdileri aslında değişmemiş olabilir. Fark etmez — zincir kırıldığı an aşağısı tamamen yeniden çalışır.

Kafam karıştı, daha basit anlat

Docker her adımı bir kat olarak saklar. Bir kat değişirse, üstüne kurulan bütün katlar da yeniden yapılır. Tıpkı alttaki bir tuğlayı değiştirmek gibi.

Kendin gör

Docker katman cache — sıralama neden derleme süresini belirler

Tohum 3
Build bağlamı: 12 MB · 1 sn (henüz gönderilmedi)
  • ·FROM eclipse-temurin:21-jdk
  • ·WORKDIR /app
  • ·COPY . .
  • ·RUN ./mvnw package -DskipTests
  • ·ENTRYPOINT ["java","-jar","target/app.jar"]

Bu build

0 sn

soğuk cache: 103 sn

Cache’ten gelen

2/5

katman

İmaj boyutu

672 MB

çok aşamalı olsa 252 MB

Hız
Adım 0

Şu an ne oldu?

Build bağlamı: 12 MB

Docker, tek bir satır çalıştırmadan önce bağlamın tamamını daemon’a gönderir. Bu yüzden .dockerignore yalnızca imaj boyutunu değil, her build’in başlangıç süresini de etkiler.

Aklında kalsın: .dockerignore’ı imaj temizliği sanmak yaygın bir eksiktir; asıl etkisi bağlam boyutu ve cache geçerliliğidir.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

Şu sırayla dene:

  1. Önce COPY . . + Bir Java dosyası. ~99 saniye. Bağımlılık indirmesi yeniden çalıştı.
  2. Önce pom.xml’e geç. Aynı değişiklik ~29 saniye. Tek fark: iki satırın yeri.
  3. Değişikliği Hiçbir şey yap, .dockerignore anahtarını kapat. Build yine ~107 saniye. Hiçbir şey değişmediği hâlde.
  4. Çok aşamalıya geç. Süre benzer ama imaj 672 MB’tan 252 MB’a düşüyor.
  5. Değişikliği Temel imaj yap. Her şey yeniden çalışıyor — ve bu doğru davranış.
Hızlı kontrolBaşlangıç

Docker katman cache'i ne zaman geçersiz olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sıralama: nadir üste, sık alta

Dockerfile önce COPY . . ile bütün projeyi kopyalıyor, sonra ./mvnw package çalıştırıyor. Tek bir Java dosyasını değiştirip yeniden build ettin. Bağımlılıklar yeniden indirilir mi? Cevabı göster

Evet. Kopyalanan dosyalardan biri değişince o katman ve altındaki bütün katmanlar geçersiz olur; indirme de o katmanların içinde.

Kural, kuralın kendisinden çıkar. Cache’i korumak istiyorsan az değişeni yukarı koy.

Yanlış — her kaynak değişikliği bağımlılıkları da siler
FROM eclipse-temurin:21-jdk
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests
Doğru — bağımlılıklar kendi katmanında
FROM eclipse-temurin:21-jdk
WORKDIR /app
COPY pom.xml .
RUN ./mvnw dependency:go-offline # pom.xml değişmedikçe cache'te kalır
COPY src ./src
RUN ./mvnw package -o -DskipTests

pom.xml ayda birkaç kez değişir, src/ günde onlarca kez. İki satırın yerini değiştirmek build süresini üçte bire indirir.

Kafam karıştı, daha basit anlat

Az değişen şeyleri alt katlara, sık değişen kodunu en üste koy. Böylece kodu her değiştirdiğinde yalnızca en üst kat yeniden yapılır.

Hızlı kontrolOrta

Her Dockerfile satırını, cache dostu bir sırada üstte mi altta mı olması gerektiğine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Üstte olmalı

Nadiren değişir

    Altta olmalı

    Her commit'te değişebilir

      .dockerignore — sanılandan önemli. Simülatördeki 3. adım en sinsi senaryo: hiçbir şey değişmediği hâlde cache hiç tutmuyor.

      Sebep şu: COPY . . tüm dizini kopyalar, target/ de dahil. Yerelde bir kez mvn package çalıştırdıysan target/ değişmiştir, dolayısıyla katman geçersizdir.

      .dockerignore
      target/
      .git/
      .idea/
      *.md

      İki ayrı kazanç var:

      • Cache geçerliliği. Build çıktıları bağlamdan çıkınca katman gereksiz yere geçersizleşmez.
      • Bağlam boyutu. Docker, tek satır çalıştırmadan önce bağlamın tamamını daemon’a gönderir. 240 MB yerine 12 MB göndermek her build’in başına saniyeler ekler ya da çıkarır.
      Hızlı kontrolOrta

      `.dockerignore` dosyası neden önemlidir?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Çok aşamalı build

      Derlemek için JDK, Maven ve tüm bağımlılıklar gerekir. Çalıştırmak için hiçbiri gerekmez.

      FROM eclipse-temurin:21-jdk AS build
      WORKDIR /app
      COPY pom.xml .
      RUN ./mvnw dependency:go-offline
      COPY src ./src
      RUN ./mvnw package -o -DskipTests
      FROM eclipse-temurin:21-jre
      COPY --from=build /app/target/app.jar app.jar
      ENTRYPOINT ["java","-jar","/app.jar"]

      Son FROM’dan sonrası imaja girer; öncesi build makinesinde kalır. 672 MB → 252 MB.

      İkinci, daha az bilinen kazanç: aşamalar ayrı cache zincirleridir. Kaynak kod değiştiğinde çalıştırma aşamasının FROM ...-jre katmanı cache’te kalır, çünkü o değişiklik onun aşamasını ilgilendirmez.

      Boyut neden önemli: her deploy’da registry’den çekilen bayt, her node’da tutulan disk ve saldırı yüzeyi. JRE imajında derleyici yoktur — bir saldırganın kullanabileceği araç da yoktur.

      Bir adım ötesi: layered jar. Spring Boot fat jar’ı tek bir dosyadır, yani tek bir katman. Uygulama kodunun bir satırı değişse 70 MB’lık katmanın tamamı yeniden iner.

      RUN java -Djarmode=layertools -jar app.jar extract
      COPY --from=build /app/dependencies/ ./
      COPY --from=build /app/application/ ./ # yalnızca bu katman değişir

      Bağımlılıklar ve uygulama kodu ayrı katmanlara bölünür. Deploy’da yalnızca birkaç yüz kilobayt aktarılır.

      Hepsini bir arada görmek ister misin? Aşağıda bir bankanın havale servisi için küçük bir proje var: sıralama, çok aşamalı build ve layered jar aynı dosyada.

      Derinleş · Havale servisinin imajı: katman katman 3 dosya · ~43 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      Dockerfile İki aşama: önce bağımlılıklar, sonra kod; son imaja yalnızca JRE ve uygulamanın dört katmanı girer.

      Dockerfile
      # Build stage: JDK, Maven and every library. None of it ships.
      FROM eclipse-temurin:21-jdk AS build
      WORKDIR /app
      COPY mvnw pom.xml ./
      COPY .mvn .mvn
      # Cached until pom.xml changes.
      RUN ./mvnw -q dependency:go-offline
      COPY src ./src
      # Tests run in their own CI job; this stage only packages.
      RUN ./mvnw -q -o package -DskipTests \
      && cp target/transfer-service.jar application.jar \
      && java -Djarmode=tools -jar application.jar extract --layers --destination extracted
      # Run stage: JRE only, with its own cache chain.
      FROM eclipse-temurin:21-jre
      WORKDIR /app
      RUN useradd --system --uid 10001 transfer
      # Rarely changing layers first: libraries, the Boot loader, then our own code.
      COPY --from=build /app/extracted/dependencies/ ./
      COPY --from=build /app/extracted/spring-boot-loader/ ./
      COPY --from=build /app/extracted/snapshot-dependencies/ ./
      COPY --from=build /app/extracted/application/ ./
      USER transfer
      # Core banking credentials arrive at runtime from the secret store, never at build time.
      ENTRYPOINT ["java", "-jar", "application.jar"]

      .dockerignore Build bağlamının dışında kalanlar: çıktılar ve bankanın sandbox şifreleri.

      .dockerignore
      target/
      .git/
      .idea/
      *.md
      # Local credentials for the core banking sandbox: never part of the build context.
      .env
      secrets/
      *.p12

      Dockerfile.simple Şöyle de yazılabilirdi: çalışır, ama her değişiklikte her şeyi baştan indirir ve eline geçen her dosyayı imaja koyar.

      Dockerfile.simple
      # Many first Dockerfiles look like this, and it does work.
      FROM eclipse-temurin:21-jdk
      WORKDIR /app
      # Any edited file breaks the cache here, and without a .dockerignore
      # the .env file with the sandbox credentials lands in this layer too.
      COPY . .
      # So every build downloads all the libraries again.
      RUN ./mvnw package -DskipTests
      # The JDK, Maven and the whole source tree ship to production.
      ENTRYPOINT ["java", "-jar", "target/transfer-service.jar"]
      Kafam karıştı, daha basit anlat

      Derlemek için büyük bir alet çantası gerekir, çalıştırmak için değil. Çok aşamalı build, çantayı atölyede bırakıp yalnızca bitmiş ürünü paketlemektir.

      Hızlı kontrolOrta

      Bir Spring Boot Dockerfile'ında her satırı değişim sıklığına göre yerleştir.

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Sınıflandırılmamış

      Üste — nadiren değişir

      Cache'te kalması en değerli olanlar

        Alta — sık değişir

        Her geçersizleşmesi ucuz olmalı

          Tuzaklar ve CI’da cache

          Yerelde çalışan cache, CI’da çoğu zaman çalışmaz — her job temiz bir makinede başlar ve yerel katman deposu boştur.

          Çözüm, cache’i registry üzerinden paylaşmaktır:

          Terminal window
          docker buildx build \
          --cache-from type=registry,ref=myapp:buildcache \
          --cache-to type=registry,ref=myapp:buildcache,mode=max .

          Bu satır olmadan yukarıdaki tüm sıralama çabası CI’da hiçbir işe yaramaz — yerelde kazandığın süreyi orada geri verirsin.

          Kendini sına

          Şimşek turu1/5

          Bir katman değişince aynı aşamada ondan sonraki bütün katmanlar da yeniden hazırlanır.

          Soru 1/3Orta

          Bu Dockerfile'da tek bir Java dosyası değiştiğinde ne yeniden çalışır?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Aklında kalacak üç şey

          1. 1 Bir katman değişirse, aynı aşamada ondan sonra gelen her katman da yeniden hazırlanır. Satır sırasının önemi buradan gelir.
          2. 2 Az değişen şey üste, sık değişen şey alta. Bağımlılık listesi kaynak koddan çok daha az değişir, bu yüzden önce o kopyalanır.
          3. 3 Çok aşamalı build iki işe yarar: derleme araçları son imajdan çıkar ve çalıştırma aşamasının cache'i derleme aşamasından bağımsızlaşır.
          Sonraki kapı Tek bir controller'ı test etmek için neden bütün uygulamayı ayağa kaldırasın? Spring Test Dilimleri ve Context Cache · 10 dk

          5 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı

          Bu dersin üstüne kurulanlar

          Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.