eBPF-basiertes Networking mit Cilium - Was ist das und was kann es?

20.10.2021 Tom Trapp
Cloud DevOps k8s devops framework handson

Cilium ist ein Open-Source eBPF-basiertes Networking, Security und Observability Plugin für Kubernetes und weitere Container-Orchestrierungs-Tools. Cilium ist hauptsächlich in Golang geschrieben ist. Hinter Cilium steckt die Firma Isovalent; der erste Commit bei Cilium ist nun bereits 6 Jahre her. Bevor wir uns Cilium genauer anschauen wollen wir gemeinsam verstehen, was eBPF ist und was es genau so besonders macht.

eBPF

Heutzutage ist das Linux System sehr weit und tief in der Cloud-Landschaft verbreitet. Nahezu jeder Container setzt auf einem Linux-Kernel auf. Dabei müssen Themen und Tools wie Observability & Security immer 'on top' im sogenannten User space des Systems installiert und konfiguriert werden. In diesem User Space hat man leider keinen oder nur limitierten Zugriff auf System- und Kernel-Ressourcen. Somit müssen bestimmte Kontrollmechanismen immer zusätzlich -beispielsweise als Sidecar Proxy- implementiert werden. Dies sorgt oft für eine höhere Komplexität und eine höhere Latenz. Es gibt zwar bereits eine Möglichkeit, den Kernel um gewünschte Funktionalitäten zu erweitern: Kernel Modules. Diese sind aber meist schwer zu implementieren und stellen ein grosses Risiko zur Laufzeit des Betriebssystems dar. Wenn im Code etwas schiefläuft, crashed direkt der ganze Kernel und somit auch der zu betreibende Container.

eBPF (extended Berkeley Packet Filter) stellt hier eine neue Möglichkeit dar, Code im Kernel auszuführen. Es wird oft auch als Ggeneral purpose execution engine beschrieben. So hat man die Möglichkeit, dem Kernel neue Funktionalitäten beizubringen ohne dabei das laufende System zu gefährden.

Technisch gesehen wird Bytecode eines Programms oder Tools im User Space per bdf() system call in eBPF eingespielt. Ein JIT-compiler (JIT steht für Just In Time) kompiliert diesen Bytecode dann zu einem sehr System-nahen nativen Maschinencode. Das Programm wird dann auf bestimmte Hooks und Events im Kernel Space gebunden und dort ausgeführt. Auf jeden dieser Hooks kann man dann seine eigene Funktion binden und beispielsweise sämtlichen IPv4-Traffic verbieten, oder auch ständige Metriken über laufende Prozesse sammeln.

Der sogenannte Verifier sorgt dabei für Sicherheit und Stabilität. Hier werden Infinite Loops, Crashes, o.ä. abgefangen und gehandelt. Somit ist der eigentliche Kernel von Fehlern im Code nicht betroffen. Ausserdem sorgt es mit dem rechtzeitigem Terminieren und anderen, weiteren Methoden für Sicherheit; damit kein Memory mit sensiblen Daten freigegeben wird.

Ein weiterer Vorteil ist der Zugriff auf unterschiedliche, Kernel-Helper-Funktionen und Datentypen. Ausserdem gibt es eine Kernel memory area, welche zum Teilen unterschiedlicher Key/Value-Daten mit anderen eBPF-Programmen oder gar _ User Space_-Programmen genutzt werden kann.

Quelle: 09.10.2021 - https://ebpf.io/what-is-ebpf

Die Einsatzgebiete sind hier breit gefächert, meist findet man eBPF im Themen wie Networking, Tracing, Monitoring & Security da man direkten Zugriff auf die Ressourcen hat.

Grosse Player wie beispielsweise Facebook, Netflix oder Google haben eBPF bereits im Einsatz. Nicht zuletzt dadurch ist eBPF eines der am schnellsten wachsenden Subsystemen des GNU/Linux-Betriebssystems geworden.

Tooling

Das Tooling in eBPF ist sehr breit gefächert und es gibt unterschiedliche Compiler und Toolchains, welche beispielsweise aus einem normalen C-Programm ePBF-fähigen Bytecode generieren können. Auch hier werden Toolchains und Compiler-Targets für weitere Sprachen wie Python, Golang, Rust angeboten.

eBPF selbst liefert eine grosse Menge an Helper-Funktionen und Programmen als Schnittstellen zu nahezu allen nativen Komponente wie Storage, Hardware, Network, und mehr. Die aktuell verfügbaren Tools findet man unter ebpf.io/projects. An der Anzahl der Tools sieht man sehr gut, dass das ganze System noch sehr jung ist und sich noch in einer Wachstumsphase befindet.

eBPF in Cloud Native Environments

Da wie bereits erwähnt der Linux Kernel sehr tief in der Cloud-Landschaft verankert ist, ist eBPF wie gemacht für den Einsatz in Cloud native Applikationen und Umgebungen. Beispielsweise können wir ePBF die altbekannten IP-Tables, welche im Gegensatz zu eBPF langsam und träge sind, abgelöst werden. Ebenso kann beispielweise beim Einsatz eines Proxy-Sidecar-Containers mit Envoy der Netzwerkprozess enorm vereinfacht werden.

Beides erlaubt nahezu die identische Funktionalität, beim Einsatz von eBPF gibt es 'under the hood' viel weniger eigen implementiere Prozesse und komplexe Architekturen. Die Einsatzgebiete und Use Cases sind sehr breit gefächert, da man seine komplett eigenen Funktionalitäten im Kernel (egal ob Node oder Container) erstellen und implementieren kann.

Cilium

Cilium ist eines der ersten und am meist fortgeschrittensten Use-Cases von eBPF, welches das Ziel verfolgt, die Vorteile von eBPF in die Kubernetes-Welt zu lüpfen. Es adressiert die neuartigen Anforderungen in Bezug auf Skalierbarkeit, Sicherheit und Visibilität, welche Container-Workloads heutzutage haben. Grundlegend kann man das Einsatzgebiet von Cilium in drei grosse Bereiche aufteilen:

  • Networking
  • Observability
  • Security

Networking

Cilium fungiert als CNI-Implementation (CNI steht für Cluster Network Interface) welches auf skalierbare, sehr grosse Container Workloads spezialisiert ist. Hierbei finden wir wieder den klassischen Control-, Data-Plane Aufbau. Für Loadbalancing, Tracing und Ingress/Egress-Rules wird hier eBPF (statt IP-Tables) verwendet. Ein weiteres Feature ist das Service-basierte LocalBalancing, welches ebenfalls vollwertig auf eBPF setzt.

Mit Ciliums Cluster Mesh lassen sich mehrere Cluster miteinander verbinden: Dies kommt beispielsweise im Fall von Failover-, Recovery- oder Geo-Splitting zum Einsatz und lässt sich ohne zusätzliche Gateways oder Proxies betreiben. Hier kommen Technologien wie Tunneling oder Routing zum Einsatz.

Observability

Cilium liefert ein Network-Monitoring, welches laut eigenen Angaben Identify Aware ist. Das bedeutet, dass Informationen weit mehr wie nur die IP geloggt und verfolgt werden können. Durch den Einsatz von eBPF können sämtliche Kubernetes Labels für internen Traffic sowie DNS Namen o.ä. für externen Traffic eingesehen werden. Dies ist speziell beim Troubleshooting oder beim Analysieren von Konnektivitätsproblemen sehr hilfreich.

Zusätzlich werden sämtliche Layer 3, 4 oder 7 Metriken auch in einem Prometheus-kompatiblen Format ausgeliefert. Hier werden zusätzliche Informationen über Network Policies mitgeliefert. So kann man direkt am Traffic Entry erkennen, ob und von welcher Network Policy etwas unterbunden wurde.

Ein weiterer grosser Vorteil ist die sogenannte Api-Award Network Observability. Traditionelle Firewalls sind auf den IP und TCP Layer limitiert, wobei Cilium auf Layer 7 API-basierte Informationen zu Protokollen wie HTTP, gRPC und Kafka bereitstellen kann. Auch diese Daten sind für Huddle und Prometheus verfügbar. Mittels TLS Interception kann auch HTTP(S)-Traffic genau unter die Lupe genommen werden.

Security

Grundlegend implementiert und erweiter Cilium die Kubernetes Network Policy und bietet zusätzlich, neben Label- und CIDR-Matching, noch ein DNS- und API-bewusstes Matching. So kann beispielsweise per Ciliums Network Policy der Zugriff auf bestimmte API-Pfade gesteuert und wenn nötig unterbunden werden. Dies erlaubt es, in einem Real-World-Szenario, beispielsweise Monitoring- oder Maintenance-Endpoints nur bestimmten Services freizugeben.

Die bereits im vorherigen Abschnitt beschrieben Informationen über sämtlichen Netzwerkverkehr können für Audit oder Langzeitanalysen z.B. im Angriffsfall dauerhaft und lange gespeichert werden.

Mittels der sogenannten Transparent Encyption sorgt Cilium für eine dauerhafte Verschlüsslung des Traffics innerhalb oder zwischen den Cluster(n). Hierbei wird das Kernel-eigene Feature IP Sec verwendet, es bedarf nur einer einmaligen Konfiguration in Cilium, keiner Sidecar-Proxies oder applikatorischen Changes.

Hands on

Cilium

Nun wollen wir Cilium installieren und es etwas genauer kennenlernen, der Einfachheit halber nutzen wir hierfür ein lokal installiertes K8s-Cluster mit minikube.

Falls Minikube noch nicht installiert ist, können wir dies auf macOS wie folgt installieren:

brew install minikube

Anschliessend starten wir Minicube und installieren die Cilium-CLI. Diese CLI erlaubt es uns, den eigentlichen Teil von Cilium zu installieren, den aktuellen Status der Installation zu prüfen und zahlreiche Features zu aktivieren oder deaktivieren.

minikube start --network-plugin=cni

curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-darwin-amd64.tar.gz{,.sha256sum}
shasum -a 256 -c cilium-darwin-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-darwin-amd64.tar.gz /usr/local/bin
rm cilium-darwin-amd64.tar.gz{,.sha256sum}

Nun ist die Cilium CLI parat und wir können Cilium selbst in unser Kubernetes-Cluster installieren. Die CLI erkennt hierbei unser lokal installiertes minicube und nutzt die bestmögliche Konfiguration hierfür.

cilium install

Via cilium status können wir den Status unsere Installation einsehen und validieren, ob alles korrekt installiert wurde.

Glücklicherweise bietet Cilium uns ein Kommando für automatisierte Tests unsere Cluster-Installation:

cilium connectivity test

Hubble

Nun wollen wir Hubble, den Observability Layer von Cilium aktivieren und Cluster-weite Informationen über den Network- und Security-Layer zu erhalten. Zusätzlich aktivieren wir hier die UI-Option, um ein grafisches Interface zu haben.

Hierfür installieren wir in diesem Schritt dann noch den Hubble-Client und leiten den Traffic entsprechend an Huddle weiter.

cilium hubble enable --ui

export HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-darwin-amd64.tar.gz{,.sha256sum}
shasum -a 256 -c hubble-darwin-amd64.tar.gz.sha256sum
sudo tar xzvfC hubble-darwin-amd64.tar.gz /usr/local/bin
rm hubble-darwin-amd64.tar.gz{,.sha256sum}

cilium hubble port-forward&

Nun können wir mit hubble status unsere Installation prüfen und mit hubble observe die sogenannte Flow-API abfragen, um Informationen über den Netzwerkverkehr zu bekommen.

Zusätzlich können wir mit cilium hubble ui die UI starten, welche dann unter http://localhost:12000/ aufrufbar ist. Hier können wir nun mit cilium connectivity test etwas Traffic generieren und diesen genauer in der UI unter die Lupe nehmen.

Hier sehen wir direkt auf einen Blick die Verbindungen zwischen den einzelnen Pods, welche Calls erfolgreich waren und weitere Netzwerkinformationen.

Nun wollen wir ein Demo-Projekt installieren und Network Policies und Traffic genauer unter die Lupe nehmen. Dies können wir direkt via kubectl einlesen und starten.

kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.10.4/examples/minikube/http-sw-app.yaml

Mit folgendem Command können wir dem Cilium-Pod bestimmte Informationen entlocken, hier bekommen wir beispielsweise alle Endpoints zurück. In Cilium entspricht jeder Pod einem Endpunkt.

kubectl -n kube-system exec <cilium-pod-name> -- cilium endpoint list

In der Ausgabe ist zu sehen, dass aktuell keine Ingress- oder Egress-Policy angewandt ist.

Dies bedeutet, dass all unsere Calls im Netzwerk erlaubt sind und wir folgende zwei Kommandos erfolgreich ausführen können.

kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing

Nun lernen wir das erste eigene Objekt von Cilium, die CiliumNetworkPolicy kennen. Hiermit lassen sich, wie oben beschrieben, weitere Matchings im Gegensatz zur K8s-Standard-NetworkPolicy einstellen.

So schränken wir auf Layer 3 & 4 den Traffic zum 'deathstar'-Service ein.

cat <<EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
EOF

Anschliessend sehen wir, dass unser erster Call vom 'xwing'-Service auf den 'deathstar'-Service nun mit einem Timout fehlschlägt. In der Auflistung der Endpoint sehen wir, dass die Ingress-Policy nun Enabled ist.

Neben den Layer 3 & 4 Network-Policies können auch Layer 7 Network-Policies zum Blocken bestimmter Pfade verwendet werden.

Fazit

Was macht Cilium nun anders als andere Network- und Observability-Provider? Diese Frage ist leider schwer zu beantworten, da die ähnliche/gleiche Funktionalitäten auch von anderen Tools wie Istio, Ambassador usw. angeboten werden.

Cilium macht seine Aufgaben aber unter der Haube auf eine andere Art und Weise, genau hier liegt auch der grosse Vorteil: Die eigentliche Funktionalität ist hier viel näher am Kernel, schneller und der allgemeine Prozess ist leichtgewichtiger. Ob und wann der Einsatz von Cilium gegenüber anderen Playern sinnvoll oder angebracht ist lässt sich sicher ausdiskutieren.

Tom Trapp – Problemlöser, Innovator, Sportler. Am liebsten feilt Tom den ganzen Tag an der moderner Software und legt viel Wert auf objektiv sauberen, leanen Code.