Skip to content

Bölüm 6 / 13

Konteyner nedir

VM'den farkı, imaj, konteyner, katman.

18 dakikalık okuma

Konteyner, "bende çalışıyor" sorununu ortadan kaldıran mekanizmadır: uygulamayı çalışması için gereken her şeyle (çalışma zamanı, kütüphaneler, dosya düzeni) tek bir imaj olarak paketler ve her ortamda aynı biçimde başlatır. Bu ders komut dersi değil kavram dersidir — komutlar 7. derste. Burada üç soruyu net cevaplarız: konteyner nedir (sanal makine değil: izole bir süreç — namespace, cgroup, katmanlı dosya sistemi), imaj ile konteyner arasındaki fark nedir ve katmanlar nasıl çalışır, hangi araç ne yapar (Docker, containerd, Podman, OCI). 1. dersteki "çekirdek paylaşılır" cümlesi burada somutlaşır; 16-01'in nesne deposu ile imaj katmanlarının şaşırtıcı benzerliği kurulur; 15-06'da Testcontainers için verilen söz de burada ödenir.

01Ne öğreneceksin

  • Konteynerin çözdüğü sorun: ortam farkı, "bende çalışıyor", bağımlılık çakışması
  • Konteyner = izole süreç: namespace (ne görüyor), cgroup (ne kadar kullanıyor), katmanlı dosya sistemi (ne okuyor)
  • VM ↔ konteyner karşılaştırması ve "hangi durumda hangisi" kararı
  • İmaj ↔ konteyner ↔ katman: salt okunur katmanlar + yazılabilir üst katman, copy-on-write, verinin neden kaybolduğu
  • İçerik adresli katman ve digest: Git'in nesne deposuyla birebir koşutluk, katman paylaşımı
  • OCI standardı; Docker ↔ containerd ↔ Podman; Docker Desktop ↔ Engine; Linux çekirdeği şartı
  • Testcontainers'ın altındaki Docker; temel imaj seçimi (-alpine ↔ -slim ↔ tam) ve musl/glibc tuzağı

02Çözülen sorun: "bende çalışıyor"

Bir uygulamayı çalıştırmak, kodu kopyalamaktan fazlasıdır: doğru çalışma zamanı sürümü (.NET 8, Node 20), doğru yerel kütüphaneler, doğru dosya düzeni, doğru ortam değişkenleri ve doğru işletim sistemi paketleri gerekir. Bu kümenin adı çalışma ortamıdır ve geleneksel yayında her ortamda elle kurulur. Sonuç dört tanıdık arızadır:

  • Ortam farkı. Geliştirme makinesinde .NET 8.0.8, sunucuda 8.0.3; testte çalışan sorgu üretimde başka davranır. 14-11'in "geliştirme / test / üretim ortamları ayrılır ve aynı biçimde kurulur" maddesi elle kurulumla güvenilir biçimde sağlanamaz.
  • Bağımlılık çakışması. Aynı sunucuda biri Node 18, öbürü Node 22 isteyen iki uygulama; biri OpenSSL 1.1, öbürü 3.0 bekleyen iki kütüphane.
  • Kurulum belgesinin bayatlaması. "Sunucu kurulum adımları" dokümanı altı ayda yalan söyler (16-11'in bayat doküman uyarısı).
  • Yeni gelenin ilk günü. Projeyi çalıştırmak için PostgreSQL, Redis ve üç servis kurmak gerekiyorsa ilk gün kaybolur.

Konteyner bu sorunu paketlemeyi değiştirerek çözer: uygulama ve ihtiyaç duyduğu bütün kullanıcı alanı (çalışma zamanı, kütüphaneler, dosyalar) tek bir imaj içinde, sürümlü ve değişmez (immutable) biçimde saklanır. Aynı imaj geliştirme makinesinde, CI'da ve üretimde çalışır; aradaki tek fark ortam değişkenleri ve bağlanan verilerdir. Bu, 14-11'in ortam ayrımı maddesini mekanizmayla garanti eder ve 18. hattaki "bir kez derle, her yere dağıt" ilkesinin taşıyıcısı olur.

Dikkat edilecek nokta: konteyner çekirdeği paketlemez. Paketlenen şey kullanıcı alanıdır; çekirdek hostun çekirdeğidir (1. ders). Bu, hem konteynerin hafifliğinin hem de izolasyon sınırının nedenidir.

03Konteyner = izole süreç: namespace, cgroup, katman

Bir konteyner, hostta ps aux ile görülebilen sıradan bir süreçtir; onu "konteyner" yapan şey çekirdeğin ona kısıtlı bir dünya göstermesidir. Üç mekanizma:

MekanizmaSoruNe yapar
namespaceSüreç ne görüyor?PID, ağ, bağlama (mount), hostname (UTS), IPC, kullanıcı ve cgroup görünümlerini ayırır
cgroupSüreç ne kadar kullanabiliyor?CPU, bellek, disk G/Ç, süreç sayısı sınırları — 4. dersteki MemoryMax ile aynı altyapı
katmanlı dosya sistemiSüreç hangi dosyaları görüyor?overlayfs ile birleştirilmiş salt okunur katmanlar + yazılabilir üst katman (05. bölüm)

Namespace'lerin somut sonuçları, konteynerde hata ayıklarken sürekli karşına çıkar:

  • PID namespace: konteynerin içinde uygulaman PID 1'dir (1. dersteki cat /proc/1/cmdline denemesi bunu gösterir). İki sonucu var: PID 1 sinyalleri farklı işler — SIGTERM için işleyici yoksa sinyal yok sayılır ve docker stop on saniye bekleyip SIGKILL atar; ayrıca PID 1 yetim süreçleri toplamakla yükümlüdür, kabuk betiğiyle başlatılan konteynerlerde zombi süreç birikmesinin nedeni budur (çözüm: --init ya da doğru ENTRYPOINT — 8. ders).
  • Ağ namespace: konteynerin kendi ağ arayüzü, kendi IP'si ve kendi localhost'u vardır. Bu yüzden konteyner içindeki 127.0.0.1 hostun localhost'u değildir — 5. dersin dinleme adresi kuralı burada hayatidir.
  • Mount namespace: konteyner kendi kök dosya sistemini görür; hostun /etc'si ya da ev dizini yoktur. "Sertifika/log/appsettings dosyası bulunamıyor" hatalarının temel nedeni bu ayrımdır.
  • User namespace: konteynerdeki UID 0 ile hostun UID 0'ı eşleştirilebilir ya da kaydırılabilir. Kaydırma yapılmıyorsa konteynerdeki root, dosya sistemi izinleri açısından hostun root'u gibi davranır — 3. dersteki bind mount ve UID eşleşmesi uyarısının kökü budur. Rootless Docker ve Podman bu kaydırmayı varsayılan yapar.

Bu mekanizmaların hepsi Linux çekirdeği özelliğidir. Docker onları icat etmedi; kullanımını kolaylaştırdı (imaj biçimi, komut satırı, registry). Bu yüzden konteyner "Docker'a özel" bir teknoloji değildir ve bu yüzden Windows ya da macOS üzerinde Linux konteyneri çalıştırmak için bir Linux çekirdeğine — yani sanal makineye (WSL2 ya da Docker Desktop'ın VM'i) ihtiyaç duyulur.

04Sanal makine ↔ konteyner

Sanal makine, donanımı taklit eder: her VM kendi çekirdeğini ve tam bir işletim sistemini çalıştırır. Konteyner ise hostun çekirdeğini paylaşır, yalnız kullanıcı alanını taşır. Bütün farklar bu tek cümleden türer.

ÖlçütSanal makineKonteyner
ÇekirdekKendi çekirdeği (hipervizör üzerinde)Hostun çekirdeğini paylaşır
BoyutBirkaç GB (tam OS)Onlarca–yüzlerce MB (yalnız kullanıcı alanı)
Açılış süresiOnlarca saniye (OS açılışı)Milisaniye–saniye (süreç başlatma)
YoğunlukBir makinede onlarcaBir makinede yüzlerce
İzolasyon gücüDaha güçlü: donanım düzeyinde ayrımDaha zayıf: çekirdek paylaşımı ortak saldırı yüzeyi bırakır
İşletim sistemi seçimiFarklı OS/çekirdek çalıştırılabilir (Linux VM üstünde Windows)Yalnız host çekirdeğiyle uyumlu olan (Linux'ta Linux konteyneri)
Yama yönetimiHer VM'in OS'u ayrı yamalanırİmaj yeniden derlenir (4. ders: imaj yenilenmeden yama kapanmaz)
Uygun yerFarklı OS ihtiyacı, güçlü izolasyon gereği (çok kiracılı/güvenlik sınırı), eski/monolitik sistemlerAynı OS üzerinde çok sayıda uygulama, hızlı ölçekleme, CI, tekrarlanabilir ortam

İkisi birbirinin alternatifi değildir; pratikte iç içe kullanılır: bulut sağlayıcısında bir VM kiralar, üstünde konteyner koşturursun — izolasyonu VM sınırından, yoğunluğu ve tekrarlanabilirliği konteynerden alırsın. Kubernetes kümeleri de (12. ders) tam olarak böyle kurulur.

Kıdem düzeyinde bilinmesi gereken üç ayrıntı: (1) Güvenlik sınırı olarak konteyner zayıftır; birbirine güvenmeyen kiracıları ayırmak için VM ya da hafif VM (gVisor, Kata Containers, Firecracker) kullanılır. (2) Windows konteynerleri vardır ama Windows çekirdeği gerektirir; Linux ve Windows konteynerleri aynı hostta aynı anda koşmaz. (3) Konteynerde top ya da free çalıştırdığında gördüğün değerler, cgroup sınırlarını değil hostun değerlerini gösterebilir — bu yüzden .NET'in konteyner farkındalığı (cgroup limitini okuyup GC'yi ona göre ayarlaması) önemlidir, ölçüm için docker stats kullanılır (7. ders).

05İmaj, konteyner, katman

Üç kavram sık karıştırılır; ayrımı netleştirelim:

KavramNedirBenzetme
İmajSalt okunur, sürümlü paket: dosya sistemi katmanları + yapılandırma (giriş komutu, ortam, port)Sınıf (tanım)
Konteynerİmajdan başlatılmış çalışan örnek + üstüne eklenmiş yazılabilir katmanNesne (örnek)
KatmanDosya sistemi değişikliği paketi; imaj katmanların üst üste bindirilmesidirCommit (16-01)

Bir imaj, sırayla uygulanan katmanlardan oluşur. Örneğin bir ASP.NET Core imajı: temel Debian katmanı → .NET çalışma zamanı katmanı → bağımlılıklar → uygulama dosyaları. Çalıştırma anında bu katmanlar overlayfs ile tek bir dosya ağacına birleştirilir ve en üste ince, yazılabilir bir katman eklenir. Yazma davranışı copy-on-write'dır: alt katmandaki bir dosyayı değiştirdiğinde dosya önce üst katmana kopyalanır, sonra değiştirilir. Alt katmanlar hiç değişmez.

Bundan üç önemli sonuç çıkar:

  • Konteyner silinince yazılan veri gider. Yazılabilir katman konteynerin ömrüne bağlıdır. Veritabanı dosyası, yüklenen belgeler ya da üretilen raporlar konteynerin içinde tutulursa ilk yeniden dağıtımda kaybolur. Volume'ün gerekçesi budur ve 7. derste kurulur.
  • Büyük dosyayı değiştirmek pahalıdır. Copy-on-write, 1 GB'lık bir dosyanın tek baytını değiştirmek için onu üst katmana kopyalar; veritabanı gibi yoğun yazma yapan işler yazılabilir katmanda değil volume üzerinde çalıştırılır (performans gerekçesi).
  • Silme, yer kazandırmaz. Bir katmanda eklenen dosyayı sonraki katmanda silmek onu imajdan çıkarmaz; yalnız görünmez kılar (whiteout). Bu yüzden "önce sır dosyasını kopyala, sonra sil" yaklaşımı hem imajı küçültmez hem de sırrı imaj içinde bırakır — 8. ve 9. dersin çok aşamalı derleme (multi-stage) gerekçelerinden biri.

İmajın yapılandırma tarafı da katmanlar kadar önemlidir: hangi komutla başlayacağı (ENTRYPOINT/CMD), hangi kullanıcıyla koşacağı (USER — 3. ders), hangi ortam değişkenlerini taşıdığı, hangi portu beyan ettiği ve çalışma dizini. Bu bilgiler imajın manifest'inde durur ve docker inspect ile okunur.

06İçerik adresleme: Git nesne deposuyla aynı fikir

16-01'de Git'in nesne deposunu öğrendik: içerik SHA özetiyle adreslenir, aynı içerik tek kez saklanır, bir commit'in kimliği içeriğinden türer. İmaj katmanları birebir aynı fikirle çalışır ve bu benzerlik tesadüf değildir — ikisi de değişmezlik ve tekrarlanabilirlik için aynı çözümü bulmuştur.

Git (16-01)Konteyner imajı
blob / tree / commit nesnesikatman (tar arşivi) / imaj yapılandırması / manifest
SHA-1/SHA-256 özeti = kimlikdigest: sha256:9f2c…
Aynı içerik tek kez saklanırAynı katman birden çok imaj arasında paylaşılır
git fetch yalnız eksik nesneyi indirirdocker pull yalnız eksik katmanı indirir ("Already exists")
Dal/etiket = hareketli işaretçiEtiket (:8.0, :latest) = hareketli işaretçi
Commit değişmez; yeni commit yeni kimlikKatman ve digest değişmez; yeni içerik yeni digest

Bu koşutluğun pratik faydaları: (1) Aynı temel imajı kullanan on uygulama, o katmanı diskte bir kez tutar — sunucuda yer ve ağda bant genişliği kazanılır. (2) docker pull çıktısındaki "Already exists" satırları bunun görünen yüzüdür. (3) Etiket ↔ digest ayrımı üretim güvenliğinin temelidir: etiket taşınabilir, digest taşınamaz. nginx:1.27 etiketi yarın farklı bir imajı gösterebilir; nginx@sha256:… her zaman tam olarak aynı imajı gösterir. Bu yüzden üretim dağıtımlarında ve tedarik zinciri güvenliğinde digest sabitlemesi (pinning) yapılır. Aynı sebeple :latest etiketi üretimde kullanılmaz: "ne olduğu belirsiz, dün ile bugün farklı olabilen" bir işaretçidir ve geri alma planını imkânsızlaştırır (10. ders, imaj registry; 18. hat, dağıtım).

07Ekosistem: OCI, Docker, containerd, Podman

"Docker" günlük dilde hem şirketi, hem ürünü, hem de konteyner kavramını anlatır; teknik olarak katmanları ayırmak gerekir. 2015'te oluşturulan OCI (Open Container Initiative) üç şeyi standartlaştırdı: imaj biçimi, çalışma zamanı ve dağıtım (registry API'si). Bunun anlamı: bir araçla ürettiğin imaj başka bir araçla çalışır.

KatmanÖrnekİşi
Komut satırı / arayüzdocker CLI, nerdctl, podmanKullanıcının konuştuğu yüz
Motor / APIDocker Engine (dockerd)İmaj yönetimi, ağ, volume, REST API (soket üzerinden)
Yüksek seviyeli çalışma zamanıcontainerd, CRI-Oİmajı çeker, konteyner ömrünü yönetir; Kubernetes bunu kullanır
Düşük seviyeli çalışma zamanırunc, crunNamespace/cgroup kurup süreci gerçekten başlatır
İmaj derleyiciBuildKit, buildahDockerfile'dan imaj üretir (8. ders)
RegistryDocker Hub, GitHub/GitLab registry, Harbor, ACR/ECRİmaj deposu (10. ders)

Bilinmesi gereken ayrımlar:

  • Docker Desktop ↔ Docker Engine. Engine, Linux'ta çalışan arka plan servisidir (systemctl status docker — 4. ders). Docker Desktop, Windows/macOS için bir sanal makine + arayüz + Kubernetes paketidir ve ticari lisans koşullarına tabidir (belirli büyüklükteki şirketlerde ücretli). Kurumsal ortamda bu bir karar konusudur; alternatifleri Rancher Desktop, Podman Desktop ya da doğrudan WSL2 içine kurulan Engine'dir.
  • Podman. Docker ile komut uyumlu (alias docker=podman çalışır), ama arka plan servisi olmadan (daemonless) ve varsayılan olarak köksüz (rootless) koşar; systemd ile iyi bütünleşir. 3. dersteki "docker grubu = root yetkisi" riskini ortadan kaldırdığı için RHEL ailesinde ve güvenlik odaklı kurumlarda tercih edilir.
  • Kubernetes ve Docker. Kubernetes, çalışma zamanı olarak Docker Engine'i değil containerd/CRI-O'yu kullanır ("Docker desteği kaldırıldı" haberinin anlamı budur). Ama Docker ile ürettiğin imaj OCI standardında olduğu için Kubernetes'te sorunsuz çalışır — 12. derste kavramsal olarak görülecek.

08Geliştirici için anlamı: Testcontainers senedi

Konteynerin bir geliştirici için en hızlı getirisi üretim değil geliştirme ve test tarafındadır. 7. hatta PostgreSQL kurmak, 19. hatta Redis ve RabbitMQ ayağa kaldırmak tek komuta iner (7. derste yapacağız). En önemlisi 15-06'da verilen sözün ödenmesidir.

Senet: Testcontainers (15-06). Entegrasyon testi dersinde şunu söylemiştik: gerçek veritabanına karşı test yazmanın doğru yolu, testin kendi veritabanını başlatmasıdır; sahte (in-memory) sağlayıcı SQL davranışını taklit edemez. Testcontainers'ın yaptığı iş tam olarak budur ve altında bu dersin mekanizması vardır: test başlarken bir imajdan konteyner başlatır, rastgele bir porta bağlar, hazır olmasını bekler, testler bitince konteyneri siler.

// xUnit + Testcontainers (15-06'nın devamı) — kavramsal iskelet
public class BasvuruTestleri : IAsyncLifetime
{
    private readonly PostgreSqlContainer _db = new PostgreSqlBuilder()
        .WithImage("postgres:16-alpine")      // sürüm sabit: üretimle aynı ana sürüm
        .Build();

    public Task InitializeAsync() => _db.StartAsync();   // konteyner ayağa kalkar
    public Task DisposeAsync()    => _db.DisposeAsync().AsTask();  // ve silinir

    [Fact]
    public async Task Basvuru_kaydedilir()
    {
        var baglanti = _db.GetConnectionString();        // rastgele porta bağlı
        // ... gerçek EF Core migration + gerçek sorgu
    }
}

Bu desenin bu derse bağlanan dört noktası: (1) Docker gereksinimi — bu testler geliştirme makinesinde ve CI koşucusunda çalışan bir konteyner çalışma zamanı ister (18. hatta CI'da Docker erişimi bir yapılandırma maddesi olarak döner). (2) Rastgele port — paralel testlerin çakışmaması için port yayımlama dinamik yapılır (7. ders). (3) Hazır olma kontrolü — konteynerin başlaması servisin hazır olması değildir; sağlık kontrolü beklenir (7. ve 9. ders). (4) Temizlik — silinen konteynerin verisi gider; testin izole olmasını sağlayan şey de bu (05. bölüm).

Aynı mantık geliştirme ortamının tamamına genişletilir: docker compose up ile API + veritabanı + önbellek tek komutla ayağa kalkar ve yeni gelen ilk gün kod yazmaya başlar (9. ders). Bu, 16-11'in README "beş dakika kuralı"nı gerçekten uygulanabilir kılan mekanizmadır.

09Temel imaj seçimi: alpine, slim, tam

Her imaj bir temel imajdan (base image) başlar ve bu seçim boyut, güvenlik ve uyumluluğu birlikte belirler. 1. dersteki glibc/musl ayrımı burada pratik karara dönüşür.

TürÖrnekBoyutNot
Tammcr.microsoft.com/dotnet/aspnet:8.0 (Debian)~220 MBKabuk ve araçlar var, hata ayıklamak kolay; güvenli varsayılan
-slimnode:20-slimDaha küçükAynı libc (glibc), gereksiz paketler atılmış
-alpinepostgres:16-alpineÇok küçükmusl: native bağımlılıklarda uyum riski; DNS ve zaman dilimi davranışında farklar
chiseled / distrolessdotnet/aspnet:8.0-noble-chiseledKüçükKabuk yok, paket yöneticisi yok: saldırı yüzeyi en küçük, hata ayıklama en zor
scratch—0Tamamen boş; yalnız statik bağlı (self-contained, AOT) ikili dosyalar için

Seçim ölçütleri: (1) Uyum önce gelir — .NET için Alpine ayrı bir çalışma kimliği (linux-musl-x64) ister; native bağımlılığı olan kütüphaneler (görüntü işleme, ICU, bazı veritabanı sürücüleri) Alpine'da farklı davranabilir. Yalnız boyut istiyorsan Debian tabanlı -slim ya da chiseled imajlar daha güvenli seçimdir. (2) Küçük imaj = küçük saldırı yüzeyi: içinde kabuk ve paket yöneticisi olmayan bir imajda, ele geçirilen bir uygulamanın yapabilecekleri belirgin biçimde azalır (14. hattaki sertleştirme ilkesi). (3) Hata ayıklama bedeli: kabuksuz imajda docker exec … sh çalışmaz; teşhis için geçici bir yardımcı konteyner (docker debug ya da aynı namespace'e bağlanan bir araç konteyneri) gerekir — 7. derste göreceğiz. (4) Mimari: Apple Silicon ya da ARM sunucularda linux/arm64, bulutta çoğunlukla linux/amd64 gerekir; yanlış mimarideki imaj "exec format error" verir. Çok mimarili imaj üretimi 10. dersin konusudur.

Son bir kural ve 8. derse köprü: temel imaj sürümüyle seçilir (:8.0 ya da digest), :latest ile değil; ve güvenlik yaması temel imajı yenileyip yeniden derlemekle gelir (4. ders). İmaj tarama (10. ders) bu seçimi ölçülebilir kılar: aynı uygulamanın tam ve chiseled imajları arasındaki bilinen açık sayısı farkı, boyut tartışmasını tahminden çıkarır.

10Mini alıştırma — mekanizmayı gözle doğrula

Yaklaşık 20 dakika · Docker (ya da Podman) kurulu bir ortam

  1. Çekirdek paylaşımı. Hostta uname -r, sonra docker run --rm alpine uname -r çalıştır. Çıktılar neden aynı? Aynı komutu ubuntu imajıyla yinele ve sonucu yorumla.
  2. Farklı kullanıcı alanı. docker run --rm alpine cat /etc/os-release ve docker run --rm ubuntu cat /etc/os-release çıktılarını hostunkiyle karşılaştır. Neyin değiştiğini, neyin aynı kaldığını yaz.
  3. PID namespace. docker run --rm alpine ps -ef çalıştır: kaç süreç var, PID 1 ne? Sonra hostta ps -ef | grep -c "" ile karşılaştır.
  4. Katman paylaşımı. docker pull postgres:16-alpine ve ardından docker pull postgres:16 çalıştır; çıktıdaki "Already exists" satırlarını say. docker image ls ve docker system df ile toplam disk kullanımının imaj boyutları toplamından küçük olduğunu gözle doğrula.
  5. Etiket ↔ digest. docker image inspect --format '{{index .RepoDigests 0}}' alpine ile digest'i al. docker run --rm alpine@sha256:<digest> echo ok ile digest üzerinden çalıştır. Etiketin neden üretimde yetersiz olduğunu iki cümleyle yaz.
  6. Yazılabilir katman ve kaybolan veri. docker run -it --name deneme alpine sh içinde echo veri > /kok-dosya.txt yaz, çık. docker start -ai deneme ile geri dön: dosya var mı? Sonra docker rm deneme yapıp aynı imajdan yeni konteyner başlat: dosya var mı? Sonucu 7. dersin volume gerekçesiyle bağla.
  7. Katmanları oku. docker history mcr.microsoft.com/dotnet/aspnet:8.0 (ya da alpine) çıktısını incele: kaç katman var, en büyük katman hangisi? Sonra docker image inspect --format '{{len .RootFS.Layers}}' alpine ile katman sayısını doğrula.
  8. Boyut karşılaştırması. docker pull node:20 ve docker pull node:20-alpine çekip docker image ls | grep node ile boyutları karşılaştır. Alpine'ı seçmenin bedelini (musl) bir cümleyle yaz.

11Kendini sına

Konteynerin çözdüğü sorun nedir; konteyner teknik olarak nedir (namespace, cgroup, katmanlı dosya sistemi) ve bunların somut sonuçları nelerdir?

Çözülen sorun ortam farkıdır: uygulamanın çalışması için gereken çalışma zamanı, kütüphaneler, dosya düzeni ve paketler her ortamda elle kurulduğunda "bende çalışıyor", bağımlılık çakışması, bayatlayan kurulum dokümanı ve uzun ilk gün çıkar. Konteyner uygulamayı bütün kullanıcı alanıyla birlikte sürümlü ve değişmez bir imaj olarak paketler; aynı imaj geliştirmede, CI'da ve üretimde koşar — ortam ayrımı mekanizmayla garanti edilir. Teknik olarak konteyner izole bir süreçtir ve üç çekirdek mekanizmasına dayanır: namespace sürecin ne göreceğini ayırır (PID, ağ, mount, UTS, IPC, user), cgroup ne kadar kaynak kullanacağını sınırlar (systemd'nin MemoryMax'i ile aynı altyapı), katmanlı dosya sistemi (overlayfs) hangi dosyaları göreceğini belirler. Somut sonuçlar: konteynerde uygulama PID 1'dir — SIGTERM işleyicisi yoksa sinyal yok sayılır ve docker stop zorla öldürür, ayrıca zombi süreçler birikebilir (--init); konteynerin kendi localhost'u vardır, bu yüzden uygulama 0.0.0.0 dinlemelidir; kendi kök dosya sistemini görür, hostun /etc'si yoktur (sertifika/yapılandırma "bulunamadı" hatalarının kaynağı); user namespace kaydırılmadıysa konteynerdeki root dosya izinleri açısından hostun root'u gibi davranır. Bunlar Linux çekirdeği özellikleridir; Docker onları icat etmedi, kullanımını kolaylaştırdı — bu yüzden Windows/macOS'ta Linux konteyneri için bir sanal makine (WSL2) gerekir.

Sanal makine ile konteyner arasındaki farklar nelerdir; hangi durumda hangisi seçilir? Konteyner güvenlik sınırı olarak yeterli midir?

Temel fark tek cümledir: VM kendi çekirdeğini ve tam bir işletim sistemini çalıştırır, konteyner hostun çekirdeğini paylaşır ve yalnız kullanıcı alanını taşır. Buradan türeyenler: boyut (GB ↔ MB), açılış süresi (onlarca saniye ↔ milisaniye-saniye), yoğunluk (onlarca ↔ yüzlerce), izolasyon gücü (donanım düzeyinde güçlü ↔ çekirdek paylaşımı nedeniyle daha zayıf), OS seçimi (farklı çekirdek çalıştırılabilir ↔ yalnız host çekirdeğiyle uyumlu; Linux ve Windows konteynerleri aynı hostta koşmaz) ve yama yönetimi (her VM ayrı yamalanır ↔ imaj yeniden derlenir). Uygun yerler: VM farklı OS ihtiyacı, güçlü izolasyon gereği ve eski monolitik sistemler için; konteyner aynı OS üzerinde çok uygulama, hızlı ölçekleme, CI ve tekrarlanabilir ortam için. İkisi alternatif değil, iç içedir: bulutta VM kiralanır, üstünde konteyner koşar (Kubernetes kümeleri böyle kurulur). Güvenlik sınırı olarak konteyner zayıftır: birbirine güvenmeyen kiracıları ayırmak için VM ya da hafif VM teknolojileri (gVisor, Kata, Firecracker) kullanılır; ayrıca konteyner içinde top/free hostun değerlerini gösterebilir — ölçüm docker stats ile, kaynak farkındalığı .NET'in cgroup limitini okumasıyla sağlanır.

İmaj, konteyner ve katman arasındaki fark nedir; copy-on-write nasıl çalışır ve hangi üç sonucu doğurur?

İmaj salt okunur, sürümlü pakettir: dosya sistemi katmanları + yapılandırma (giriş komutu, kullanıcı, ortam, port). Konteyner imajdan başlatılmış çalışan örnektir ve üstüne ince bir yazılabilir katman eklenir — sınıf/nesne ilişkisi gibi. Katman bir dosya sistemi değişikliği paketidir; imaj katmanların üst üste bindirilmesidir (overlayfs ile tek ağaca birleştirilir). Copy-on-write: alt katmandaki bir dosya değiştirilmek istendiğinde önce üst yazılabilir katmana kopyalanır, sonra değiştirilir; alt katmanlar asla değişmez. Üç sonuç: (1) Konteyner silinince yazılan veri gider — yazılabilir katman konteynerin ömrüne bağlıdır; kalıcı veri için volume gerekir. (2) Büyük dosyayı değiştirmek pahalıdır — 1 GB'lık dosyanın tek baytı için tamamı kopyalanır, bu yüzden yoğun yazan veritabanı yazılabilir katmanda değil volume üzerinde çalıştırılır. (3) Silme yer kazandırmaz — sonraki katmanda silinen dosya imajdan çıkmaz, yalnız görünmez olur (whiteout); dolayısıyla "kopyala, kullan, sil" yaklaşımı hem imajı küçültmez hem de sırrı imajın içinde bırakır — çok aşamalı derlemenin gerekçelerinden biri.

İmaj katmanları ile Git nesne deposu arasındaki koşutluk nedir; etiket ↔ digest ayrımı neden önemli? OCI ekosisteminde Docker, containerd, Podman ne yapar ve Testcontainers bu dersle nasıl ilişkili?

Her iki sistem de içerik adreslemeye dayanır: Git'te blob/ tree/commit SHA özetiyle adreslenir, imajda katman/yapılandırma/manifest sha256: digest ile; aynı içerik tek kez saklanır, bu yüzden aynı katman birçok imaj arasında paylaşılır ve docker pull yalnız eksik katmanı indirir ("Already exists"). Git'teki dal/etiket gibi imaj etiketi de hareketli işaretçidir: nginx:1.27 yarın başka bir imajı gösterebilir, ama nginx@sha256:… her zaman aynı imajı gösterir. Bu yüzden üretimde ve tedarik zinciri güvenliğinde digest sabitlenir ve :latest kullanılmaz — geri alma planını imkânsızlaştırır. Ekosistem katmanları: docker/podman CLI (arayüz), Docker Engine (imaj/ağ/volume yönetimi ve API), containerd/CRI-O (yüksek seviyeli çalışma zamanı; Kubernetes bunu kullanır), runc (namespace/cgroup kurup süreci başlatan düşük seviyeli çalışma zamanı), BuildKit/buildah (imaj derleme), registry (imaj deposu). OCI imaj biçimini, çalışma zamanını ve dağıtımı standartlaştırdığı için bir araçla üretilen imaj diğeriyle çalışır. Docker Desktop, Windows/macOS için VM + arayüz paketidir ve belirli büyüklükteki şirketlerde ticari lisans gerektirir; Podman daemonless ve varsayılan köksüzdür, bu yüzden "docker grubu = root yetkisi" riskini ortadan kaldırır. Testcontainers, entegrasyon testinin kendi veritabanını başlatmasını sağlar (15-06 senedi): imajdan konteyner başlatır, rastgele porta bağlar, hazır olmasını bekler, test sonunda siler — bu yüzden geliştirme makinesinde ve CI koşucusunda bir konteyner çalışma zamanı gerekir, izolasyon da silinen konteynerin verisinin gitmesinden gelir.

12Sınav köşesi

Yazılıda nasıl sorulur

(1) Konteyner teknolojisi nedir; hangi sorunu çözer? (2) Sanal makine ile konteyner arasındaki farkları karşılaştırınız. (3) Konteynerlerin dayandığı Linux çekirdeği özellikleri nelerdir (namespace, cgroup)? (4) İmaj ile konteyner arasındaki fark nedir? (5) Katmanlı dosya sistemi ve copy-on-write nedir? (6) Bir konteyner silindiğinde içine yazılan veriler ne olur; kalıcı veri nasıl sağlanır? (7) İmaj etiketi (tag) ile digest arasındaki fark nedir; latest etiketi neden üretimde önerilmez? (8) OCI standardı nedir; Docker, containerd ve runc'un işlevleri nelerdir? (9) Docker'ın Windows üzerinde çalışması için neye ihtiyaç vardır? (10) Konteyner temel imajı seçiminde hangi ölçütler dikkate alınır?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Tanım — "Konteyner sanal makine değil, izole bir süreçtir: namespace ne göreceğini, cgroup ne kadar kaynak kullanacağını, katmanlı dosya sistemi hangi dosyaları göreceğini belirler. Çekirdek hostla paylaşılır; bu yüzden MB ölçeğinde ve saniyeler içinde açılır." (2) Fark — "VM kendi çekirdeğini taşır, izolasyonu daha güçlüdür ama ağırdır. Pratikte iç içe kullanırım: bulutta VM, üstünde konteyner. Güvenlik sınırı gereken çok kiracılı senaryoda konteynerle yetinmem." (3) İmaj modeli — "İmaj salt okunur katmanlardan oluşur, konteyner üstüne yazılabilir bir katman ekler; copy-on-write nedeniyle konteyner silinince yazılan veri gider — bu yüzden kalıcı veri volume'de tutulur, veritabanı yazılabilir katmanda çalıştırılmaz." (4) Sürüm disiplini — "Katmanlar içerik adreslidir, Git'in nesne deposu gibi; aynı katman imajlar arasında paylaşılır. Üretimde etiket yerine digest sabitlerim, :latest kullanmam; güvenlik yaması temel imajı yenileyip yeniden derlemekle gelir." (5) Kıdem vuruşu — "Temel imajda uyumu boyuttan önce tutarım: .NET'te Alpine musl uyumu risk taşır, boyut istiyorsam slim ya da chiseled imaj seçerim — kabuksuz imaj saldırı yüzeyini küçültür ama hata ayıklamayı zorlaştırır. Testlerde Testcontainers ile gerçek veritabanını konteynerde koştururum; bu yüzden CI koşucusunda konteyner çalışma zamanı erişimini bir gereksinim olarak belirtirim."