Bölüm 10: Container ve İzolasyon
CPU'ya“Sen”iKatmak yazısının parçası: bilgisayarının programları nasıl çalıştırdığına doğru inen uzun bir teknik tavşan deliği.
Ş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.
| Özellik | Sanal Makine | Container |
|---|---|---|
| İzolasyon seviyesi | Donanım | İşletim sistemi |
| Başlangıç süresi | Dakikalar | Saniyeler (hatta milisaniyeler) |
| Bellek tüketimi | GB’lar | MB’lar |
| Kernel | Her VM’de ayrı | Tümü ana makine kernel’ını paylaşır |
| Taşınabilirlik | VM 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:
| Namespace | Ne İzole Eder? | Örnek |
|---|---|---|
| PID | Process ID’leri | Container içinde init (PID 1) kendi process ağacını görür |
| NET | Ağ arayüzleri, portlar, routing | Her container kendi eth0‘ını ve IP’sini görür |
| MNT | Dosya sistemi mount noktaları | Container kendi root dosya sistemini görür |
| IPC | Inter-process communication | Paylaşılan bellek alanları izole edilir |
| UTS | Hostname ve domain name | Her container kendi hostname’ini belirleyebilir |
| USER | Kullanıcı ve grup ID’leri | Container içinde root (UID 0) görünebilir ama dışarıda normal kullanıcıdır |
| CGROUP | Cgroup bilgileri | Container kendi cgroup sınırlarını görür |
| TIME | Boot ve monotonic zamanı | Çok nadir kullanılır |
Bir process,
clone()sistem çağrısı ile yeni bir namespace seti oluşturabilir.unsharekomutu 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.
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:
unshare --pid --fork --mount-proc /bin/bash: Yeni bir PID namespace oluşturur, mevcut shell’i bu namespace’e taşır ve/proc‘u yeniden mount eder.--forkolmadan mevcut process’in kendisi taşınmaz, yeni bir child process gerekir.echo $$: Mevcut shell’in process ID’sini yazdırır. PID namespace içinde bu process artık 1 numaralı PID’dir — container’lardakiinitprocess gibi.ps aux:/proc‘dan process listesini okur./procyeniden mount edildiği için sadece bu namespace’teki process’leri görür.ls -la /proc/self/ns/: Her namespace’in kernel’de bir inode numarası vardır. Farklı namespace’ler farklı inode’lara sahiptir; bu sayede iki process aynı namespace’te mi kontrol edilebilir.
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.
$ 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.
| Kaynak | Sınır Türü |
|---|---|
| CPU | Yüzde, weight, period/quota |
| Bellek (RAM) | Hard limit, soft limit, swap limit |
| Disk I/O | Bant genişliği / IOPS sınırlaması |
| Ağ (dolaylı) | Genellikle namespace + traffic shaping (tc) veya runtime entegrasyonlarıyla yönetilir |
| PIDs | Bir gruptaki maksimum process sayısı |
docker run --memory=512m --cpus=1.5komutu 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:
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:
mkdir /sys/fs/cgroup/mycontainer: cgroup v2 hiyerarşisinde yeni bir grup oluşturur. Her dizin bir cgroup’tur.echo "+cpu +memory" > .../cgroup.subtree_control: Bu cgroup’un child gruplarına CPU ve memory controller’larını devretmesini sağlar.subtree_contrololmadan alt gruplar kaynak sınırlandıramaz.echo $$ > .../cgroup.procs: Mevcut shell process’ini bu cgroup’a üye yapar. Artık bu process ve child’ları bu grubun sınırlarına tabidir.cat /sys/fs/cgroup/mycontainer/cgroup.controllers: Bu cgroup içinde kullanılabilir controller’ları listeler. Gerçek sınırlarcpu.max,memory.maxgibi controller dosyalarıyla ayarlanır; production runtime’ları bu ayrıntıları senin yerine düzenler.
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.
- seccomp-bpf: BPF (Berkeley Packet Filter) programları ile esnek filtreleme.
- Docker’ın varsayılan seccomp profili, sürüme göre değişebilen bir deny/allow list ile çok sayıda riskli syscall’u kısıtlar (örneğin
mount,reboot,swapongibi host’u etkileyebilecek yollar).
docker run --security-opt seccomp=unconfinedseccomp’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:
#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.
prctl(PR_SET_SECCOMP, ...)process’in seccomp modunu ayarlar.SECCOMP_MODE_FILTERBPF tabanlı esnek filtreleme sağlar.- Filtre programı, her sistem çağrısı öncesinde kernel tarafından çalıştırılır. Sonuç
SECCOMP_RET_ALLOWise çağrı izin verilir;SECCOMP_RET_KILLise process anında sonlandırılı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.
| Capability | Anlamı |
|---|---|
CAP_NET_ADMIN | Ağ yapılandırması yapabilir |
CAP_SYS_ADMIN | Birçok yönetimsel işlem (“yeni root”) |
CAP_CHOWN | Dosya sahibini değiştirebilir |
CAP_KILL | Herhangi bir process’i sonlandırabilir |
CAP_NET_BIND_SERVICE | 1024 altı portlara bağlanabilir |
CAP_SYS_PTRACE | Başka process’leri debug edebilir |
Container’lar genelde gereksiz capability’leri bırakır. Örneğin
--cap-drop ALL --cap-add NET_BIND_SERVICEile sadece düşük port bağlama yetkisi verilir, gerisi alınır.
Derinleşme: Container Escape ve CAP_SYS_ADMIN
CAP_SYS_ADMINgeleneksel 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) veyaCAP_SYS_ADMINverilmiş 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_ADMINolmadan 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.
/procve/syssızıntıları: Bazı host bilgileri container içinden/procve/sysüzerinden sızdırılabilir. Modern Docker sürümleri bu dosya sistemlerini kısıtlar.En iyi pratik: container’lara
CAP_SYS_ADMINyalnızca çok güçlü, denetlenmiş bir gerekçe varsa verilmeli;--privilegedbayrağı ü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?
- chroot: Eski yöntem. Bir dizini yeni root olarak ayarlar ama güvenlik açıkları vardır (chroot escape).
- 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:
ps auxsadece container process’lerini gösterir (PID namespace).ifconfigcontainer’ın kendi sanal ağ arayüzünü gösterir (NET namespace).hostnamecontainer’a özel bir isim döndürür (UTS namespace).df -hcontainer’ın kendi root dosya sistemini gösterir (MNT namespace).
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:
| Dizin | Rolü |
|---|---|
| lowerdir | Salt okunur katmanlar. Docker imaj katmanları burada yer alır. |
| upperdir | Yazılabilir katman. Container çalışırken yapılan tüm değişiklikler buraya yazılır. |
| workdir | OverlayFS’in iç işlemleri için gerekli geçici dizin. |
| merged | Son kullanıcının gördüğü birleşik dosya sistemi görünümü. |
mount -t overlay overlay -o lowerdir=/lower,upperdir=/upper,workdir=/work /merged
Bu mount komutu şunları yapar:
lowerdir=/lower: Salt okunur temel katmanları belirtir. Birden fazla katman virgülle ayrılarak verilebilir (lowerdir=/layer1:/layer2).upperdir=/upper: Tüm yazma işlemlerinin yapılacağı katman. Container silindiğinde bu dizin de silinir.workdir=/work: OverlayFS’in atomic rename ve diğer iç işlemleri için kullandığı geçici alan./merged: Kullanıcının veya container içindeki uygulamaların gördüğü, tüm katmanların birleşmiş hâli.
OverlayFS, copy-on-write (COW) prensibiyle çalışır. Bir dosya değiştirilmek istendiğinde, salt okunur
lowerdir‘denupperdir‘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
- Container’lar VM’lerden farklı olarak aynı kernel’ı paylaşan, izole kullanıcı alanlarıdır.
- Namespaces (PID, NET, MNT, IPC, UTS, USER, CGROUP) “kim ne görür”ü belirler.
- cgroups CPU, bellek, disk ve ağ gibi kaynakları sınırlar.
- seccomp kullanılabilir sistem çağrılarını filtreler.
- Capabilities root yetkilerini parçalara ayırarak asgari yetki prensibini uygular.
- pivot_root + OverlayFS container’ın kendi dosya sistemini ve katmanlı imajlarını sağlar.
Bu bölümde öğrendiğimiz kavramlar, kitabın önceki bölümleriyle doğrudan bağlantılıdır:
- Bölüm 9 — Sanal Bellek: Container’lar, process’lerin sanal bellek alanlarını aynı mekanizmalarla kullanır. Her container process’i kendi page table’ları üzerinden çalışır; namespace’ler bu izolasyonu görünürlük seviyesine taşır.
- Bölüm 6/7/11 — exec, shell ve fork: Bir container başlatılırken runtime, yeni namespace’lerle
clone()/fork()ailesini kullanır ve ardından container içindeki ilk process’iexecve()ile çalıştırır. Container başlatma, bu sistem çağrılarının pratik bir uygulaması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.
11. bölüme devam et: Fork'lar ve COW'lar Hakkında KonuşalımSonraki 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 vefork + execmodelinin bütün bu dünyayı nasıl başlattığını göreceğiz.