Cilium Nedir: kube-proxy'siz Kubernetes, eBPF ve Hubble ile Uygulamalı Giriş

Cilium'u kind'da kube-proxy'siz kurdum: 103 service'te kube-proxy 1217 iptables satırı yazdı, Cilium 93'te kaldı. eBPF, identity ve Hubble görsellerle.

Yazan: Mirac Can Yılmaz 10 dk okuma
Cilium Nedir: kube-proxy'siz Kubernetes, eBPF ve Hubble ile Uygulamalı Giriş - platform-mühendisliği yazısının kapak görseli

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ı deathstar servisi, tiefighter (İmparatorluk) ve xwing (İ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ı.

kind cluster'ı Cilium'dan önce: üç node NotReady, CoreDNS pod'ları Pending, kube-proxy yok

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.

cilium status: Cilium, Operator, Envoy DaemonSet ve Hubble Relay OK, 17/17 pod Cilium tarafından yönetiliyor

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.

cilium-dbg status: KubeProxyReplacement True; Routing Host Legacy ve Masquerading IPTables

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:

Diyagram: kube-proxy'de paket KUBE-SERVICES, service başına KUBE-SVC ve KUBE-SEP zincirlerinden geçiyor; Cilium'da eBPF programı LB map'inde tek arama yapıyor. Ölçüm: 117'den 1217'ye karşı 93'te sabit

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:

Ölçüm: kube-proxy'li cluster'da 3 service'te 117 satır, 103 service'te 1217 satır ve 104 KUBE-SVC zinciri; Cilium'lu cluster'da 11 ve 111 service'te 93 satır, 0 zincir, 459 eBPF LB kaydı

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ı.

Diyagram: class=deathstar, org=empire label'ları 8469 kimliğine; iki deathstar pod'u farklı IP'lerde aynı kimlikte. tiefighter 3098, xwing 19069

kubectl get cep ve cilium-dbg identity list: deathstar pod'ları 8469, tiefighter 3098, xwing 19069; kimlikler class ve org label'larından

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:

Bir deathstar pod'u silindi: yeni pod 10.244.2.35 IP'sinde, kimlik değişmedi: 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:

hubble observe: tiefighter (ID 3098) ve xwing (ID 19069) deathstar'a (ID 8469) gidiyor, hepsi FORWARDED

Ş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:

Hubble UI: default namespace servis haritası, tiefighter ve xwing deathstar:80 TCP'ye bağlanıyor; altta forwarded akış tablosu, saniyede 83 akış, 3/3 node

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ı.

cilium connectivity test: All 51 tests (381 actions) successful, 84 tests skipped

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.

k8sServiceHost localhost: worker'lardaki iki ajan Init:0/6, control-plane'deki Running; init log'unda her 5 saniyede localhost:6443'e bağlanma denemesi

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.

hubble observe port-forward olmadan: dial tcp 127.0.0.1:4245 connect: connection refused

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-proxy DaemonSet'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-SVC zinciri sıfır.
  • kubectl get cep: her pod'un bir kimliği var, aynı label'lı pod'lar aynı kimlikte.
  • Hubble UI'da default namespace'inin servis haritası ve forwarded akış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

İnsanın en büyük hatalarından biri de doğru zamanı yanlış kişilerle doldurmaktır.
Charles Bukowski