Soru bankası · Operasyon

Docker ve container'lar

Container'ın ne olduğundan Spring Boot için production'a uygun bir Dockerfile'a, compose ile yerel ortamdan sinyal, bellek ve güvenlik tuzaklarına kadar Docker'ı mülakatta sistematik anlatmayı öğretir.

23 soru15 çoktan seçmeli
Konu özeti

Container nedir, VM'den farkı ne?

Container, kendi dosya sistemi, ağı ve süreç ağacı varmış gibi çalışan sıradan bir Linux sürecidir. Bu izolasyonu iki kernel özelliği sağlar:

  • Namespace'ler sürecin ne gördüğünü sınırlar: pid (kendi süreç ağacı, kendisi PID 1), net (kendi ağ arayüzü ve port'ları), mnt (kendi dosya sistemi görünümü), uts (hostname), ipc, user (container içindeki root'un host'ta başka bir kullanıcıya eşlenmesi).
  • cgroup'lar sürecin ne kadar kullandığını sınırlar: bellek, CPU, süreç sayısı (pids), I/O.

VM ise hypervisor üzerinde kendi kernel'iyle açılan tam bir işletim sistemidir. Fark buradan çıkar: container'lar host kernel'ini paylaşır, bu yüzden saniyeden kısa sürede açılır, megabaytlarla ölçülür ve bir makinede yüzlercesi çalışır. Bedeli, izolasyonun daha zayıf olmasıdır: kernel açığı tüm container'ları etkiler ve Linux container'ı Linux kernel'i ister. Mac ve Windows'taki Docker Desktop bu yüzden arkada küçük bir Linux VM çalıştırır.

Image, container, layer

Image, salt okunur katmanlardan (layer) ve çalıştırma metadata'sından (ENTRYPOINT, ENV, kullanıcı, port) oluşan bir pakettir. Container, bir image'ın çalışan örneğidir: image'ın katmanları + üstte container'a özel yazılabilir bir katman + süreç. Aynı image'dan on container açılırsa alttaki katmanlar diskte bir kez durur (overlay dosya sistemi, copy-on-write). Container silinince yazılabilir katman da gider; kalıcı veri volume'a yazılır.

Dockerfile'daki her dosya sistemi değiştiren talimat (RUN, COPY, ADD) bir katman üretir. Build sırasında BuildKit her talimat için cache'e bakar: COPY için kopyalanan dosyaların içerik checksum'ı (mtime değil), RUN için sadece komut metni karşılaştırılır. Bir talimatın cache'i bozulursa ondan sonraki tüm talimatlar yeniden çalışır. Pratik kural: az değişen şeyi üste, sık değişeni alta koy. Java için bu, önce pom.xml ve bağımlılık indirme, sonra src kopyalama demektir.

Temel Dockerfile talimatları

TalimatNe yapar
FROMTaban image; AS name ile bir build aşamasına isim verir
WORKDIRSonraki talimatların çalışma dizini (yoksa oluşturur)
COPY / ADDBuild context'ten dosya kopyalar; ADD ayrıca URL ve arşiv açma bilir, genelde COPY tercih edilir
RUNBuild sırasında komut çalıştırır, sonucu katman olur
ENV / ARGÇalışma zamanı ortam değişkeni / sadece build zamanı değişkeni
USERSonraki talimatların ve container sürecinin kullanıcısı
EXPOSEBelgeleme; port'u dışarı açmaz
ENTRYPOINT / CMDContainer açılınca çalışan komut / varsayılan argümanlar
HEALTHCHECKDocker'ın container sağlığını yoklaması (Kubernetes bunu yok sayar, kendi probe'larını kullanır)

ENTRYPOINT sabit çalıştırılabilir dosyayı, CMD ona verilen ve docker run image <args> ile ezilebilen varsayılan argümanları tanımlar. İkisi de exec form (JSON dizi) ile yazılmalıdır. Shell form (ENTRYPOINT java -jar app.jar) komutu /bin/sh -c altında çalıştırır; PID 1 shell olur, docker stop'un gönderdiği SIGTERM Java'ya ulaşmaz, Docker varsayılan 10 saniye bekleyip SIGKILL gönderir ve graceful shutdown hiç çalışmaz. Kubernetes'teki karşılığı ve preStop için bkz. Kitap Bölüm 15.

Spring Boot için multi-stage Dockerfile

Multi-stage build'de derleme bir aşamada (JDK, Maven, kaynak kod) yapılır, son image'a sadece çalışmak için gerekenler (JRE ve uygulama) kopyalanır. Derleme araçları ve kaynak kod son image'a girmez.

Spring Boot'un fat jar'ı katmanlı olarak açılabilir: bağımlılıklar, Spring Boot loader, snapshot bağımlılıklar ve uygulama kodu ayrı dizinlere çıkar, her biri ayrı bir Docker katmanı olur. Bir satır kod değiştiğinde sadece birkaç yüz KB'lık application katmanı yeniden üretilir ve registry'ye itilir; yüz MB'lık bağımlılık katmanı cache'ten gelir.

Sürüm farkına dikkat: Spring Boot 3.3'e kadar resmi yol java -Djarmode=layertools -jar app.jar extract idi ve image org.springframework.boot.loader.launch.JarLauncher (3.2 öncesinde org.springframework.boot.loader.JarLauncher) ile başlatılıyordu. 3.3 ile yeni tools jar mode'u geldi ve layertools deprecated oldu; güncel dokümanda sadece java -Djarmode=tools -jar app.jar extract --layers --destination extracted yolu anlatılıyor ve çıkan dizindeki ince jar java -jar ile çalıştırılıyor. 4.x serisinde layertools'un kaldırıldığı bildiriliyor; kullandığın sürümün dokümanına bakmadan komutu ezberden yazma.

dockerfile
# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace
# Önce az değişen dosyalar: bağımlılık katmanı cache'te kalsın
COPY mvnw pom.xml ./
COPY .mvn/ .mvn/
RUN --mount=type=cache,target=/root/.m2 ./mvnw -B -q dependency:go-offline
COPY src/ src/
# Testler CI'da image build'inden önce koşar
RUN --mount=type=cache,target=/root/.m2 ./mvnw -B -q package -DskipTests \
    && cp target/*.jar application.jar \
    && java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM eclipse-temurin:21-jre
WORKDIR /app
RUN groupadd --system --gid 10001 app \
    && useradd --system --uid 10001 --gid app --no-create-home --shell /usr/sbin/nologin app
# Dosyalar root'a ait ve salt okunur kalır; uygulama kendi kodunu değiştiremez
COPY --from=build /workspace/extracted/dependencies/ ./
COPY --from=build /workspace/extracted/spring-boot-loader/ ./
COPY --from=build /workspace/extracted/snapshot-dependencies/ ./
COPY --from=build /workspace/extracted/application/ ./
USER 10001:10001
EXPOSE 8080
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70 -XX:+ExitOnOutOfMemoryError"
ENTRYPOINT ["java", "-jar", "application.jar"]

Notlar: USER'ın sayısal verilmesi Kubernetes'in runAsNonRoot kontrolünün kullanıcıyı doğrulayabilmesini sağlar. Gradle'da COPY satırları gradlew, settings.gradle(.kts), build.gradle(.kts) ve gradle/ olur, jar build/libs/ altına düşer; Spring Boot Gradle eklentisi yanına bir de -plain.jar ürettiği için build/libs/*.jar glob'u iki dosyaya eşleşebilir, dosya adını açık ver ya da jar görevini kapat. MaxRAMPercentage değerinin neden 70 civarı olduğu ve heap dışı bellek bütçesi için bkz. Kitap Bölüm 3.

Alternatif: Dockerfile hiç yazmamak. ./mvnw spring-boot:build-image ya da ./gradlew bootBuildImage, Cloud Native Buildpacks (Paketo) ile katmanlı, root olmayan kullanıcıyla çalışan ve JVM bellek ayarını container limitine göre hesaplayan bir image üretir. Bedeli, image içeriği üzerinde daha az kontrol ve build makinesinde bir Docker daemon ihtiyacıdır. Jib de daemon gerektirmeden image üretir.

Build context ve .dockerignore

docker build . komutundaki . build context'tir: COPY'nin görebildiği her şey buradan gelir. .dockerignore olmadan .git, target/, IDE dosyaları ve yanlışlıkla .env dosyası da builder'a gönderilir; build yavaşlar, COPY . . kullanılıyorsa cache gereksiz bozulur ve secret image'a girebilir.

text
.git
.idea
*.iml
target/
build/
.gradle/
.env
*.log

Volume, bind mount, network

  • Named volume (-v pgdata:/var/lib/postgresql/data): Docker'ın yönettiği alan; container silinse de kalır. Veritabanı verisi için doğru seçim.
  • Bind mount (-v ./config:/app/config): host'taki bir dizini olduğu gibi bağlar. Geliştirmede kod ya da konfigürasyon için kullanışlı, ama host'un dizin yapısına ve dosya izinlerine bağımlıdır.

Ağda varsayılan sürücü bridge'dir. -p 8080:8080 host'un 8080 port'unu container'ın 8080 port'una yönlendirir (sol taraf host, sağ taraf container). Kullanıcı tanımlı bir bridge ağında (compose her proje için otomatik bir tane kurar) container'lar birbirini servis adıyla bulur; Docker'ın gömülü DNS'i postgres adını o container'ın IP'sine çözer. Container içinden localhost, container'ın kendisidir; host ya da başka bir container değildir. Container'lar arası trafik container port'una gider; host port'u sadece dışarıdan erişim içindir.

Compose ile yerel ortam

yaml
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/orders
      SPRING_DATASOURCE_USERNAME: orders
      SPRING_DATASOURCE_PASSWORD: orders
      SPRING_DATA_REDIS_HOST: redis
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    stop_grace_period: 40s

  postgres:
    image: postgres:17
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: orders
      POSTGRES_PASSWORD: orders
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s

  redis:
    image: redis:7
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  pgdata:

depends_on tek başına sadece başlatma sırasını belirler; Postgres container'ı başladı diye bağlantı kabul ediyor değildir. condition: service_healthy, bağımlılığın healthcheck'i geçmesini bekler (diğer değerler service_started ve service_completed_successfully). $$, compose'un değişkeni kendisi genişletmemesi ve container içindeki shell'e bırakması içindir. Dosyanın başına version: yazmak artık gereksizdir. Postgres 18 ve sonrası image'larında veri dizini düzeni değişti; büyük sürüm atlarken mount yolunu image dokümanından kontrol et. Spring Boot 3.1'den beri spring-boot-docker-compose modülü, uygulama açılırken compose.yaml'ı kendisi başlatıp bağlantı bilgilerini otomatik verebilir.

Image boyutu, güvenlik, registry

  • Taban image: JDK yerine JRE; -alpine varyantları küçüktür ama glibc yerine musl kullanır (native kütüphanelerde ve DNS davranışında sürpriz olabilir). Distroless image'larda shell ve paket yöneticisi yoktur: saldırı yüzeyi ve CVE sayısı düşer, ama docker exec ... sh ile içeri girip bakamazsın. jlink ile sadece gereken modülleri içeren bir runtime da üretilebilir.
  • Root olmayan kullanıcı: Container içindeki root, user namespace kullanılmıyorsa host'taki root ile aynı UID'dir; bir kaçış açığında etkisi büyür.
  • Secret'lar image'a girmez. ARG ve ENV değerleri image metadata'sında ve geçmişinde kalır; sonraki bir katmanda silinen dosya önceki katmanda durur. Build sırasında gereken token için BuildKit secret mount (RUN --mount=type=secret,...), çalışma zamanı secret'ları için ortamın secret mekanizması (Kubernetes Secret, Vault) kullanılır.
  • Tarama: Trivy, Grype ya da Docker Scout ile CI'da image taranır; taban image düzenli güncellenmezse dün temiz olan image bugün kritik CVE içerir.
  • Tag'leme: latest sadece bir etikettir, "en yeni" garantisi vermez ve yerinden oynar. Production'da değişmez tag'ler (1.4.3, 1.4.3-a1b2c3d gibi git SHA'lı) ya da doğrudan digest (image@sha256:...) kullanılır. Staging'de test edilen digest'in aynısı production'a gider (build once, deploy many; bkz. Kitap Bölüm 15).

Sorular (23)

01BaşlangıçTahminicontainervmnamespacecgroup

Container nedir, sanal makineden (VM) farkı nedir?

02BaşlangıçTahminiimagecontainerlayer

Image ile container arasındaki fark nedir?

03BaşlangıçTahminidockerfile

Dockerfile nedir, temel talimatları nelerdir?

04BaşlangıçTahmininetworkport

docker run -p 8080:8080 ne yapar? EXPOSE ile farkı nedir?

05BaşlangıçTahminivolumebind-mountstorage

Volume ile bind mount arasındaki fark nedir?

06BaşlangıçTahminicompose

Docker Compose ne işe yarar?

07BaşlangıçTahmininetworkdnscompose

Compose'da uygulama container'ı veritabanına neden localhost ile bağlanamaz?

08BaşlangıçTahminitagregistryimage

latest tag'ini kullanmanın sakıncası nedir?

09BaşlangıçTahminiloggingstdout

Container'ın log'ları nereye yazılmalı, nasıl okunur?

10BaşlangıçTahminipid1troubleshootinglifecycle

Container başlar başlamaz neden kapanıyor?

11OrtaTahminilayercachebuild

Layer cache nasıl çalışır? Dockerfile'da talimat sırası neden önemli?

12OrtaTahminimulti-stagedockerfilespring-boot

Multi-stage build nedir? Spring Boot için neden kullanılır?

13OrtaTahminientrypointcmddockerfile

ENTRYPOINT ile CMD arasındaki fark nedir?

14OrtaTahminidockerfilespring-bootlayered-jarbuildpacks

Spring Boot uygulaması için iyi bir Dockerfile nasıl yazarsın?

15OrtaTahminibuild-contextdockerignoresecurity

Build context nedir? .dockerignore neden gerekir?

16OrtaTahminisecuritynon-rootuser

Container neden root kullanıcıyla çalışmamalı?

17OrtaTahminicomposehealthcheckdepends-on

Uygulama veritabanından önce açılıp bağlantı hatasıyla çöküyor. depends_on ve healthcheck ile nasıl çözersin?

18OrtaTahminiimage-sizealpinedistrolessjlink

Image boyutunu nasıl küçültürsün? Alpine ve distroless arasında nasıl seçersin?

19İleriTahminisigtermpid1graceful-shutdownentrypoint

docker stop sonrası uygulama 10 saniye bekleyip ölüyor, graceful shutdown çalışmıyor. Neden?

20İleriTahminijvmmemorycgroupoomkilled

JVM container'da bellek limitini nasıl görür? Container neden OOMKilled oluyor?

21İleriTahminisecuritysecretsbuildkitbuild-arg

Build sırasında özel bir Maven deposuna token gerekiyor. Secret'ı image'a sızdırmadan nasıl verirsin?

22İleriTahminikubernetescontainerdocicri

Kubernetes artık Docker kullanmıyor deniyor. Docker ile build ettiğin image Kubernetes'te nasıl çalışıyor?

23İleriTahminisecurityscanningsbomtagging

CI'da image güvenliğini nasıl sağlarsın? Tarama, tag'leme ve tekrarlanabilir build

Çoktan seçmeli (15)

01Başlangıç

Container ile sanal makine (VM) arasındaki temel fark hangisidir?

Tek doğru cevap var.

02Başlangıç

Çalışan bir container içinde /app/config.yml dosyasını değiştirip container'ı silersen ne olur?

Tek doğru cevap var.

03Başlangıç

docker run -p 9090:8080 order-service komutu için hangisi doğrudur?

Tek doğru cevap var.

04Başlangıç

Dockerfile'daki EXPOSE 8080 talimatı ne yapar?

Tek doğru cevap var.

05Başlangıç

Compose dosyasında app ve postgres adlı iki servis var; postgres ports: ["15432:5432"] ile publish edilmiş. app container'ı veritabanına hangi URL ile bağlanmalı?

Tek doğru cevap var.

06Başlangıç

Container içindeki bir Spring Boot uygulaması log'larını nereye yazmalıdır?

Tek doğru cevap var.

07Başlangıç

docker ps -a çıktısında container'ın Exited (137) ile durduğunu görüyorsun. 137 neyi gösterir?

Tek doğru cevap var.

08Orta

Bir Maven projesi için hangi Dockerfile sırası, sadece Java kodu değiştiğinde bağımlılıkların yeniden indirilmesini önler?

Tek doğru cevap var.

09Orta

Dockerfile'da ENTRYPOINT ["java", "-jar", "app.jar"] ve CMD ["--spring.profiles.active=prod"] var. docker run image --server.port=9090 çalıştırılınca hangi komut çalışır?

Tek doğru cevap var.

10Orta

Compose'da depends_on: [postgres] (kısa yazım) neyi garanti eder?

Tek doğru cevap var.

11Orta

Multi-stage bir Dockerfile'da son aşama FROM eclipse-temurin:21-jre ile başlıyor ve build aşamasından sadece katmanlara ayrılmış jar içeriği kopyalanıyor. Son image'da ne bulunmaz?

Tek doğru cevap var.

12İleri

ENTRYPOINT java -jar /app/app.jar (shell form) kullanılan bir container'da server.shutdown=graceful açık. docker stop sonrası 10 saniye beklenip container 137 ile kapanıyor. En olası neden nedir?

Tek doğru cevap var.

13İleri

1 GiB bellek limiti olan bir container'da JVM'e -Xmx1g verilmiş. Yük altında en olası sonuç nedir?

Tek doğru cevap var.

14İleri

Aşağıdakilerden hangileri bir build secret'ını image'a ya da image geçmişine sızdırabilir? (Birden fazla doğru var.)

Birden fazla doğru olabilir.

15İleri

Kubernetes 1.24 ile dockershim kaldırıldı. Docker ile build edilip registry'ye push edilmiş bir Spring Boot image'ı için hangisi doğrudur?

Tek doğru cevap var.

Kaynaklar