Tools Used in Kubernetes · Cilium

Cilium

Every CNI plugin has to satisfy the same flat, no-NAT Pod network that Networking & the CNI lays out — what separates them is what they do with the CPU cycles once that baseline is met. Cilium's answer is eBPF: instead of the kernel walking a chain of iptables rules for every packet, small, verified programs run directly inside the kernel and make forwarding and policy decisions from a hash-map lookup keyed on a workload's identity rather than its ephemeral IP address. That one architectural choice is why Cilium can replace kube-proxy outright, enforce policy on HTTP methods and DNS names instead of just ports, and hand you Hubble — a live, per-flow record of exactly which rule allowed or dropped every connection in the cluster. This page stays at the level you'll actually operate day to day: what you install, what you write, what breaks at 2 a.m. Cilium's own CNCF certification and the wider Kubestronaut ladder get their full treatment elsewhere on this platform, linked out to rather than repeated here.

☺ Explain it like I'm 10

Picture a school hallway with a big paper binder taped to every classroom door. To walk in, a hall monitor has to flip through the binder, page by page, checking your name against the list — fine with ten kids, painfully slow with a thousand. Cilium rips out every binder and builds a smart scanner right into the doorframe itself: it recognizes you instantly, by your actual badge (that's your identity, built from your labels) rather than by a note you're carrying that changes every time you get a new locker (that's your IP address). And bolted above every door is a tiny camera, quietly recording who walked through and who got turned away — that's Hubble. Nobody has to guess what happened in the hallway anymore; you just watch the footage.

🐦Your host for this topic: Pip the Hummingbird — the fastest wings in the cluster, who already cares about exactly one thing: the right bytes reaching the right Pod. Cilium just hands Pip a kernel-level shortcut and a camera to prove it happened.

What Cilium replaces, and why eBPF

☺ Like you're 10: Same job as any other CNI — get every Pod an address and let it talk — just done by teaching the kernel itself the rules instead of handing it a paper checklist.

At its floor, Cilium is an ordinary CNI plugin: on ADD it allocates a Pod IP, wires up the network namespace, and programs routes, exactly the contract Networking & the CNI walks through for any plugin. What makes it worth a page of its own is everything built on top of that floor. A conventional datapath — Flannel's overlay, or Calico in its default mode — still leans on iptables or IPVS to turn a Service into forwarding rules, evaluated by walking a list or hashing on IP address for every packet. Cilium instead attaches eBPF programs at hook points in the kernel's own packet path — the network interface (XDP) and the traffic-control layer (tc) — that make the same decisions via a kernel hash-map lookup, keyed on a security identity Cilium derives from a Pod's labels rather than its IP.

◆ Key idea

Two sentences carry almost everything else on this page. eBPF replaces per-packet rule-chain traversal with a hash-map lookup, so cost stops scaling with the number of Services or policies. And Cilium enforces policy on identity, not IP — every Pod carrying the same labels shares one numeric identity cluster-wide, so rescheduling a Pod, scaling it to fifty replicas, or having its IP recycled by a totally unrelated Pod tomorrow changes nothing about what a policy written today still means.

Because Cilium's datapath already knows what a packet is and where it's going, adding "and log the verdict" or "and check the HTTP path" is cheap — which is exactly where Hubble and Layer-7 policy come from later on this page. Cilium is a CNCF-graduated project (the first CNI to reach that maturity level), and it's increasingly the datapath underneath managed offerings you may already be running without knowing it — GKE's Dataplane V2 is Cilium under a different name.

Architecture: the agent, the operator, and Hubble

☺ Like you're 10: One helper rides along on every machine teaching its kernel the rules; one boss in the cluster hands out ID badges; one camera crew watches it all happen.

Three pieces do almost all the work. cilium-agent runs as a DaemonSet, one Pod per node — it watches kube-apiserver for Services, Pods, and CiliumNetworkPolicy objects, compiles what it sees into eBPF programs and maps, and loads them into that node's kernel; it's also the process that installs the CNI plugin binary and drives that node's Envoy proxy for anything a policy asks to inspect at Layer 7. cilium-operator runs as a small Deployment handling cluster-wide bookkeeping that doesn't belong on every node — IPAM block allocation and garbage-collecting stale identity objects chief among them; it sits off the datapath entirely, so if it's briefly unavailable, traffic already flowing keeps flowing, though new Pods may stall waiting on an IP. Hubble is embedded in every agent, reading flow events straight out of the eBPF datapath; hubble-relay fans those per-node streams out into one cluster-wide API, and hubble-ui turns it into a live service map.

Kubernetes API server Services · Pods · CiliumNetworkPolicy cilium-operator IPAM · identity GC 🐦 Hubble Relay + UI cluster-wide flow record worker node cilium-agent compiles + loads BPF maps Pod · cart-api identity 4102 Pod · inventory-api identity 5590 Linux kernel · eBPF datapath (tc / XDP) identity lookup + Service load-balancing — no iptables, no kube-proxy watch flow verdicts labels → security identity → BPF map lookup — cost stays flat whether the cluster has 10 Services or 10,000

The Helm values and CiliumNetworkPolicy you actually write

☺ Like you're 10: One file to switch the smart scanners on, and a couple of small rule files saying who's allowed through which door, and about what.

Cilium ships as a Helm chart, so the entire datapath configuration is one reviewable, GitOps-managed values file rather than a pile of one-off kubectl flags — install with helm install cilium cilium/cilium -f values.yaml, or with the cilium CLI, which is a thin wrapper around the same chart.

# values.yaml — a reasonable production-shaped starting point
kubeProxyReplacement: true          # eBPF handles ClusterIP/NodePort/LoadBalancer — delete kube-proxy
k8sServiceHost: api.cluster.internal   # REQUIRED once kube-proxy is gone: how the agent reaches the API
k8sServicePort: 6443

routingMode: tunnel                 # "tunnel" (portable default) or "native" (faster, needs a cooperating fabric
tunnelProtocol: vxlan                #   or BGP) — see the routing-strategy callout in Networking & the CNI

ipam:
  mode: cluster-pool                 # kubernetes | cluster-pool | eni | azure
  operator:
    clusterPoolIPv4PodCIDRList: ["10.244.0.0/16"]

hubble:
  enabled: true
  relay: { enabled: true }
  ui:    { enabled: true }
  metrics:
    enabled: [dns, drop, tcp, flow]

The object you'll actually hand-write most often is CiliumNetworkPolicy — the same grammar as a standard NetworkPolicy, extended with the parts identity makes possible. The rule below allows cart-api to call exactly one HTTP method and path on inventory-api, and nothing else — a plain NetworkPolicy can only say "TCP port 8080, yes or no," never "which method, which path."

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: inventory-api-ingress
  namespace: shop
spec:
  endpointSelector:                  # which Pods this policy protects
    matchLabels:
      app: inventory-api
  ingress:
    - fromEndpoints:                 # who may call — by LABELS, never by IP
        - matchLabels:
            app: cart-api
            io.kubernetes.pod.namespace: shop
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:                    # L7: parsed by the per-node Envoy proxy the agent drives
              - method: "GET"
                path: "/v1/stock/[0-9]+"
⚠ Selecting a Pod flips that direction to default-deny

The single most common way a Cilium rollout takes down its own service. A Pod with no policy selecting it allows everything. The instant any policy selects it, that direction — ingress or egress, independently — becomes default-deny, and only what's explicitly listed still works. Write an egress rule for inventory-api's database and stop there, and every hostname lookup from that Pod fails a moment later — DNS to kube-dns was never on the list either. Always allow UDP/TCP 53 to kube-dns in the very first egress rule of any default-deny policy, exactly as the standard NetworkPolicy DNS trap already warns about — Cilium doesn't relax that rule, it just gives you a faster way to prove it happened, covered two sections down.

Kube-proxy replacement: one eBPF program instead of two systems

☺ Like you're 10: Instead of one worker wiring the hallway and a second, separate worker manually forwarding every phone call down it, one system does both jobs at once and never has to check with anyone else.

Networking & the CNI already covers kube-proxy's iptables and IPVS dataplanes as two independent watchers of the same Service and EndpointSlice objects, each reprogramming a node's rules on its own schedule. Cilium's eBPF datapath can absorb that job entirely: set kubeProxyReplacement: true and ClusterIP, NodePort, LoadBalancer, and session affinity are all implemented as eBPF programs, and kube-proxy is deleted from the cluster outright. Because ClusterIP translation happens at the socket layer — a Pod connecting to a Service gets rewritten to a real backend address before a packet is ever built — there's no per-packet DNAT cost the way there is with iptables, and no separate ruleset that can drift out of sync with what the CNI already knows about the Pod network.

Standard CNI + kube-proxy CNI plugin wires Pod network — veth, routes kube-proxy separate DaemonSet, own watch loop two components, two loops, two places to debug Cilium · kubeProxyReplacement: true eBPF datapath wires the Pod network AND load-balances Services — one program kube-proxy deleted entirely — one loop, one map, one place to look
$ kubectl -n kube-system exec ds/cilium -- cilium status | grep -i KubeProxyReplacement
KubeProxyReplacement:   True

$ kubectl -n kube-system get daemonset kube-proxy
Error from server (NotFound): daemonsets.apps "kube-proxy" not found   # expected — it's gone

Hubble and day-to-day commands

☺ Like you're 10: One command asks "is everything healthy?", one runs a full obstacle course to prove it, and one shows you every hallway trip and whether it made it through.

This is the capability that changes how an incident actually feels. Instead of arguing about whether a policy is blocking traffic, you watch the verdict.

# Install / upgrade — pin a version explicitly and read that release's notes first
$ cilium install --version 1.16.4
$ cilium status --wait                         # agents, operator, Hubble all healthy?
$ cilium connectivity test                     # deploys a test namespace, runs the full check suite

# The single most useful command in this entire page
$ hubble observe --verdict DROPPED -f
shop/cart-api-7d9f:52344 (ID:4102) -> shop/inventory-api-6b4c:8080 (ID:5590) Policy denied DROPPED (TCP Flags: SYN)

# Narrow it down
$ hubble observe --namespace shop --pod cart-api --verdict DROPPED --last 50
$ hubble observe --protocol http --http-status 403     # L7 verdicts, if an http rule applies
$ hubble observe --to-fqdn "api.stripe.com"             # did an FQDN egress rule resolve it?

# Ground truth straight from one node's agent
$ CILIUM_POD=$(kubectl -n kube-system get pods -l k8s-app=cilium -o name | head -1)
$ kubectl -n kube-system exec -it $CILIUM_POD -- cilium-dbg endpoint list   # every Pod: identity + policy state
$ kubectl -n kube-system exec -it $CILIUM_POD -- cilium-dbg identity list  # numeric identity ↔ label-set table
$ kubectl -n kube-system exec -it $CILIUM_POD -- cilium-dbg bpf lb list    # the Service load-balancing map itself
🐦 Pip's-eye view

"I used to treat a dropped connection as a mystery to be solved from the outside — check the Service, check the endpoints, check the Pod logs, shrug. The first time I ran hubble observe --verdict DROPPED -f against a real outage I felt a little cheated, honestly — it just told me, in one line, which policy denied which flow and why. There was no detective work left to do. Now that command is the very first thing I run, before I even open a manifest, because it turns 'is the network broken' from a theory into a fact in about four seconds."

Gotchas

☺ Like you're 10: Four things bite almost everyone once: forgetting DNS, an old kernel, expecting the fancy rules to be free, and trying to swap CNIs while the plane's still in the air.

Choosing Cilium vs. Calico vs. Flannel

☺ Like you're 10: Three road crews with three different budgets — a bare road, a road crew that checks IDs, and a road crew that wired cameras into every corner.

DimensionCalicoCilium
Datapathiptables by default, BGP native routing; optional eBPF modeeBPF everywhere, in-kernel
Policy identityIP- and selector-based, with a mature GlobalNetworkPolicy extensionIdentity-based (derived from labels), plus L7 HTTP/Kafka and FQDN egress
kube-proxy replacementPartial, in its eBPF modeFull — kube-proxy can be deleted entirely
ObservabilityFlow logsHubble — live, per-flow verdicts and a service map
Reach for it whenA mature, conventional policy engine and BGP routing are enough, without adopting eBPF everywhereService count or churn is genuinely hurting iptables, or policy needs to reason about identity, HTTP, or hostnames, not just ports

Flannel isn't in that table because it isn't really competing on the same axis — no policy engine at all, chosen purely for "make Pods reach each other" simplicity on a lab cluster. Networking & the CNI covers the full three-way comparison and the overlay-vs-native-routing tradeoff underneath all three plugins; this page assumes you've already decided Cilium is the answer and need to actually run it. Cilium's Cluster Mesh feature is also the concrete implementation behind this course's own Multi-Cluster & Fleet Management, and its optional sidecar-free mesh mode overlaps with Service Mesh Fundamentals' territory — for the platform-engineering depth on installation modes, Cluster Mesh prerequisites, and where Cilium sits relative to Istio and Linkerd, Platform Engineering's own Cilium tool page goes considerably further than this page needs to.

✎ Try it

On a kind cluster created without its default CNI (disableDefaultCNI: true), install Cilium with kubeProxyReplacement: true and run cilium status --wait. Deploy two Pods and curl one from the other while watching hubble observe -f — see the flow appear live. Then apply a CiliumNetworkPolicy selecting the server Pod that allows nothing at all, curl again, and find the exact Policy denied DROPPED line. Add an ingress rule allowing the client by label and watch it recover. Finally, add an egress policy with no DNS rule and watch name resolution itself break — then fix it. Those few minutes teach default-deny more durably than any amount of reading it described in prose.

Cilium isn't a named domain on the CKA blueprint, but the concepts it embodies — CNI, default-deny NetworkPolicy, kube-proxy's job, identity-based vs. IP-based thinking — sit squarely inside it. Cilium has its own dedicated CNCF credential, the Cilium Certified Associate (CCA), covered in full on Platform Engineering's CCA page — domains, weights, and a study plan — and the wider Golden Kubestronaut ladder it belongs to is mapped on the sibling Golden Astronaut course.

🎬 At the Pod Squad
🐦

Pip the Hummingbird: cart-api can reach inventory-api's Pod IP directly, no problem. But the second I put a CiliumNetworkPolicy in front of inventory-api, DNS lookups from cart-api started failing too — and I only wrote an egress rule for port 8080.

🦊

Foxy: You selected the Pod with a policy at all — so that direction's default-deny now, right? What did you actually list for the DNS query itself?

🐦

Pip the Hummingbird: …nothing. I never allowed port 53 to kube-dns.

👺

Gizmo: Easy fix — delete the policy. Or add toEntities: [world] and allow literally everything. One line, ships today. 🤑

🐢

Timmy the Turtle: Absolutely not — that's not securing cart-api, that's removing the lock and calling it a fix. Add the DNS rule. Keep everything else denied exactly as it was.

🦫

Benny the Beaver: Adding UDP 53 to kube-dns at the top of the egress list now. Redeploying — this is a one-line diff, not a rewrite.

🐘

Ellie the Elephant: Logging which policy caused it, and how many minutes it took hubble observe --verdict DROPPED to find. Next postmortem writes itself.

🐢 Timmy's checkpoint

1. What does Cilium replace iptables with, and what does it use to identify a workload instead of its IP address? 2. Name two things that change about a cluster once kubeProxyReplacement: true is set. 3. A CiliumNetworkPolicy selects cart-api's egress and allows only port 8080 to inventory-api. What breaks a moment later, and why does it look like an application bug rather than a policy bug? 4. Which single Hubble command tells you exactly which rule dropped a flow, and why? 5. Name one hard requirement of a node's kernel before Cilium's datapath will run correctly. 6. Cilium can replace kube-proxy — but what earlier, CNI-level contract still has to happen first before any of this works at all? 7. Where should you go to study Cilium at CCA-certification depth, rather than this page's operational level?

Check your answers
  1. Cilium replaces the iptables rule-chain walk with eBPF programs doing kernel hash-map lookups, and identifies a workload by a security identity derived from its Pod labels rather than by its IP address.
  2. Any two of: kube-proxy is deleted from the cluster entirely; ClusterIP/NodePort/LoadBalancer/session-affinity are implemented as eBPF programs instead; ClusterIP translation happens at the socket layer before a packet is built, so there's no per-packet DNAT cost; the agent needs k8sServiceHost/k8sServicePort set explicitly to reach the API without kube-proxy's help.
  3. Every DNS lookup from cart-api fails, because selecting the Pod with any policy flips that direction to default-deny and DNS (UDP/TCP 53 to kube-dns) was never explicitly allowed. It looks like an application or dependency bug because the symptom is "can't resolve hostnames" or "the database seems down," not an obvious permissions error.
  4. hubble observe --verdict DROPPED -f (optionally narrowed with --namespace/--pod) — it reads flow events straight out of the eBPF datapath and prints both the flow and the specific policy verdict that dropped it, replacing guesswork with a fact.
  5. A reasonably modern kernel — the documented minimum climbs with each Cilium release, and features like kube-proxy replacement, WireGuard encryption, or the bandwidth manager expect newer kernels still; cilium status --verbose reports what the running kernel actually supports.
  6. The ordinary CNI ADD contract — allocating a Pod IP via IPAM, creating the veth pair into the Pod's network namespace, and programming the node's routes — exactly what Networking & the CNI covers for any CNI plugin. Cilium still has to do this before eBPF policy or kube-proxy replacement mean anything.
  7. Platform Engineering's Cilium tool page for the deeper platform-engineering treatment, and its CCA certification page for the Cilium Certified Associate exam specifically — part of the wider ladder mapped on the sibling Golden Astronaut course.