Hubble is Cilium’s built-in network observability tool, and it’s your window into what’s actually happening with your network traffic inside Kubernetes.
Let’s see it in action. First, you need Cilium installed with the Hubble components enabled. If you’re using Helm, it’s usually something like this:
helm install cilium cilium/cilium --version 1.14.0 \
--namespace kube-system \
--set prometheus.enabled=true \
--set ipam.mode=kubernetes \
--set enable-ipv4=true \
--set enable-ipv6=false \
--set kube-proxy-replacement=strict \
--set cni.chainingMode=none \
--set egress-gateway.enabled=false \
--set hubble.enabled=true \
--set hubble.listen-address=":4244" \
--set hubble.relay.enabled=true \
--set hubble.relay.listen-address=":8081" \
--set hubble.ui.enabled=true \
--set hubble.ui.ingress.enabled=true \
--set hubble.ui.ingress.class=nginx \
--set hubble.ui.ingress.host=hubble.local
Once Cilium and Hubble are up, you’ll have a few key components:
hubble-relay: This service aggregates flow data from all Cilium agents and makes it available via an API.hubble-ui: A web-based interface that visualizes the network flows.- Cilium Agents: Each node running a Cilium agent collects flow data locally and sends it to
hubble-relay.
The core concept is network flow data. Cilium, sitting at the network layer (L3/L4) and often L7 with its service mesh capabilities, can observe every packet that passes through its eBPF datapath. It doesn’t just see "connection established"; it sees the entire lifecycle of a connection: source, destination, ports, protocols, and even application-layer requests (like HTTP method, URL, response code) if L7 visibility is enabled. This flow data is then exported, aggregated, and visualized.
Imagine you have two deployments, frontend and backend, in the default namespace.
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
spec:
containers:
- name: backend
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
After deploying these and a Kubernetes Service for backend, you’d expose frontend to the internet. Then, you’d send some traffic to frontend.
If you’ve configured Hubble UI correctly (as in the Helm command above, pointing to hubble.local or wherever you’ve set up your ingress), you can access it in your browser. You’ll see a graph. Nodes in the graph represent your Kubernetes services or workloads. Edges represent network flows between them.
Clicking on an edge or a node reveals details. For an HTTP request from frontend to backend, you’d see source IP, destination IP (likely the ClusterIP of the backend service), source port, destination port (80), the HTTP method (GET), the requested URL, and the response code (e.g., 200 OK). This is incredibly powerful for debugging. You can see if frontend is even trying to reach backend, what port it’s using, and if backend is responding successfully.
A key detail most people miss is that Hubble’s L7 visibility, particularly for HTTP, relies on Cilium’s eBPF program injecting itself into the request path after it has been processed by the Kubernetes Service. This means you’re seeing the flow as it hits the actual pod backing the service, not just the Service IP. This distinction is crucial when debugging issues where traffic reaches the Service IP but not the pod.
The next frontier is correlating these network flows with application-layer events or tracing information, which is where projects like OpenTelemetry and Cilium’s integration with them become relevant.