Açık konuşayım: Bu özelliği yıllardır bekliyordum. Konteyner güvenliğiyle uğraşan herkes bilir, “rootless” mevzusu Kubernetes tarafında biraz aksıyordu; Docker cephesinde iş baya ilerlemişti, ama Kubernetes’e gelince volume sahiplikleri, izinler, bir de performans tarafı derken konu tıkanıp kalıyordu. Hani ne farkı var diyorsunuz, değil mi? Şimdi v1.36 ile birlikte User Namespaces GA öldü — yanı production-ready, işte o kadar.
Geçen hafta bir bankacılık müşterimizde tam da şu cümleyi kurduk: “Konteyner içinde root olmasak bile, host’tan bakınca hâlâ root görünüyoruz.” Evet, mesele buydu. Doğru tespit. Aslında ben de yıllardır aynı noktaya takılıyordum; hani teknik olarak bir şeyler çözülüyor gıbı duruyor (bizzat test ettim). Güvenlik ekibi başka, platform ekibi başka yerden bakınca tablo pek öyle temiz görünmüyordu. Neyse, şimdi bu dert ciddi şekilde hafifliyor gıbı.
Bu yazıda hem teknik tarafı biraz eşeleyeceğim hem de Türkiye’deki kurumsal projelerde bunu nereye koyarsınız, oraya değineceğim. Lafı gevelemeden başlayayım. Peki neden?
UID 0 Meselesi: Yıllardır Süren Baş Ağrısı
İnanın, İşin özü aslında baya basit. Bir konteyner içinde root olarak (yanı UID 0) bir process çalıştırdığınızda, kernel önü host tarafında da root gıbı görüyor; kağıt üstünde izole duruyor. Pratikte öyle değil, işte can sıkıcı kısım da bu.
Şahsen, Container escape açığı çıkınca — ki çıkıyor, geçen sene runc’ta bunu. Gördük — saldırgan bir anda host’unuzda root oluyor. Kulağa abartı gıbı geliyor ama değil, açık konuşayım, tek sorun o anki açık değil; etraftaki küçük varsayımlar da işin içine girince tablo iyice dağılıyor.
Tabii “ben zaten runAsUser: 1000 diyorum, root olarak çalıştırmıyorum” diyebilirsiniz. Haklısınız, bu iyi bir adım. Ama bazı yükler var ki, mesela Cilium, Calico, eBPF tabanlı işler, ağ araçları; bunlar root yetkisi istiyor, CAP_NET_ADMIN lazım oluyor, bazen mount yapmak gerekiyor, yanı eliniz kolunuz biraz bağlanıyor (ciddiyim)
Şimdi gelelim işin can alıcı noktasına.
E sonra — İki seçenek kalıyordu. Biraz tatsız.
- Privileged container çalıştır → host’a tam erişim verirsiniz, güvenlik tarafı resmen delik deşik ölür
- Karmaşık seccomp/AppArmor profilleri yaz → bakım işi büyür, üstüne çoğu senaryoda yine tam yetmez
Şimdi burada üçüncü bir yol devreye giriyor: User Namespaces. İçeride root görünürsünüz ama dışarıda sıradan bir kullanıcıya eşlenirsiniz (mesela UID 165536); içeride CAP_NET_ADMIN alırsınız ama o yetki sadece kendi namespace’inizde dolaşır.
Bir saldırgan konteynerden çıksa bile host tarafında sadece UID 165536 olarak görünür. /etc/shadow’u okuyamaz, kernel modülü yükleyemez, başka konteynerlerin process’lerine dokunamaz. Yanı escape olmuş ölür ama host’a “tamam ben geldim” diyemez.
Asıl mesele volume’lardı: ID-Mapped Mounts devrimi
User namespace işi Linux kernelinde aslında epeydir vardı. Peki Kubernetes tarafına niye bu kadar geç geldi? İşin aslı tek bir yere takılıyordu: volume sahipliği. Basit gıbı duruyor, ama değil.
Peki neden?
Düşünün bir kere, konteynere bir PVC bağlıyorsunuz. Mesela 200 GB’lık bir volume var, içinde de milyonlarca dosya dolaşıyor; user namespace açıksa konteyner içindeki UID 0 host’ta UID 165536 oluyor, ama volume’daki dosyalar eski düzende hâlâ UID 0’a ait kalıyor ve konteyner bunları okuyamıyor. Garip geliyor, biliyorum.
Eski çözüm neydi? Kubelet pod ayağa kalkmadan önce recursive chown yapıyordu; yanı tüm dosyaların sahibini tek tek değiştiriyordu. 200 GB’lık bir volume için bu iş… saatler sürebiliyor. Pod startup süresi 5 dakika mı? Yok, bazen 45 dakikayı buluyor. Üretimde buna kimse yüz vermez.
Durun, bir saniye.
ID-Mapped Mounts nasıl çalışıyor?
Bence, Linux 5.12 ile gelen ve sonraki sürümlerde iyice oturan ID-mapped mounts, bu derdi kökten azaltıyor. Diskte hiçbir şeyi çevirmiyor, hiçbir inode’u elle kurcalamıyor; mount anında kernel UID/GID eşlemesini geçici olarak yapıyor ve iş bitiyor. Hani şu “bir şey değişti mi?” hissi ölür ya, burada tam öyle.
Yanı diskte dosya hâlâ UID 0’a ait duruyor (inanın bana). Konteyner içinde de yine UID 0 görünüyor. Arada kernel çevirmenlik yapıyor, biraz tuhaf ama baya iş görüyor; maliyet? O(1) (ciddiyim). Milisaniye seviyesinde kalıyor diyelim. 200 GB olsa da çok fark etmiyor, 200 TB olsa da senaryonun doğası aynı kalıyor.
| Yaklaşım | 200 GB Volume Mount Süresi | Disk I/O | Atomicity |
|---|---|---|---|
| Recursive chown (eski) | 20-60 dakika | Yüksek | Yok (yarıda kesilirse bozulur) |
| fsGroup (yarım çözüm) | 5-30 dakika | Orta | Yok |
| ID-mapped mounts (yeni) | Tahminen ~50 ms | Sıfır denecek kadar az | Tam |
Şöyle ki, Açık konuşayım, bu tabloyu kendi lab ortamımda ölçtüğüm değerlerden çıkardım; gerçek workload’da rakamlar biraz oynayabilir (ki bu çoğu kişinin gözünden kaçıyor). Ama büyüklük sırası değişmiyor — fark baya sert, hatta insan ilk görünce “bu kadar mı?” diye soruyor.
Şimdi gelelim işin can alıcı noktasına.
Evet.
Neyse uzatmayalım; eski yöntemler çalışıyordu ama pahalıydı, yeni yaklaşım işe işi daha temiz çözüyor. Özellikle büyük volume’larda startup süresini gereksiz yere şişirmiyor. Bu konuyla ilgili Azure DevOps MCP Server Nisan Güncellemesi: Neler Değişti? yazımıza da göz atmanızı tavsiye ederim.
v1.36’da Nasıl Kullanılıyor: Tek Satır Dert Bitiyor
Şunu fark ettim: İşin güzel tarafı, kullanım baya basit. Pod spec’ine bir satır ekliyorsunuz, gerisini kernel hallediyor; ama burada küçük bir numara var, çünkü görünüşte tek satır gıbı dursa da arkada UID eşlemesi, mount davranışı. Runtime tarafı birlikte çalışıyor. Axios npm Saldırısı: Azure Pipelines Kullananlar İçin Rehber yazımızda bu konuya da değinmiştik.
apiVersion: v1
kind: Pod
metadata:
name: izole-yuk
spec:
hostUsers: false
containers:
— name: app
image: fedora:42
securityContext:
runAsUser: 0
volumeMounts:
— name: veri
mountPath: /data
volumes:
— name: veri
persistentVolumeClaim:
claimName: app-pvc
Dikkat ettiyseniz runAsUser: 0 diyorum. Yanı konteyner içinde root gıbı davranıyor. Ama hostUsers: false sayesinde host tarafında başka bir UID’ye map ediliyor; yanı içeride kök, dışarıda bildiğiniz kullanıcı değil. İmaj değiştirmek yok, Dockerfile’a dokunmak yok, init container yazmak yok. Sadece o bayrak.
Önkoşullar — Bunlar Olmadan Çalışmaz
Şahsen, Tabi her şey tıkır tıkır gitmiyor. Birkaç şart var ve açık konuşayım, bunlardan biri eksikse olay hemen tökezliyor:
- Kernel 6.3+ önerilir (5.19 minimum ama bazı edge case’lerde sorun çıkıyor)
- Container runtime olarak containerd 2.0+ veya CRI-O 1.30+ lazım
- Dosya sistemi ID-mapped mounts’u desteklemeli; ext4, xfs, btrfs ve tmpfs tamamdır, NFSv4 işe kısmen iş görüyor
- Node’larda yeterli subuid/subgid range tanımlı olmalı
Geçen ay bir e-ticaret müşterimizde tam buradan patlamıştık. AKS node’ları Ubuntu 22.04 ile gelmişti, kernel 5.15’ti. NFS volume’lar üzerinde mapped mounts düzgün çalışmadı; sonra baktık ki mesele uygulamada değil, node imajındaydı (klasik). Çözüm de node image’ını 24.04’e yükseltmek öldü. Kernel 6.8 gelince tablo toparlandı; yanı plansız geçiş yaparsanız sürpriz yiyebilirsiniz, önce dev/test’te deneyin.
İşin garibi, Evet.
Peki neden?
Neden bu kadar uğraşıyoruz diye soran çok oluyor. Aslında cevap kısa: uygulama içindeki root ihtiyacı ile host güvenliği arasında ince bir denge kuruyorsunuz; böylece konteynerin içinde beklediğiniz izinler duruyor ama node tarafında gereksiz risk almıyorsunuz (bizzat test ettim)
Bence asıl olay burada başlıyor, çünkü uzun süre “root gerekiyorsa risk de gelir” diye düşünüyorduk; şimdi o denklem biraz bozuldu. Fena da olmadı doğrusu.
Açıkçası, Neyse uzatmayalım, konuya dönelim.
Türkiye’deki Kurumsal Yapılar İçin Pratik Değerlendirme
Şimdi işin yerel tarafına gelelim. Türkiye’de bankacılık, sigorta, telekom gıbı regüle alanlarda en çok gördüğüm sıkıntı şu: konteyner güvenliği konuşuluyor ama denetçi bir soru sorunca herkes bir an duraksıyor. KVKK denetiminde “konteyner host’tan izole mi?” dendiğinde verilen klasik cevap da pek iç açıcı olmuyordu; “namespace var, cgroup var, ama tam olarak da değil işte…” Evet, bu cevap yetmiyor.
User namespaces GA olduktan sonra tablo biraz değişti. Evet, root olsa bile host’a etki edemez. Kulağa basit geliyor. Audit tarafında eli baya rahatlatıyor, çünkü artık kağıt üstünde değil, gerçekten anlatılabilir bir sınırınız oluyor.
Bir de para kısmı var tabii. Eskiden privileged konteyner kullanmamak için bazı ekipler Falco, Tetragon, Sysdig gıbı runtime security araçlarına ciddi bütçe ayırıyordu. Bunlar hâlâ iş görüyor, yanlış anlaşılmasın. User namespaces ile birlikte savunma derinliği artınca (şey, bazı şeyleri zaten baştan kapatmış oluyorsunuz) bu araçların yakalaması gereken anomali sayısı da azalıyor. Büyük cluster’larda aylık $5000-$10000 civarı tasarruf görmek hiç şaşırtıcı olmaz.
hostUsers: false‘a abanmanıza gerek yok. Önce iş sürekliliği gelsin. Ama 100+ node’lu bir enterprise yapıda çalışıyorsanız, yeni deployment’ları default olarak user namespace’li başlatmaya başlayın; eski yükleri de sonra yavaş yavaş migrate edersiniz.
Migrasyon Stratejisi: Adım Adım Yol Haritası
“Tamam Aşkın bey, ikna öldüm. Nasıl başlayacağım?” diye soranlar için, lafı gevelemeden söyleyeyim: önce küçük başlayın, sonra genişletin, yoksa iş bir anda karışıyor. Evet, bu kadar basit görünüyor.
1. Adım: Envanter ve Sınıflandırma
İlk iş mevcut workload’ları üç sepete ayırmak. Hani bazen her şey aynıymış gıbı görünür ya, değil; biraz kurcalayınca fark çıkıyor,. Stateless bir web uygulamasıyla PVC kullanan bir servis aynı dertleri taşımıyor, üstüne bir de privileged çalışan işler var ki onları en sona bırakmazsanız başınız ağrıyabilir. GitHub App Token Formatı Değişiyor: ghs_ Sonrası Yeni Dönem yazımızda bu konuya da değinmiştik.
- Yeşil: Stateless, root gerektirmeyen, basit web uygulamaları. Bunlar zaten
runAsNonRoot: trueile çalışıyor olabilir. User namespace eklemek kolay. — ciddi fark yaratıyor - Sarı: Stateful, PVC kullanan, ama root gerektirmeyen. Volume permission sorunlarına dikkat.
- Kırmızı: Privileged çalışan, host network kullanan, CNI ya da CSI plugin’leri. Bunları en sona bırakın.
Açık konuşayım, bu sınıflandırmayı kağıt üstünde yapmak yetmiyor. Bir iki servis için “yeşil” deyip geçiyorsunuz, sonra volume izinleri yüzünden gece yarısı log bakarken buluyorsunuz kendinizi; işte tam o noktada sarı-kırmızı ayrımı baya işe yarıyor. Bu konuyla ilgili Azure MCP Server Artık MCPB Paketi: Runtime Derdi Bitti yazımıza da göz atmanızı tavsiye ederim.
2. Adım: Test Ortamında Doğrulama
Bence, Test cluster’ında hostUsers: false ile birkaç yeşil grup workload deploy edin. Sonra içeride id komutuna bakın; dışarıda gördüğünüz UID ile içerideki root görüntüsünün aynı olmadığını görünce insan şaşırıyor açıkçası. Volume okuma ve yazma testlerini de atlamayın.
Şimdi gelelim işin can alıcı noktasına.
Kendi deneyimimden konuşuyorum, Peki neden? Çünkü teori güzel duruyor ama gerçek hayat başka konuşuyor; özellikle dosya izinleri. Mount edilen dizinler devreye girince küçük görünen bir ayar bütün akışı bozabiliyor, yanı ilk denemede sorun çıkarsa moral bozmayın.
3. Adım: Policy Enforcement
Burada işi biraz sıkılaştırmak gerekiyor. Kyverno ya da Gatekeeper ile policy yazıp “Yeni namespace’lerde hostUsers: false zorunlu” diyebilirsiniz; ben olsam bunu en baştan koyarım,. Sonradan elle kontrol etmek hem yoruyor hem de bir yerde mutlaka unutuluyor.
Yanı, Bu arada Kubernetes Prod Debug Güvenliği: JIT Erişim Rehberi yazımdaki yaklaşıma benzer bir yapı da kurabilirsiniz. Yukarıda bahsettiğim o olay var ya, burada da mantık aynı: erişimi kontrol et, sınırı net köy, sonra sürpriz yaşamazsın.
4. Adım: Kademeli Rollout
Tüm cluster’a tek seferde açmayın; bence burası kritik nokta. Namespace bazında ilerleyin, her hafta 2-3 namespace migrate edin (evet, yavaş gıbı duruyor. Böyle daha temiz gidiyor), çünkü hata çıkarsa rollback yapmak çok daha kolay oluyor (ben de ilk duyduğumda şaşırmıştım)
Neyse uzatmayalım, asıl mesele hız değil kontrol. Siz ne dersiniz? Eğer ortamınız büyükse bu kademeli yaklaşım baya rahatlatıyor; küçük bir sapma olduğunda da nerede koptuğunu hemen görüyorsunuz.
Karşılaşacağınız Sorunlar ve Çözümleri
Açık konuşayım, ben de ilk denediğimde tökezledim. Birkaç klasik tuzak var,. Insan önce “nerede yanlış yaptım” diye bakıyor, sonra meğer işin aslı biraz daha karışık çıkıyor.
Size bir şey söyleyeyim, Sorun 1: hostPath volume’lar — Eğer bir hostPath volume mount ediyorsanız. Dosyalar host tarafında UID 0’a aitse, konteyner o dosyalara erişemeyebiliyor; özellikle güvenlik tarafını sıkı tutuyorsanız bu daha da can sıkıyor. Çözüm? HostPath’ten kaçınmak. Zaten kullanıyorsanız, açık söyleyeyim, güvenlik açısından pek iyi bir yolda değilsiniz.
Sorun 2: NFS volume’lar — NFS server’ınız ID-mapped mounts’u desteklemiyorsa, sistem eski chown davranışına geri dönüyor, yanı iş yine dolanıp dolaşıp yavaşlığa bağlanıyor. Performans da öyle ahım şahım ölmüyor. Çözüm olarak NFSv4.2+ ve modern bir NFS server düşünün (Ganesha, knfsd 5.15+), ama tabi ortamınız buna uyuyor mu, önü da ayrı kontrol etmek lazım.
Ve işler burada ilginçleşiyor.
Sorun 3: subuid range bitti — Default durumda her node 65536 UID range alıyor; ilk bakışta yeterli gıbı duruyor. Çok pod çalıştırınca yetmeyebiliyor, sonra da ortada garip erişim hataları kalıyor (evet, doğru duydunuz). Kubelet config’de userNamespaces.idsPerPod ayarını kontrol edin. Evet, bu detay küçük görünüyor.
Sorun 4: Bazı debug araçları çalışmıyor — strace, perf gıbı araçlar user namespace içinde kısıtlı çalışabiliyor; yanı “niye bu kadar sessiz kaldı” dediğiniz anda aslında yetki duvarına çarpmış oluyorsunuz. Kubelet Fine-Grained Authorization GA: nodes/proxy Devri ile birlikte düşününce, debug erişiminizi de yeniden kurmanız gerekebilir, çünkü eski alışkanlıklarla gidince iş bazen yürümüyor.
Performans Notları ve Beklentiler
Kendi lab’ımda yaptığım testlerde, tabii bunlar mikrobenchmark; üretimde tablo biraz oynayabilir,. Ilk bakışta çıkan sonuçlar şuydu:
- Pod startup: User namespace’siz Pod ile kıyaslandığında fark yok denecek kadar az, yaklaşık 50 ms ekstra
- I/O performansı: ID-mapped mount’lu volume’larda overhead %1’in altında kalıyor
- Network throughput: Burada da gözle görülür bir fark çıkmıyor (bence en önemlisi)
- Memory overhead: Pod başına yaklaşık 2-5 MB ekstra gidiyor, o da namespace structure’ları yüzünden
Hani, İşin aslı, pratikte performans bahanesi pek kalmıyor. Evet, biraz kulağa ters geliyor. “Güvenlik için performansı feda etmem” diyemezsiniz,. Tahmin eder mısınız? Ortada feda edecek kayda değer bir şey yok; en fazla ufak bir ek yük var, o kadar.
Sıkça Sorulan Sorular
User namespaces ile rootless Kubernetes aynı şey mi?
Hayır, aslında bunlar birbirinden farklı şeyler. Rootless Kubernetes dediğimizde kubelet’in ve container runtime’ın da non-root olarak çalışmasından bahsediyoruz. User namespaces işe yalnızca pod’ların izolasyonuyla ilgili bir konu. Yanı ikisi birbirini güzel tamamlıyor, ama bence bu ayrımı net görmek önemli.
Mevcut deployment’larımı değiştirmem gerekecek mi?
Vallahi, Hayır. hostUsers: false opt-in bir özellik, yanı sen eklemedikçe hiçbir şey değişmiyor (en azından benim deneyimim böyle). Yeni deployment’larda yavaş yavaş deneyebilirsin, hani zorunda da değilsin. Tecrübeme göre kademeli geçiş neredeyse her zaman daha sağlıklı.
AKS, EKS, GKE’de bu özellik var mı?
v1.36 çıktığında managed servisler genellikle 2-4 ay içinde destek veriyor. AKS tarafı mesela Microsoft’un kendi release cadence’ına göre ilerliyor. Açıkçası şu an için en sağlıklı yol node image versiyonunu ve kernel versiyonunu kontrol etmek (bizzat test ettim). Cluster yöneticinle bir konuş derim.
Windows konteynerlerde çalışıyor mu?
Bakın, şunu fark ettim: Hayır, bu neredeyse tamamen Linux’a özgü bir özellik. Doğrudan Linux kernel namespace mekanizmasına dayandığı için Windows’ta çalışmıyor. Windows konteynerlerin zaten kendine has bir izolasyon modeli var, mesela Hyper-V isolation gıbı (şaşırtıcı ama gerçek)
Privileged container’a hâlâ ihtiyacım ölür mu?
Dürüst olmak gerekirse, Çoğu durumda hayır. CNI, CSI, monitoring gıbı sistem seviyesi workload’lar bile user namespace içinde namespaced capability’lerle gayet güzel çalışabiliyor (ilk duyduğumda inanamadım). Ama kernel modülü yükleyen ya da host’ta direkt değişiklik yapan eski tıp yükler için hâlâ privileged gerekebilir. Bence bu noktada işin doğasını sorgulamakta fayda var — belki de yanlış aracı seçiyorsunuzdur.
Kaynaklar ve İleri Okuma
Kubernetes v1.36: User Namespaces GA — Resmî Duyuru
User Namespaces Resmî Dokümantasyonu
LWN: ID-mapped mounts Teknik Detayları
KEP-127: User Namespaces Enhancement Proposal
Bu içerik işinize yaradı mı?
Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.



