Ana içeriğe geç

Bölüm 10: Container ve İzolasyon

Şimdiye kadar process’lerin birbirinden sanal bellek ve kullanıcı/kernel modu ile nasıl izole edildiğini gördük. Bu bölüm, kernel başlangıcına dönmeden önce kısa bir modern uygulama molası: Peki bulut sistemlerindeki container’lar (Docker, Kubernetes) bu izolasyonu nasıl paketliyor? Her container ayrı bir sanal makine mi? Linux-native bağlamda hayır. Container’lar aslında Linux kernel’inin sunduğu izolasyon ve kaynak yönetimi mekanizmalarının akıllıca bir araya getirilmesinden ibaret.

Bu bölümde neyi çözüyoruz?

  • Container’ların sanal makinelerden nasıl farklı olduğunu göreceğiz.
  • Linux namespaces, cgroups, seccomp ve capabilities kavramlarını anlayacağız.
  • Bir container’ın içinden dışarıyı nasıl gördüğünü ve neden “kendi dünyası” olduğunu bağlayacağız.

Container vs Sanal Makine

Sanal Makine (VM): Donanım seviyesinde izolasyon. Her VM kendi işletim sistemi kernel’ını, sürücülerini ve tam bir sanal donanım setini çalıştırır. Hypervisor (KVM, VMware, Hyper-V) fiziksel donanımı birden fazla sanal makine arasında böler.

Container: İşletim sistemi seviyesinde izolasyon. Linux-native bir host’ta container’lar aynı Linux kernel’ını paylaşır. Her container sadece kendi dosya sistemini, process listesini ve ağını görür. Çok daha hafif ve hızlıdır. Docker Desktop gibi macOS/Windows kurulumlarında ise bu Linux kernel çoğu zaman host üzerinde çalışan ayrı bir Linux VM içindedir; container’lar yine kendi aralarında o kernel’ı paylaşır.

ÖzellikSanal MakineContainer
İzolasyon seviyesiDonanımİşletim sistemi
Başlangıç süresiDakikalarSaniyeler (hatta milisaniyeler)
Bellek tüketimiGB’larMB’lar
KernelHer VM’de ayrıTümü ana makine kernel’ını paylaşır
TaşınabilirlikVM imajıContainer imajı (örn. Docker)

Container’ların hafif olmasının nedeni, her seferinde yeni bir kernel başlatmamasıdır. Sadece izole bir kullanıcı alanı (user space) oluşturur.

Linux Namespaces: Kim Ne Görsün?

Namespace (ad alanı), kernel’ın aynı kaynağı farklı process gruplarına farklı görünür hâle getirmesini sağlar. Linux’ta 8 ana namespace vardır:

NamespaceNe İzole Eder?Örnek
PIDProcess ID’leriContainer içinde init (PID 1) kendi process ağacını görür
NETAğ arayüzleri, portlar, routingHer container kendi eth0‘ını ve IP’sini görür
MNTDosya sistemi mount noktalarıContainer kendi root dosya sistemini görür
IPCInter-process communicationPaylaşılan bellek alanları izole edilir
UTSHostname ve domain nameHer container kendi hostname’ini belirleyebilir
USERKullanıcı ve grup ID’leriContainer içinde root (UID 0) görünebilir ama dışarıda normal kullanıcıdır
CGROUPCgroup bilgileriContainer kendi cgroup sınırlarını görür
TIMEBoot ve monotonic zamanıÇok nadir kullanılır

Bir process, clone() sistem çağrısı ile yeni bir namespace seti oluşturabilir. unshare komutu mevcut process’i yeni namespace’lere taşır. Docker container’ları aslında bu mekanizmaları otomatikleştirir.

Namespace’leri Elle Oluşturmak

Container runtime’ları arkada bu işlemleri otomatikleştirir, ancak elle bir namespace oluşturmak izolasyonun nasıl çalıştığını anlamak için çok faydalıdır.

PID namespace oluşturma
sudo unshare --pid --fork --mount-proc /bin/bash
echo $$   # Output: 1 (PID namespace içinde ilk process)
ps aux    # Sadece namespace içindeki process'leri gösterir
ls -la /proc/self/ns/  # Namespace inode'larını listeler

Her komutun yaptığı işlem şöyledir:

Namespace’ler iç içe (nested) oluşturulabilir. Bir PID namespace içindeki process, kendi child’ları için daha derin bir PID namespace açabilir. Bu, Kubernetes’teki pod ve container hiyerarşisinin temelidir.

Kabuk oturumu
$ sudo unshare --pid --fork --mount-proc /bin/bash
# Artık yeni bir PID namespace içindeyiz
# ps komutu sadece bu namespace'teki process'leri gösterir

cgroups: Kaynak Sınırları

Namespace’ler “kim ne görür”ü çözer ama “kim ne kadar tüketir”i çözmez. cgroups (control groups), process gruplarına kaynak sınırları koyar.

KaynakSınır Türü
CPUYüzde, weight, period/quota
Bellek (RAM)Hard limit, soft limit, swap limit
Disk I/OBant genişliği / IOPS sınırlaması
Ağ (dolaylı)Genellikle namespace + traffic shaping (tc) veya runtime entegrasyonlarıyla yönetilir
PIDsBir gruptaki maksimum process sayısı

docker run --memory=512m --cpus=1.5 komutu aslında arka planda cgroup sınırları oluşturur. Container 512 MB RAM ve 1.5 CPU core kullanabilir.

cgroups v2 ile Kaynak Sınırlamak

Modern Linux dağıtımları cgroups v2 kullanır. Hiyerarşik bir yapı sunar ve tüm controller’lar tek bir ağaçta toplanır. Elle bir cgroup oluşturmak şöyledir:

cgroups v2 oluşturma
sudo mkdir /sys/fs/cgroup/mycontainer
echo "+cpu +memory" > /sys/fs/cgroup/mycontainer/cgroup.subtree_control
echo $$ > /sys/fs/cgroup/mycontainer/cgroup.procs
cat /sys/fs/cgroup/mycontainer/cgroup.controllers

Adım adım açıklama:

cgroup sınırları kalıcı değildir; dizin silindiğinde veya sistem yeniden başlatıldığında kaybolur. Ayrıca cgroup v2’nin “no internal process” gibi hiyerarşi kuralları vardır; yukarıdaki komutları production reçetesi değil, mekanizmayı görmek için sadeleştirilmiş deney olarak oku. Container runtime’ları her container başlatışında bu yapıları dinamik olarak oluşturur ve sonlandığında temizler.

seccomp: Sistem Çağrısı Filtreleme

seccomp (secure computing mode), bir process’in hangi sistem çağrılarını yapabileceğini kısıtlar. Bir container’a “sadece şu syscall’ları kullanabilirsin, gerisi yasak” denebilir.

docker run --security-opt seccomp=unconfined seccomp’u tamamen devre dışı bırakır; üretimde önerilmez.

seccomp ile Sistem Çağrısı Filtreleme

seccomp-bpf kullanarak bir process’in yapabileceği sistem çağrılarını kernel seviyesinde filtrelemek mümkündür. Aşağıda minimal bir seccomp-bpf yapılandırma örneği görüyorsunuz:

Minimal seccomp-bpf (C)
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>

// prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
// Bu çağrı ile process'e bir BPF programı uygulanır.
// İzin verilen syscall'lar whitelist'e alınır, gerisi SIGKILL ile sonlanır.

Docker’ın varsayılan seccomp profili sürümden sürüme değişebilir. Ancak özelleştirilmiş bir profil yazarak container’ınızın ihtiyaç duymadığı syscall’ları engelleyebilirsiniz. Bu, attack surface’i minimize eder.

Capabilities: Root Yetkilerinin Parçalanması

Geleneksel Unix’te bir kullanıcı ya root’tur (her şeyi yapar) ya değildir (çok az şey yapar). Linux capabilities (yetenekler) bu ikiliği parçalar.

CapabilityAnlamı
CAP_NET_ADMINAğ yapılandırması yapabilir
CAP_SYS_ADMINBirçok yönetimsel işlem (“yeni root”)
CAP_CHOWNDosya sahibini değiştirebilir
CAP_KILLHerhangi bir process’i sonlandırabilir
CAP_NET_BIND_SERVICE1024 altı portlara bağlanabilir
CAP_SYS_PTRACEBaşka process’leri debug edebilir

Container’lar genelde gereksiz capability’leri bırakır. Örneğin --cap-drop ALL --cap-add NET_BIND_SERVICE ile sadece düşük port bağlama yetkisi verilir, gerisi alınır.

Derinleşme: Container Escape ve CAP_SYS_ADMIN

CAP_SYS_ADMIN geleneksel Unix’te “geriye kalan her şey” yetkisiydi ve bu yüzden sıkça “yeni root” olarak adlandırılır. Neredeyse tüm yönetimsel işlemleri kapsar: mount, pivot_root, namespace yönetimi, sysctl değişiklikleri ve daha fazlası.

Container güvenliğinde en tehlikeli yetki budur. Privileged bir container (--privileged) veya CAP_SYS_ADMIN verilmiş bir container, host kernel’ının sundğu pek çok interface’e erişebilir. Bilinen kaçış teknikleri:

  • Mount abuse: Host üzerindeki bir dosya sistemini container içine mount edip root olarak erişmek. CAP_SYS_ADMIN olmadan mount işlemi engellenir.
  • Kernel exploit’ler: Container içinden host kernel’ına erişmek için unpatched bir vulnerability kullanmak. Namespace’ler bunu tamamen engellemez, sadece zorlaştırır.
  • /proc ve /sys sızıntıları: Bazı host bilgileri container içinden /proc ve /sys üzerinden sızdırılabilir. Modern Docker sürümleri bu dosya sistemlerini kısıtlar.

En iyi pratik: container’lara CAP_SYS_ADMIN yalnızca çok güçlü, denetlenmiş bir gerekçe varsa verilmeli; --privileged bayrağı üretimde varsayılan çözüm olarak kullanılmamalıdır. Asgari yetki prensibi burada kritiktir.

pivot_root ve Container Dosya Sistemi

Container kendi root dosya sistemini (/) görür. Bu nasıl sağlanır?

  1. chroot: Eski yöntem. Bir dizini yeni root olarak ayarlar ama güvenlik açıkları vardır (chroot escape).
  2. pivot_root: Modern yöntem. Mevcut root’u yeni bir dizinle değiştirir ve eski root’u başka bir yere taşır. Daha güvenlidir.

Docker imajları, katmanlı (layered) bir dosya sistemi (OverlayFS) kullanır. Her katman salt okunurdur; container çalıştığında üzerine yazılabilir bir katman (container layer) eklenir.

Bir Container’ın İçinden Bakış

Container içinde çalışan bir program, kendini normal bir Linux sisteminde sanar:

Ama kernel versiyonuna bakarsan (uname -r), host makine ile aynı olduğunu görürsün. Çünkü kernel paylaşılıyor.

OverlayFS: Katmanlı Dosya Sistemi

Docker ve benzeri container runtime’ları, imajların katmanlı yapısını sağlamak için OverlayFS kullanır. OverlayFS, birden fazla dizini tek bir birleşik görünümde sunar. Üç temel dizin vardır:

DizinRolü
lowerdirSalt okunur katmanlar. Docker imaj katmanları burada yer alır.
upperdirYazılabilir katman. Container çalışırken yapılan tüm değişiklikler buraya yazılır.
workdirOverlayFS’in iç işlemleri için gerekli geçici dizin.
mergedSon kullanıcının gördüğü birleşik dosya sistemi görünümü.
OverlayFS mount
mount -t overlay overlay -o lowerdir=/lower,upperdir=/upper,workdir=/work /merged

Bu mount komutu şunları yapar:

OverlayFS, copy-on-write (COW) prensibiyle çalışır. Bir dosya değiştirilmek istendiğinde, salt okunur lowerdir‘den upperdir‘e kopyalanır ve orada değiştirilir. Orijinal imaj katmanı bozulmaz; bu sayede aynı imajdan birden fazla container hızla ve az disk kullanarak başlatılabilir.

Özet

Bu bölümde öğrendiğimiz kavramlar, kitabın önceki bölümleriyle doğrudan bağlantılıdır:

Container teknolojisi, bu bölüme kadar öğrendiğimiz tüm Linux kernel mekanizmalarının — sanal bellek, process yönetimi, sistem çağrıları, dosya sistemleri — pratik ve modern bir uygulamasıdır. Kernel’ın sunduğu izolasyon ve kaynak yönetimi araçlarını bir araya getirerek, hafif, hızlı ve taşınabilir çalışma ortamları oluşturur.


Sonraki bölüm: Modern container molasından kernel hikâyesine geri dönüyoruz: process’lerin nasıl klonlandığını, bilgisayarın açılışında ilk process’in (init) nasıl doğduğunu ve fork + exec modelinin bütün bu dünyayı nasıl başlattığını göreceğiz.

11. bölüme devam et: Fork'lar ve COW'lar Hakkında Konuşalım