Kubernetes'te bir pod bir service'e istek attığında, paketi doğru pod'a kimin yönlendirdiğini çoğu ekip bilmez, çünkü bilmesi gerekmez: kube-proxy her node'a iptables kuralları yazar ve iş görür. Ta ki cluster büyüyene kadar. Bu lab'da aynı iki nginx pod'unun önüne 100 service daha ekledim: kube-proxy'li cluster'da bir node'daki iptables kuralları 117 satırdan 1217 satıra çıktı. Cilium'lu cluster'da 111 service'le de 93 satırda kaldı.
Bu yazı Cilium serisinin ilk yazısı. kube-proxy'siz bir kind cluster'ı kurup Cilium'u yüklüyorum ve üç soruya bakıyorum: kube-proxy'nin yerine ne geçti, Cilium pod'ları neye göre tanıyor ve ağda ne olduğunu nasıl görüyoruz. Her adım lab'daki gerçek çıktılarla ve ekran görüntüleriyle.
Sorun ne, neden gerekli
Kubernetes'te ağın iki temel işi var: pod'lara IP verip birbirine ulaştırmak ve "bu service" dendiğinde isteği arkadaki pod'lardan birine çevirmek. Klasik kurulumda ikinci işi kube-proxy yapıyor ve bunu her node'da, her service için kural yazarak yapıyor. Service sayısı arttıkça kural sayısı da artıyor; her değişiklikte de bu kurallar yeniden yazılıyor.
İkinci sorun güvenlikle ilgili. Kim kiminle konuşabilir sorusunu IP adresleriyle cevaplamak Kubernetes'te zor, çünkü pod'lar sürekli yeniden başlıyor ve IP'leri değişiyor. Dün 10.244.2.241 olan ödeme servisi bugün başka bir adreste.
Üçüncüsü görünürlük. Bir istek neden ulaşmadı sorusunun cevabı genelde bir node'a bağlanıp paket yakalamaktan geçiyor; cluster genelinde "hangi pod hangi servise gitti, izin verildi mi" diye sormanın standart bir yolu yok.
Cilium bu üçünü tek bileşende çözüyor: kural tablosu yerine kernel içinde hızlı bir arama, IP yerine label'lardan türeyen bir kimlik ve bütün akışları gösteren Hubble.
Cilium nedir: eBPF, identity ve Hubble
eBPF. Kernel'in içinde, belirli noktalara (bir ağ arayüzüne paket girdiği an, bir bağlantı açıldığı an) takılan ve kernel'i yeniden derlemeden çalışan küçük, doğrulanmış programlar. Cilium bu programları her pod'un ağ arayüzüne takıyor. Paket geldiğinde program bir eBPF map'ine bakıyor: bu service IP'si hangi pod'lara gidiyor, bu kaynak bu hedefe konuşabilir mi. Map bir hash tablosu; beş service da olsa beş bin de, arama aynı sürede.
Identity. Cilium her pod'a label'larından türetilen sayısal bir kimlik veriyor. Aynı label'lara sahip pod'lar aynı kimliği taşıyor; pod yeniden başlayıp IP'si değişse de kimlik değişmiyor. Policy'ler IP'ye değil bu kimliğe yazılıyor. Bu yazıda kimliği görüyoruz; kurallarını serinin üçüncü yazısında yazacağız.
Hubble. Cilium'un veri yolu her akışı zaten görüyor; Hubble bunu bir API'ye ve bir arayüze çıkarıyor. Paket yakalamak yerine cluster genelinde "kim kime gitti, sonuç ne oldu" diye soruyorsunuz.
Neden Cilium: kube-proxy ile karşılaştırma
| kube-proxy (iptables modu) | Cilium (kube-proxy replacement) | |
|---|---|---|
| Service çözümleme | Her node'da, service başına iptables zinciri | eBPF load balancer map'i, hash araması |
| Service sayısı artınca | Kural sayısı doğrusal büyüyor (lab'da service başına ~11 satır) | iptables değişmiyor (lab'da 93 satır sabit) |
| Pod'u tanıma | IP adresi | Label'lardan türeyen identity |
| Görünürlük | Yok; node'da paket yakalama | Hubble: CLI ve arayüz, cluster genelinde |
| Bedeli | Basit, herkes biliyor | Öğrenme eğrisi; hata ayıklama cilium-dbg gerektiriyor |
Calico'nun da kendi eBPF veri yolu var ve ciddi bir alternatif. Bu seride Cilium'u seçmemin sebebi, sonraki yazılarda ihtiyaç duyacağım şeylerin (LoadBalancer IP'leri, Gateway API, şifreleme, çok cluster) aynı ajanın içinde gelmesi; ayrı ayrı bileşen kurup işletmek yerine tek bir şeyi öğrenmek.
Neden kind ve neden kube-proxy'siz? "Cilium kube-proxy'nin yerine geçiyor" cümlesini okumakla, kube-proxy pod'u hiç olmayan bir cluster'da service'lerin çalıştığını görmek farklı şeyler. kind bu cluster'ı birkaç dakikada veriyor, iş bitince silmek tek komut.
Lab: ne kurdum
Apple Silicon bir Mac'te, Colima (4 CPU / 8 GB, kernel 6.8) üzerinde:
- kind v0.33 ile 1 control plane + 2 worker, Kubernetes v1.37.0. Varsayılan ağ eklentisi (CNI) ve kube-proxy kapalı.
- Cilium 1.20.2, Helm ile: kube-proxy replacement açık, VXLAN tünel, Hubble Relay ve Hubble UI açık.
- Demo olarak Cilium'un kendi Star Wars uygulaması: iki replikalı
deathstarservisi,tiefighter(İmparatorluk) vexwing(İttifak). Seri boyunca aynı üç uygulamayı göreceğiz.
Bütün adımlar bir Makefile'da; kurulumun tamamı sıfırdan yaklaşık 2,5 dakika sürüyor (cluster 28 sn, Cilium 121 sn).
Adım 1 — CNI'sız ve kube-proxy'siz kind: node'lar NotReady
Cluster ilk açıldığında node'lar NotReady, CoreDNS pod'ları Pending. Bu beklenen durum: pod'lara IP verecek bir ağ eklentisi yok. kube-proxy de hiç kurulmadı.

Adım 2 — Cilium kurulumu ve cilium status
Cilium Helm ile kuruldu; cilium status bütün bileşenleri OK gösterdi ve node'lar Ready oldu. Her node'da bir Cilium ajanı (DaemonSet), bir Envoy (L7 trafik için, sonraki yazılarda), bir operator, Hubble Relay ve Hubble UI çalışıyor.

Adım 3 — kube-proxy'nin yerine ne geldi: eBPF service map
Ajanın içindeki cilium-dbg status iki şeyi söylüyor. Birincisi KubeProxyReplacement: True: service çözümlemesi artık eBPF'te. İkincisi, beklemediğim bir şey: Masquerading: IPTables ve Host: Legacy. Yani kube-proxy'siz bir cluster'da da Cilium, dışarı çıkan trafiğin adres çevirisi için hâlâ iptables kullanıyor. Bunu da eBPF'e taşımak ayrı bir ayar ve bu yazıda açmadım; service tarafı tamamen eBPF.

kube-proxy'li bir cluster'da paketin yolu, service başına bir zincirden geçiyor. Cilium'da ise pod'un arayüzündeki bir eBPF programı tek bir tabloda arama yapıyor:

Farkı ölçmek için iki cluster açtım: biri kind'ın varsayılanı (kube-proxy'li), diğeri bu lab. İkisinde de aynı iki nginx pod'unun önüne 100 service ekleyip bir node'daki iptables satırlarını saydım:

Küçük bir cluster'da fark küçük: 3 service'le 117'ye 93. Fark service sayısıyla açılıyor: kube-proxy her service için yaklaşık 11 satır ve bir zincir ekliyor, Cilium'da iptables hiç değişmiyor; service'ler eBPF load balancer tablosunda duruyor (459 kayıt). Ölçümü ikinci kez tekrarladığımda başlangıç sayıları biraz farklı çıktı (kube-proxy'de 105, Cilium'da 89; node'daki diğer kurallar zamanla değişiyor), ama sonuç aynıydı: kube-proxy'de 1217, Cilium'da hiç artış yok.
Adım 4 — Cilium identity: IP değil, label
Demo uygulaması kurulunca her pod bir CiliumEndpoint oldu ve bir kimlik aldı. İki deathstar replikası farklı node'larda, farklı IP'lerde ama aynı kimlikte (8469), çünkü label'ları aynı.


Kimliğin IP'ye bağlı olmadığını görmek için bir deathstar pod'unu sildim. Yerine gelen pod yeni bir IP aldı (10.244.2.35), kimliği yine 8469:

Bu, serinin geri kalanının temeli: policy'ler bu kimliklere yazılacak, pod'lar ne kadar yer değiştirirse değiştirsin kurallar aynı kalacak. Şu an policy yok, her kimlik her kimliğe konuşabiliyor: tiefighter da xwing de Ölüm Yıldızı'na iniş yapabiliyor ("Ship landed"). İkincisinin kabul edilmesi serinin üçüncü yazısının problemi.
Adım 5 — Hubble ile akışları görmek: CLI ve Hubble UI
Hubble Relay bütün node'lardaki ajanlardan akışları topluyor. hubble observe ile deathstar'a giden akışlara baktım. Her satırda kaynak pod ve kimliği, hedef pod ve kimliği, yön ve sonuç (verdict) var:

Şu an tek sonuç FORWARDED. Policy gelince DROPPED da görünecek; L7 policy gelince HTTP metodu ve yolu da bu satırlara girecek.
Aynı bilgi Hubble UI'da bir servis haritası olarak duruyor. default namespace'inde tiefighter ve xwing'in deathstar'ın 80 numaralı portuna gittiği çiziliyor; altta da akış tablosu:

Adım 6 — cilium connectivity test
Cilium'un kendi bağlantı testi, kendi namespace'ine istemci ve sunucu pod'ları açıp senaryoları deniyor. Pod'dan pod'a, pod'dan service'e ve L7 testleriyle daraltılmış hali Colima'da 574 saniye sürdü: 51 test (381 adım) başarılı, 84 test bu daraltma yüzünden atlandı.

Nerede patladı: 4 gerçek hata
1. Establishing connection to apiserver ... localhost:6443: k8sServiceHost'a localhost yazınca
kube-proxy'siz bir cluster'da Cilium, API server'a nasıl ulaşacağını kendisi bilmek zorunda; service IP'leri henüz çalışmıyor, çünkü onları çalıştıracak olan Cilium. Bu yüzden Helm'de API server'ın adresini açıkça veriyoruz. Ben bir kez localhost yazdım, kubeconfig'de öyle göründüğü için.
Sonuç sinsi oldu: hiçbir hata satırı yok. Worker node'lardaki ajanlar Init:0/6'da takılı kaldı; init container'ı beş saniyede bir sessizce "Establishing connection to apiserver ... localhost:6443" yazıp bekledi. Control-plane node'undaki ajan ise çalıştı, çünkü o node'da localhost:6443 gerçekten API server.

Doğrusu, control-plane container'ının Docker ağındaki adı (cilium-lab-control-plane). Production'da bu, API server'ın load balancer adresi.
2. dial tcp 127.0.0.1:4245: connect: connection refused: Hubble CLI port-forward'sız
Hubble CLI, cluster içindeki Hubble Relay'e bağlanıyor. Port-forward açmadan çalıştırınca bu hatayı veriyor. cilium hubble port-forward açınca düzeldi.

3. Ajanın içinden hubble observe boş döndü
Port-forward ile uğraşmamak için hubble observe'u bir Cilium ajanının içinden çalıştırdım; demo trafiği olduğu halde çıktı boştu. Ajanın içindeki Hubble sadece o ajanın node'unu görüyor; demo pod'ları başka node'lardaydı. Cluster'ın tamamını görmek için Relay'den bakmak gerekiyor (#2'deki port-forward ile).
4. kube-proxy yok ama Masquerading hâlâ "IPTables"
"kube-proxy'siz" kurulumdan sonra iptables'ın tamamen gittiğini düşünüyordum. cilium-dbg status öyle demiyor: dışarı giden trafiğin adres çevirisi (masquerading) ve host routing varsayılan kurulumda iptables ve legacy yolla yapılıyor. Service'ler eBPF'te (#3'teki ölçüm bunu gösteriyor), ama "sıfır iptables" değil: node'da 93 satır var. eBPF masquerading ayrı bir Helm ayarı ve kernel desteği istiyor; bu yazıda açmadım.
Doğrulama
Lab'ın doğru kurulduğunu şunlar gösteriyor:
kube-proxyDaemonSet'i yok (NotFound), ama service'ler çalışıyor.cilium status: Cilium, Operator, Envoy ve Hubble Relay OK; node'lar Ready.cilium-dbg status:KubeProxyReplacement: True.- Bir node'da iptables satır sayısı service eklenince değişmiyor;
KUBE-SVCzinciri sıfır. kubectl get cep: her pod'un bir kimliği var, aynı label'lı pod'lar aynı kimlikte.- Hubble UI'da
defaultnamespace'inin servis haritası veforwardedakışlar. cilium connectivity test: 51/51 başarılı.
Kaynaklar: Cilium dokümanları Kubernetes Without kube-proxy, Identity-Relevant Labels, Setting up Hubble Observability ve Connectivity Test; kind Configuration (disableDefaultCNI, kubeProxyMode); Kubernetes Virtual IPs and Service Proxies.
Seride sonraki
Bu, Cilium Uçtan Uca serisinin ilk yazısı. Sonraki: Cilium IPAM: Pod CIDR Modları, LB-IPAM ve L2 Announcement ile Bare-Metal LoadBalancer. Önceki yazı: Kyverno ile Kubernetes Admission Webhook.
Bu lab'ın kodu (kind yapılandırması, Cilium değerleri, script'ler ve Makefile) Buy Me a Coffee destekçileriyle paylaşılıyor; destek olduktan sonra erişim e-postayla geliyor: https://buymeacoffee.com/miraccanyilmaz