Tools · Cilium

Cilium

Cilium is the CNCF-graduated CNI that replaces the Linux kernel’s old iptables plumbing with eBPF programs loaded directly into the kernel — and in doing so turns the pod network from a dumb pipe into a platform capability that routes, load balances, enforces security policy at L3 through L7, and explains itself. It solves the problem that on a large cluster the network is simultaneously your worst performance bottleneck, your least observable component, and your most important security boundary — and traditional CNIs give you almost no leverage on any of the three.

☺ Explain it like I’m 10

Imagine a huge post office where every letter has to be checked against a giant paper rulebook. Ten rules is fine. Fifty thousand rules and the clerk reads down the list one line at a time until she finds a match — everything slows to a crawl. Now imagine you could teach the sorting machine itself the rules, so it decides instantly as each letter drops in, and it also keeps a perfect diary: “letter from Ana to Ben, delivered, 2ms” or “letter from Ana to the outside world, blocked, because of rule 7.” Cilium is that upgrade. The sorting machine is the Linux kernel, the taught-in rules are eBPF programs, and the diary is called Hubble.

🐦Your host for this topic: Pip the Hummingbird — she beats her wings eighty times a second, hovers exactly where the packet is, and sees everything without ever landing. That is the eBPF datapath with feathers on.

What Cilium is and the problem it solves

☺ Like you’re 10: The part of the cluster that carries messages between apps — but a much smarter version that also checks IDs and takes notes.

Cilium is a CNCF-graduated project (it graduated in 2023, the first CNI to do so) providing networking, observability and security for Kubernetes. At its most basic it is a CNI plugin: it gives every pod an IP, wires it into a flat network, and makes pod-to-pod traffic work. What makes it interesting is how it does that — and everything it can do once the plumbing is in place.

The problem: iptables was never designed for this

A conventional Kubernetes datapath leans on iptables. Every Service becomes a chain of DNAT rules; every NetworkPolicy becomes more rules. That model is battle-tested and it works beautifully at small scale. At large scale it develops three chronic ailments. Rule evaluation is broadly linear, so a cluster with tens of thousands of Services burns real CPU walking chains for every new connection. Updates are worse than lookups: changing the ruleset means rewriting large blocks of it, so endpoint churn during a rollout can produce seconds of control-plane lag and dropped connections. And iptables reasons entirely in IP addresses — which in Kubernetes are ephemeral, recycled, and completely meaningless as a description of who a workload is.

The answer: programs in the kernel, identities instead of IPs

Cilium attaches eBPF programs at strategic hook points in the kernel — the network interface (tc/XDP), the socket layer, the cgroup layer — where they make forwarding, load balancing and policy decisions using hash-map lookups rather than list traversal. Cost stops scaling with the number of rules. Better still, the maps are keyed on a security identity that Cilium derives from a pod’s labels, not its IP. Every pod carrying app=checkout, env=prod shares one numeric identity cluster-wide; policy is expressed and enforced between identities. Reschedule the pod, change its IP, scale it to fifty replicas — the identity is unchanged and the policy still means what you wrote.

◆ Key idea

The one sentence to carry into the exam: Cilium replaces the iptables datapath with eBPF, and replaces IP-based policy with label-derived identity-based policy. Everything else on this page — kube-proxy replacement, L7 rules, FQDN egress, Hubble, Cluster Mesh — falls out of those two moves. Once the kernel already knows which identity a packet belongs to and is already parsing it, adding “and log the verdict” or “and check the HTTP path” is cheap.

What Cilium is not

Cilium is not a GitOps tool, a policy admission controller, or a replacement for Kyverno and OPA Gatekeeper — those govern what manifests may be admitted; Cilium governs what packets may flow. It is not a general-purpose observability stack either: Hubble sees network flows, not application traces, so it complements OpenTelemetry and Prometheus rather than replacing them. And while Cilium Service Mesh is real, it is not a drop-in swap for every Istio feature.

Where Cilium fits in a platform

☺ Like you’re 10: Right at the bottom, underneath everything. If Cilium is unwell, nothing above it works.

Which plane and which domain it serves

Cilium sits in the substrate layer of the platform architecture — below your apps, below your mesh, below your ingress. Its control plane is the cilium-operator Deployment plus each node’s agent watching the Kubernetes API and compiling desired state into BPF maps; its data plane is the eBPF bytecode in the kernel of every node. It is a classic platform-team-owned, application-team-invisible capability: developers write a Service and a policy, and never learn that XDP exists.

Its neighbours

CNPE domain relevance

Cilium is not on the official CNPE tool list — do not expect a question that names it. But the exam absolutely tests the concepts it embodies: CNI and the flat pod network, NetworkPolicy and default-deny, kube-proxy and Service load balancing, egress control, and multi-cluster connectivity. Cilium is the canonical modern implementation of all of them, so studying it is the fastest way to make the Networking lesson concrete. Treat this page as depth for real platform work and as a mental model for exam questions phrased generically.

How Cilium works

☺ Like you’re 10: One helper on every machine teaches the machine’s brain the rules; one boss in the cluster hands out ID numbers and IP addresses.

The components

ComponentShapeWhat it does
cilium-agentDaemonSet, one per nodeWatches the Kubernetes API, compiles Services, endpoints and policies into eBPF programs and maps, and loads them into the kernel. It also installs the CNI plugin binary onto the node and drives the per-node Envoy proxy that enforces L7 rules.
cilium-operatorDeployment (usually 2 replicas)Cluster-wide duties: IPAM block allocation, garbage-collecting stale CiliumEndpoint and identity objects, KVStore/CRD housekeeping. Not on the datapath — if it dies, existing traffic keeps flowing but new pods may not get IPs.
Hubble (embedded)Inside each agentReads flow events straight out of the eBPF datapath and serves them on a local gRPC API.
cilium-envoyDaemonSet (recent releases)The L7 proxy that http:, dns: and kafka: rules are redirected to, and the data plane behind Gateway API and Ingress. Older releases embedded it inside the agent process instead; check which shape your version ships.
hubble-relayDeploymentFans out to every node’s Hubble and presents a single cluster-wide flow API — what the hubble CLI and UI talk to.
hubble-uiDeploymentThe service-map web UI: a live graph of who talks to whom, with allowed and dropped verdicts drawn in.
clustermesh-apiserverDeployment (optional)Exposes this cluster’s services, identities and node info to peer clusters in a Cluster Mesh.
Kubernetes API Services · pods · CiliumNetworkPolicy cilium-operator IPAM · identity GC 🐦 hubble-relay + UI cluster-wide flows worker node cilium-agent compiles + loads BPF Pod · checkout identity 4211 Pod · payments identity 7788 Linux kernel · eBPF datapath (tc / XDP / socket hooks) identity-based policy · Service load balancing · NAT — no iptables chains, no kube-proxy 🐦 Hubble flow verdicts watch load maps labels → security identity → BPF map lookup · the same decision costs the same at 10 or 10,000 services

Datapath choices: routing mode, kube-proxy replacement, IPAM

Three install-time decisions shape everything afterwards, and they are the ones a platform engineer must be able to reason about.

The CRDs it introduces

ResourceScopeWhat it is for
CiliumNetworkPolicy (CNP)NamespacedPolicy beyond standard NetworkPolicy: L7 rules (HTTP, DNS, Kafka), FQDN egress, entity selectors, and explicit ingressDeny/egressDeny rules.
CiliumClusterwideNetworkPolicy (CCNP)ClusterThe same grammar applied cluster-wide — the platform team’s baseline that tenants cannot delete. Only the cluster-wide kind can use nodeSelector to write host-firewall rules for the nodes themselves.
CiliumIdentityClusterThe label set ↔ numeric identity mapping. You read these; you never write them.
CiliumEndpointNamespacedOne per pod: its identity, addressing and current policy enforcement state.
CiliumNodeClusterPer-node addressing and IPAM allocation state.
CiliumLoadBalancerIPPoolClusterHands out external IPs to type: LoadBalancer Services on bare metal — no cloud provider needed.
CiliumEgressGatewayPolicyClusterForces selected pods’ outbound traffic through chosen gateway nodes with a stable source IP, so partners can allow-list you.
CiliumEnvoyConfig / CiliumClusterwideEnvoyConfigNamespaced / ClusterLow-level Envoy configuration for the shared per-node proxy — the mechanism under Cilium’s Gateway API and mesh features.
🦆 Dot’s-eye view

“Our egress policy used to be a spreadsheet of IP ranges for the payment provider, and it broke roughly monthly when they changed CDN. Someone on the platform team rewrote it as four lines of toFQDNs naming the actual hostnames. It has not broken since. I still don’t know what eBPF is, and I have never needed to.”

The resources you will actually write

☺ Like you’re 10: One file to switch the smart network on, and a couple of small rule files saying who may talk to whom — and about what.

Installing with an opinionated values file

Cilium is installed with Helm (or the cilium CLI, which wraps Helm), which means the whole datapath is one reviewable, GitOps-managed file — exactly the “infrastructure through the same reconciler as apps” idea from GitOps Workflows.

# values.yaml — a production-shaped Cilium install (helm install cilium cilium/cilium -f values.yaml)
kubeProxyReplacement: true       # eBPF handles ClusterIP/NodePort/LoadBalancer; delete kube-proxy
k8sServiceHost: api.acme.internal   # REQUIRED without kube-proxy: how the agent reaches the API
k8sServicePort: 6443

routingMode: native              # "tunnel" (default) or "native"
ipv4NativeRoutingCIDR: 10.244.0.0/16   # required in native mode: what NOT to masquerade
autoDirectNodeRoutes: true       # only valid when all nodes share an L2 segment
# routingMode: tunnel
# tunnelProtocol: vxlan          # or geneve — costs ~50 bytes of MTU per packet

ipam:
  mode: cluster-pool             # kubernetes | cluster-pool | eni | azure
  operator:
    clusterPoolIPv4PodCIDRList: ["10.244.0.0/16"]
    clusterPoolIPv4MaskSize: 24  # a /24 block per node → roughly 250 usable pod IPs per node

bpf:
  masquerade: true               # do SNAT in eBPF rather than iptables

hubble:
  enabled: true
  relay: { enabled: true }       # cluster-wide flow API for the CLI
  ui:    { enabled: true }       # the service map
  metrics:
    enabled: [dns, drop, tcp, flow, "httpV2:exemplars=true"]

cluster:
  name: eu-west-1-prod           # MUST be unique across a Cluster Mesh
  id: 3                          # MUST be unique, 1-255

gatewayAPI:
  enabled: true                  # needs the Gateway API CRDs installed first
⚠ Swapping a CNI is not a rolling upgrade

Replacing an existing CNI with Cilium on a live cluster is one of the genuinely dangerous operations in platform engineering: the two CNIs disagree about routes and iptables while both are installed, and every pod must be recreated to get an interface from the new plugin. The safe move is a new cluster with Cilium from day zero and a workload migration — the multi-cluster and Cluster API patterns exist partly for this. If you must migrate in place, use Cilium’s documented per-node migration procedure, drain node by node, and rehearse it somewhere disposable first.

Identity-based policy with L7 and DNS awareness

Here is where Cilium visibly outgrows standard NetworkPolicy. The rule below allows checkout to call exactly two HTTP endpoints on payments — a GET on a path pattern and a POST — and nothing else. A plain NetworkPolicy can only say “TCP port 8080, yes or no.”

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: payments-api
  namespace: prod
spec:
  endpointSelector:                 # WHICH pods this policy protects
    matchLabels:
      app: payments
  ingress:
    - fromEndpoints:                # WHO may call — by LABELS, never by IP
        - matchLabels:
            app: checkout
            io.kubernetes.pod.namespace: prod
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:                   # L7: parsed by the per-node Envoy proxy
              - method: "GET"
                path: "/v1/balance/[0-9]+"    # a regex, not a glob
              - method: "POST"
                path: "/v1/charge"
    - fromEntities:
        - cluster                   # allow kubelet probes and in-cluster scrapers
      toPorts:
        - ports: [{ port: "9090", protocol: TCP }]

And here is DNS-aware egress: instead of chasing a SaaS provider’s rotating IP ranges, you name the hostname. Cilium proxies the pod’s DNS request, learns the answer, and programs exactly that IP into the policy map for the record’s TTL.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: payments-egress
  namespace: prod
spec:
  endpointSelector:
    matchLabels: { app: payments }
  egress:
    # 1. DNS must be allowed FIRST, and must be visible to the DNS proxy,
    #    otherwise toFQDNs can never learn any addresses.
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports: [{ port: "53", protocol: ANY }]
          rules:
            dns:
              - matchPattern: "*"           # observe every lookup this pod makes
    # 2. Now allow egress to hostnames, not IP ranges.
    - toFQDNs:
        - matchName: "api.stripe.com"
        - matchPattern: "*.s3.eu-west-1.amazonaws.com"
      toPorts:
        - ports: [{ port: "443", protocol: TCP }]
---
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy    # the platform team's non-negotiable baseline
metadata:
  name: baseline-egress-default-deny
spec:
  # Simply SELECTING a pod is what flips egress to default-deny for it.
  # This baseline allows only DNS; every other destination has to be opened
  # by a namespace-scoped CNP like the one above.
  endpointSelector:
    matchExpressions:
      - key: io.kubernetes.pod.namespace
        operator: NotIn
        values: [kube-system]
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports: [{ port: "53", protocol: ANY }]
          rules:
            dns:
              - matchPattern: "*"

Cilium also has explicit ingressDeny/egressDeny rules, which standard NetworkPolicy cannot express — but reach for them deliberately, because deny rules take precedence over every allow rule anywhere in the cluster. A blanket egressDeny: [{toEntities: [world]}] would not merely set a floor; it would cancel the toFQDNs allow above and every other team’s egress rule with it. Build baselines out of default-deny-by-selection, which composes, and save explicit deny for the handful of destinations nobody may ever reach.

⚠ The moment a policy selects a pod, that pod is default-deny

This is the single most common way people take down their own service. A pod with no policy selecting it allows everything. The instant any policy selects it, that direction becomes default-deny and only what you explicitly listed survives — and people routinely add one egress rule for their database without realising the same policy has just denied every other destination, DNS included. (Directions are independent: an ingress-only policy leaves egress untouched, and vice versa.) Two habits fix it forever: always allow DNS to kube-dns in your first egress rule, and always check hubble observe --verdict DROPPED before declaring the policy “not working.” Cross-check symptoms against Triage: Networking.

Global Services with Cluster Mesh

Once two clusters are meshed, making a Service global is a single annotation — applied to a Service of the same name and namespace in each participating cluster, since the mesh works by merging their endpoint sets. Traffic then prefers local backends and fails over to the remote cluster only when local endpoints are gone — cross-cluster failover without a global load balancer.

apiVersion: v1
kind: Service
metadata:
  name: payments
  namespace: prod
  annotations:
    service.cilium.io/global: "true"      # merge endpoints from ALL meshed clusters
    service.cilium.io/affinity: "local"   # prefer local backends; fail over only if none are ready
spec:
  type: ClusterIP
  selector: { app: payments }
  ports:
    - port: 8080
      targetPort: 8080
◆ Key idea

Cluster Mesh works because identity travels with the flow. A pod in cluster A carries the same label-derived identity semantics in cluster B, so a CiliumNetworkPolicy written once means the same thing in every meshed cluster. That is why the mesh requires globally unique cluster names and IDs and strictly non-overlapping PodCIDRs: if two clusters can allocate the same pod IP, the datapath has no way to know which pod a packet really came from.

Day-to-day commands

☺ Like you’re 10: One command asks “are you OK?”, one runs a full health check, and one shows you every letter and whether it was delivered.

Install, status and the connectivity test

# Install / upgrade (the CLI wraps Helm; a values file is more GitOps-friendly)
# Always pin a version explicitly and read that release's upgrade notes first.
cilium install --version <x.y.z>
cilium upgrade --version <x.y.z>

# The first question, always: are agents, operator and Hubble healthy?
cilium status --wait
# /¯¯\__/¯¯\    Cilium:             OK
# \__/¯¯\__/    Operator:           OK
#    \__/       Hubble Relay:       OK
# DaemonSet  cilium   Desired: 6, Ready: 6/6, Available: 6/6

# The end-to-end proof: deploys a test namespace and runs a large suite of checks
# (pod-to-pod, pod-to-service, DNS, egress to world, policy enforcement, node-port).
cilium connectivity test
# Flags for narrowing or relocating the run (--test, --test-namespace) and for
# concurrency differ between CLI versions — read `cilium connectivity test --help`
# on the binary you actually have before wiring this into CI.

# Turn Hubble on (this performs a Helm upgrade — on a GitOps-managed cluster
# prefer the values file above) and open the service map
cilium hubble enable --ui
cilium hubble port-forward &
cilium hubble ui

# Cluster Mesh
cilium clustermesh enable --context eu-west --service-type LoadBalancer
cilium clustermesh connect --context eu-west --destination-context us-east
cilium clustermesh status --context eu-west --wait

Hubble: proving which rule dropped the packet

This is the capability that changes how incidents feel. Instead of arguing about whether a policy is blocking traffic, you watch the verdict.

# Every dropped flow in the cluster, live — the single most useful Cilium command
hubble observe --verdict DROPPED -f

# ...and the drop reason is printed for you, e.g.:
# prod/checkout-7d9f:52344 (ID:4211) -> prod/payments-6b4c:8080 (ID:7788) Policy denied DROPPED (TCP Flags: SYN)

# Narrow it down
hubble observe --namespace prod --pod checkout --verdict DROPPED --last 50
hubble observe --from-pod prod/checkout --to-pod prod/payments -f
hubble observe --protocol http --http-status 403           # L7 verdicts, if an http rule applies
hubble observe --type l7 --namespace prod                  # only L7 events
hubble observe --to-fqdn "api.stripe.com"                  # did the FQDN policy resolve?
hubble observe --type drop --output json | jq .          # full flow record, drop reason included

hubble status                                              # is relay seeing all nodes?

Digging into the datapath with cilium-dbg

When you need ground truth about what the kernel actually believes, exec into the agent. cilium-dbg is the in-pod debug CLI (it was simply cilium inside the agent in older releases).

CILIUM_POD=$(kubectl -n kube-system get pods -l k8s-app=cilium -o name | head -1)
K="kubectl -n kube-system exec -it $CILIUM_POD -- cilium-dbg"

$K status --verbose                 # datapath mode, IPAM usage, BPF map pressure, proxy status
$K endpoint list                    # every pod on this node: identity + policy enforcement state
$K identity list                    # the numeric identity ↔ label-set table
$K policy get                       # the compiled policy the agent is actually enforcing
$K bpf lb list                      # the Service load-balancing map — is your backend really in it?
$K bpf tunnel list                  # tunnel endpoints (tunnel mode only)
$K bpf policy get <endpoint-id>     # per-endpoint allow/deny map, keyed by identity
$K monitor --type drop              # raw datapath drop events on THIS node
$K service list                     # Services as the datapath sees them

# Is kube-proxy really gone and is eBPF really handling Services?
$K status | grep -i "KubeProxyReplacement"

Add the two you will reach for under pressure — cilium status and hubble observe --verdict DROPPED — to the muscle memory you are building on Command Reference.

Gotchas and failure modes

☺ Like you’re 10: Four things bite: forgetting to allow DNS, overlapping addresses, an old kernel, and expecting L7 rules to be free.

Default-deny and the DNS trap

Restating it because it causes more outages than everything else combined: policy in Kubernetes is additive allow with implicit deny once selected. Write an egress policy that lists your database and nothing else, and your pod loses name resolution instantly — every hostname lookup fails, and the symptom looks like “the database is down,” not “I broke DNS.” Always allow UDP/TCP 53 to kube-dns, and if you use toFQDNs, the DNS rule must also be present so the DNS proxy can observe the answers. If it looks like a mystery, hubble observe --verdict DROPPED ends the mystery in seconds.

Overlapping PodCIDRs and the Cluster Mesh that will not connect

Cluster Mesh has hard prerequisites that are painful to retrofit: every cluster needs a unique cluster.name, a unique numeric cluster.id, non-overlapping PodCIDRs, and node-to-node reachability between them. Almost every cluster in the world was bootstrapped with 10.244.0.0/16, so two default clusters overlap perfectly and the mesh either refuses to connect or routes traffic to the wrong pod. There is no clever fix — you re-IP a cluster, which in practice means rebuilding it. Allocate CIDRs from a registry before the first cluster exists; it is a five-minute decision that saves a rebuild. See Multi-Cluster and the reference architecture.

Kernel versions and where L7 costs you

Two owners for one network

If Cilium is also serving Gateway API or mesh duties while Istio or an NGINX ingress controller owns the same traffic path, you get two systems with opinions about the same packet — asymmetric routing, doubled retries, contradictory policy. Pick one owner per layer and write it down in the architecture doc. It is the same one-resource-two-controllers anti-pattern that stalks progressive delivery.

🐦 Pip’s workshop · 25 min

On a throwaway kind cluster created without a default CNI: install Cilium with kubeProxyReplacement: true, then run cilium status --wait and cilium connectivity test and read what the test actually checks — it is a free tour of the feature set. Then: (1) deploy two pods, curl one from the other, and watch the flow appear in hubble observe -f; (2) apply a CiliumNetworkPolicy selecting the server pod that allows nothing, curl again, and find the exact Policy denied DROPPED line in hubble observe --verdict DROPPED; (3) add an ingress rule allowing the client by label and watch it recover; (4) now add an egress policy without a DNS rule and watch name resolution break — then fix it. Finish with cilium-dbg endpoint list and cilium-dbg identity list so you can see your pods’ identities with your own eyes. Those twenty-five minutes teach default-deny more durably than any amount of reading.

Alternatives and when to choose it

☺ Like you’re 10: There are a few different postal systems. They differ in speed, how much they can inspect, and how much you have to learn.

The field

OptionDatapathPolicy reachObservabilityChoose it when…
CiliumeBPF; tunnel or native routing; optional kube-proxy replacementNetworkPolicy plus CNP/CCNP — L7 HTTP/Kafka, FQDN egress, identity-based, cluster-wideHubble: per-flow verdicts, service map, metricsYou want the network to be a platform capability: scale, L7 and DNS-aware policy, deep flow visibility, multi-cluster
Calicoiptables by default; optional eBPF dataplane; BGP-native routingNetworkPolicy plus its own GlobalNetworkPolicy with ordering and DENY rulesFlow logs (much of it in the commercial edition)You want a mature, widely-supported policy engine and BGP routing without adopting eBPF everywhere
FlannelSimple VXLAN overlayNone — no policy enforcement at allEssentially noneA learning or throwaway cluster where you only need the flat network to exist
Cloud CNI (AWS VPC CNI, Azure CNI)Native VPC addressing per podLimited natively; often layered with Calico or Cilium for policyCloud flow logsDeep VPC integration matters most and you accept per-node IP limits

The honest decision rule

Choose Cilium when at least one of these is true: your Service count or connection churn is large enough that the iptables datapath is measurably hurting you; you need policy expressed in something other than IP addresses — HTTP methods and paths, Kafka topics, or SaaS hostnames; you need to prove what the network did during an incident; or you are building toward multi-cluster with global Services. Choose Calico when a mature, conventional policy engine and BGP are enough and you would rather not take on eBPF operations. Choose Flannel when the cluster is disposable. And be honest about the cost: Cilium has more surface area than the CNI it replaces, it puts a kernel-version constraint on your node images, and it is very hard to swap out later. That is a strategic commitment, not a checkbox — which is precisely why it belongs in the architecture conversation and not in a ticket.

🎬 At the Platform Guild
🦊

Foxy: The payments service is down. Nothing deployed, nothing changed. Must be the database.

🐦

Pip: Give me four seconds. hubble observe --namespace prod --pod payments --verdict DROPPED… there. Every DNS lookup to kube-dns, Policy denied. Your service isn’t down. It just can’t resolve the database’s name.

🦫

Benny: And here’s the merge from ninety minutes ago — a new CiliumNetworkPolicy with an egress rule allowing port 5432. Which quietly flipped egress to default-deny. No DNS rule.

👺

Gizmo: Easy! Delete the policy. Or better — one toEntities: [world] allow-all on every namespace. Ships in one commit! 🤑

🐢

Timmy: Gizmo, that is “fix the lock by removing the door.” Add the DNS rule, keep the deny, and put a connectivity test in the pipeline so the next policy PR fails before it reaches prod.

🐦

Pip: The lesson isn’t “policy is scary.” It’s that with Hubble you get a verdict, not a theory. We went from “must be the database” to the exact rule in one command.

🦆

Dot: Can I have that command on a sticker? I have genuinely spent whole afternoons on what that just answered.

Exam relevance and going further

☺ Like you’re 10: Cilium isn’t named on the exam — but the ideas inside it are. And its website is locked during the test anyway.

What to know cold

Cilium is not on the official CNPE tool list, so nothing here needs to be recalled by product name. What you must own is the concept layer it implements — and the standard Kubernetes objects that express it. Without notes, be able to:

The documentation allowlist — read this twice

⚠ Cilium’s docs are NOT available in the exam

During the CNPE exam the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, any task-specific docs explicitly linked in the exam’s Quick Reference box, and local man pages and /usr/share docs on the exam machine. docs.cilium.io is therefore off-limits — you cannot look up the CiliumNetworkPolicy schema, the toFQDNs syntax or a Helm value mid-task. The good news is that the exam-relevant object is the standard networking.k8s.io/v1 NetworkPolicy, which is fully documented on kubernetes.io — practise finding it there, fast. If Cilium’s CRDs happen to be installed on an exam cluster, kubectl explain ciliumnetworkpolicy.spec and kubectl api-resources | grep cilium are legitimate and available; do not count on it. The manifest shapes worth memorising are collected on Know Cold, the wider tool map lives on Tools, and any term here you cannot define belongs in the glossary.

⚖ CNPA vs CNPE — That allowlist is a CNPE-only mechanic — CNPA permits none of it. CNPA is fully closed-book multiple-choice: no kubernetes.io, no man pages, no lookups of any kind, which makes it stricter than CNPE, not looser. Even so, the concept layer above — what a CNI does, default-deny semantics, identity-based policy — is exactly the kind of concept-level knowledge that pays off in CNPA's closed-book recall.

Practically: budget one study session for hand-writing a default-deny NetworkPolicy plus a DNS-allow rule on a blank page, then checking yourself against Know Cold. Rehearse the diagnosis half with Triage: Networking and the troubleshooting playbook, and read the parent lesson on Networking & Service Mesh at least twice.

Official links (for study time, not exam time)

🐢 Timmy’s checkpoint

1. In one sentence, what does Cilium replace iptables with, and what does it use instead of IP addresses to identify workloads? 2. Name two things a CiliumNetworkPolicy can express that a standard NetworkPolicy cannot. 3. What happens to a pod the moment any policy selects it — and what is the classic thing people forget to allow? 4. What is the difference between tunnel mode and native routing, and what does each cost or require? 5. Which single Hubble command tells you exactly what was blocked? 6. Name two hard prerequisites for Cluster Mesh. 7. Can you open docs.cilium.io during the CNPE exam — and which networking page is available to you?

Check your answers
  1. Cilium replaces the iptables datapath with eBPF programs running in the Linux kernel (hash-map lookups instead of linear rule chains), and identifies workloads by a security identity derived from their labels rather than by IP address — so policy survives rescheduling and IP reuse.
  2. Any two of: L7 HTTP rules (method and path), Kafka topic rules, DNS/FQDN egress via toFQDNs, entity selectors such as world, cluster or kube-apiserver, explicit egressDeny/ingressDeny rules, and cluster-wide scope via CiliumClusterwideNetworkPolicy.
  3. That direction becomes default-deny — only what the policy explicitly allows still works. The classic omission is DNS egress to kube-dns on port 53, which breaks every hostname lookup and looks like a dependency outage rather than a policy bug.
  4. Tunnel mode encapsulates pod traffic in VXLAN or Geneve, so the underlay only needs node-to-node reachability — portable, at the cost of encapsulation overhead and reduced MTU. Native routing forwards unencapsulated for lower overhead, but the underlying network must be able to route your pod CIDRs (via autoDirectNodeRoutes on a flat L2 segment, or by announcing them with the BGP control plane).
  5. hubble observe --verdict DROPPED (add -f to follow, plus --namespace/--pod to narrow) — it prints the flow and the drop reason, e.g. Policy denied.
  6. Any two of: a unique cluster name, a unique numeric cluster ID, non-overlapping PodCIDRs across all clusters, and node-to-node network reachability between them.
  7. No — the allowlist is kubernetes.io/docs, kubernetes.io/blog, task-specific docs given in the Quick Reference box, and local man//usr/share docs. But the standard NetworkPolicy page on kubernetes.io is available, and that is the object the exam actually cares about.