The Cilium state kubectl will never see
Most of Cilium's interesting state never reaches the Kubernetes API. It lives inside the agents, and reading it means SSHing into a node and cross-referencing cilium status, cilium bgp peers, and hubble observe by hand, then repeating the whole exercise on every other node in the cluster. kubectl can't help. It only understands the API's built-in objects, treats Cilium's CRDs as opaque YAML, and has no view at all into the live identity, endpoint, and agent state that never touches the API server.
That state is where everything actually happens. Cilium replaces the flat, IP-based picture of a Kubernetes network with identity: every pod carries a cluster-wide identity derived from its labels, network policy is enforced against that identity inside the kernel, and BGP, encryption, load balancing, and multi-cluster meshing all run as agent state on top.
We built a Cilium extension for Kunobi that reads both sides at once: what the CRDs declare, and what the agents are enforcing right now, cross-referenced by the same identity IDs and node names Cilium already uses internally. The rest of this post walks through each part, backed by real screenshots from a two-node kind cluster. Everything works identically on fifty nodes. That's the point: the CLI workflow is what doesn't scale.
Identities, not IPs
A CiliumIdentity is computed from a pod's identity-relevant labels - the subset the cluster treats as significant for policy, not every label a pod happens to carry. Those labels hash into one cluster-wide numeric ID, shared by every pod carrying the same label set, regardless of which node it lands on or what IP it gets assigned along the way. Every pod also gets a CiliumEndpoint, which tracks which identity it currently holds and whether the eBPF program enforcing that identity is loaded and ready on its node. A CiliumNetworkPolicy's selectors then match against that identity at the label level, not against IPs or CIDR blocks, which is the fundamental difference between Cilium's model and a conventional IP-based firewall: the enforcement point never has to know or care what address a pod holds, only what identity it presents.
That's the graph the extension exposes end to end: labels resolve to an identity, the identity attaches to an endpoint, and enforcement is a lookup against that endpoint's identity, not its address.
Network Policies
The Network Policies view lists every CiliumNetworkPolicy with its selector, ingress and egress rule counts, and namespace already parsed out of the CRD, instead of a wall of nested YAML you'd otherwise have to read line by line. Cluster-scoped policies that apply across every namespace at once show up the same way, one view over, as CiliumClusterwideNetworkPolicy.
Opening a policy's YAML tab shows the full spec exactly as declared:
This is where the literal label keys and values a selector matches on become visible, which matters because Cilium enforces on the exact key given in the manifest, not on whatever a human reading it assumes two similarly-named labels are supposed to mean. app and app.kubernetes.io/name are different keys, and a selector matching one silently ignores pods labeled with the other.
Identities and Endpoints
The Identities view lists every CiliumIdentity in the cluster. Alongside the raw security labels each identity actually carries, it shows a derived "app label" column, checked across the label keys real clusters tend to use - app.kubernetes.io/instance, app.kubernetes.io/name, app, and k8s-app - in that priority order, so a cluster that mixes labeling conventions across teams or Helm charts still gets a sensible name in the column instead of a blank cell.
The Endpoints view shows which identity each pod currently holds, and whether its eBPF program is ready, still waiting on identity resolution, or otherwise not yet enforcing anything at all:
Both views key on the same identity ID, so a policy's selector, a pod's actual labels, and its live enforcement state cross-reference directly against each other, without shelling into a node to piece it together by hand. When two pods you expect to share a policy verdict don't, this is where their label sets visibly diverge.
Envoy and L7
A CiliumNetworkPolicy can already express some L7 rules directly, restricting an HTTP method or path without any extra configuration. For anything the built-in L7 policy syntax can't express, Cilium lets you hand it raw Envoy xDS resources through a CiliumEnvoyConfig, or its cluster-scoped counterpart CiliumClusterwideEnvoyConfig. The Envoy Configs view parses that out into listener and backend service counts instead of leaving it as an opaque resources array:
Knowing how many backend services and Envoy resources a config actually declares is the detail that matters when a listener isn't picking up traffic the way you'd expect - it tells you immediately whether the config even reaches the service you think it does, before you go looking any further.
Load balancing, IPAM, and egress
Below the policy layer, several more CRDs each control "which IP" or "which path" at a different point in the traffic flow, and Kunobi reads all of them the same way it reads the policy CRDs: parsed columns instead of raw YAML, with the full spec one click away. CiliumPodIPPool controls where pod IPs come from, in clusters running Multi-Pool IPAM. CiliumLocalRedirectPolicy keeps specific traffic node-local - node-local DNS caching is the common case - instead of letting it cross the network to reach a destination that's just as available on the same node.
CiliumLoadBalancerIPPool controls where a Service of type LoadBalancer gets its external IP from, and its detail view surfaces the pool's CIDR alongside its live utilization, not just the declared range:
In the demo pool that's 16 addresses total, 14 available, 2 in use - allocation state, not configuration.
CiliumL2AnnouncementPolicy (or BGP, covered next) is what makes an IP handed out from that pool actually reachable over the local network, which matters on bare-metal clusters that have no cloud load balancer to hand an external IP out for them. CiliumEgressGatewayPolicy sits on the other side of the connection: it controls what source IP appears when pods reach out to the internet, by routing their egress traffic through a designated gateway node instead of whatever node the pod happens to be scheduled on.
The rest of the extension
The same declared-versus-live split runs through the parts of Cilium that have no CRD at all. BGP sessions, encryption tunnels, Hubble, and Cluster Mesh peering are either partially or entirely runtime-only, driven by cluster-wide Helm configuration rather than a Kubernetes object you could list with kubectl get. The extension's Overview view rolls all of that live state into one row per connected cluster, so getting a sense of whether a cluster is actually healthy doesn't mean opening four separate CLIs one after another:
Every number on this panel comes from the agents themselves, polled live - none of it is cached from a CRD. Agent health, ClusterMesh readiness, BGP peer count, and encryption mode sit next to the cluster's effective cilium-config - routing mode, IPAM, kube-proxy replacement - and, since this cluster runs Hubble, its listen address, metrics endpoint, TLS state, and whether the UI is deployed.
BGP
Cilium can run a BGP speaker on each node to advertise pod and Service CIDRs directly to upstream routers, instead of relying on a cloud provider's load balancer integration. CiliumBGPClusterConfig and CiliumBGPPeerConfig describe the intended peering - which nodes participate, which routers they should peer with, what to advertise - but none of that tells you whether the sessions those objects describe are actually up right now. The runtime peers view shows what each node's session is doing at this moment: state, uptime, and per-address-family route counts advertised, received, and accepted.
Both nodes in this demo hold an established session to the same upstream router. A peering configuration that's misconfigured, or whose upstream router has gone unreachable, looks identical on the CRD side whether the resulting session is established, idle, or stuck retrying in a loop - the runtime view is the only place that distinction actually shows up.
Node and agent runtime
CiliumNode tracks the state Cilium needs to know about each node - its pod CIDRs, internal IP, encryption key rotation index - and CiliumNodeConfig lets you override specific agent settings for a subset of nodes. Both are declared, desired-state objects, automatically maintained by the operator rather than something you'd normally hand-edit. The Agents runtime view shows what each node's agent is actually reporting back: controller health, and IPAM allocation against the node's assigned pod CIDR.
Both agents in this demo report healthy, every controller passing, and IPAM handing out addresses from their respective pod CIDRs without incident - the same signal cilium status would give you if you ran it on each node individually, collapsed into one table instead of one SSH session per node.
Encryption
Encryption has no dedicated CRD either. It's driven entirely by cluster-wide configuration - the encryption.enabled and encryption.type Helm values, surfaced through the cilium-config ConfigMap. The only per-node encryption field visible on an actual Kubernetes object is CiliumNode.encryptionKey, and that's a key-rotation index, not proof that a tunnel is actually up. WireGuard enabled in Helm and a tunnel actually up on every node are two different claims - the runtime Encryption view is where they disagree. It lists each agent's real WireGuard interface, listen port, and public key:
Hubble
Hubble has no CRD either, and unlike the other runtime-only areas, Kunobi doesn't surface its flow log - there's no per-connection source, destination, or verdict inspection here, and no service-map view built from Hubble's data. What it does show is whether Hubble itself is healthy on each node: its running state, current and maximum flow buffer size, total flows seen, and uptime.
Cluster Mesh
Cluster Mesh has no CRD at all. It's turned on at install time through cluster identity configuration - cluster.id and cluster.name - never through a Kubernetes object you'd list or watch. A ClusterMesh peering can be fully configured while a remote cluster's heartbeat has quietly gone stale, and nothing about the declared setup would tell you that's happening. The ClusterMesh tab lists every connected cluster with its live ready state, endpoint count, and last heartbeat:
Wrapping up
Cilium enforces policy on identity, computed from labels, and keeps almost all of its interesting state out of the Kubernetes API entirely, visible only to whichever agent happens to be holding it, and only for as long as that agent stays up. Kunobi's Cilium extension reads both halves of that: the declared CRDs that say what should happen, across policy, load balancing, BGP, and Envoy configuration, and the live agent state that says what's actually happening right now, cross-referenced by the identity IDs and node names Cilium already uses internally. The whole graph is visible in one place instead of assembled by hand across kubectl, cilium, and hubble on however many nodes the cluster has.
The extension installs from Settings → Extensions in Kunobi Pro. Download at kunobi.ninja/download. If you run Cilium and something you need isn't here, tell us: feedback@kunobi.ch.
Cluster updates, in your inbox.
Kubernetes deep dives, GitOps field notes, and platform-engineering essays from the team building Kunobi. Two posts a month. No fluff.

