Cilium¶
Cilium is the CNI, and on this cluster it is considerably more than that. It
replaces kube-proxy, terminates ingress traffic through Gateway API, hands out
LoadBalancer addresses on a network with no cloud load balancer, encrypts
node-to-node traffic, and shows you what is actually talking to what.
Four components' worth of responsibility in one DaemonSet. That consolidation is the whole argument for it on bare metal — and also the reason a broken Cilium is never a small problem.
At a glance¶
| Namespace | kube-system |
| Sync wave | -1, after the Gateway API CRDs it renders against |
| Depends on | Gateway API CRDs (-10), the Prometheus operator CRDs from make install-cilium, and a k8sServiceHost that answers |
| If it is down | Everything. No CNI, no service routing, no ingress, no LoadBalancer addresses |
| Health check | kubectl -n kube-system exec ds/cilium -- cilium status --brief |
| UI | hubble.infra.k8s.wlkr.ch (Hubble) |
Components¶
- kube-proxy replacement:
kubeProxyReplacement: true. Service routing happens in eBPF rather than iptables or IPVS, which is why kubeadm never deployskube-proxy:proxy.disabledinansible/templates/kubeadm.yaml.j2keeps it out at init and on everykubeadm upgrade apply. - Gateway API: Replaces a traditional Ingress controller — see Gateway API.
- LoadBalancer Pools:
10.9.2.249(apps) and10.9.2.248(infra), announced over L2 ARP so the rest of the LAN can find them. - WireGuard encryption:
encryption.type: wireguard, transparently, between nodes. - Hubble: Observability with metrics and UI at
hubble.infra.k8s.wlkr.ch, behind the Authentik proxy outpost, because Hubble has no authentication of its own. The HTTPRoute points at the outpost, which proxies to hubble-ui; a ReferenceGrant inpayload/platform/authentik/permits the cross-namespace reference.
Hubble metrics¶
The flow-level metrics carry source and destination namespace, which the chart's Hubble dashboards in Grafana filter on and show nothing without; see Monitoring. Namespace rather than workload or IP keeps the series count to the square of the namespace count. The HTTP metric adds workloads for the L7 dashboard, but only has data for traffic Cilium proxies at L7, which here is the Gateway.
The agent reads this list at startup, so a change lands as nodes reboot or Cilium upgrades, not when ArgoCD syncs.
Resource limits¶
The operator, Envoy, Hubble Relay and Hubble UI have memory limits sized at
roughly 2.5x their measured peak working set. The cilium-agent DaemonSet is
deliberately left without one: it peaked at 337Mi, it is the one process on the
node whose death takes pod networking with it, and a day of steady state is not
enough to size something on that critical path.
The one setting that will ruin your day¶
k8sServiceHost in values.yaml is a literal IP address, because with
kube-proxy gone Cilium cannot reach the API server through a Service. It has to
be told where the control plane lives.
Point it at an address that does not answer and every Cilium pod loses the API server at once. Cluster networking goes with it, including whatever you were using to fix the problem. This is the single most effective way to take the whole cluster down from one line of YAML, so treat changes to it with the respect they deserve — the ordering is spelled out in Control Plane VIP.
Hubble earns its keep during a network policy rollout
hubble observe --verdict DROPPED --follow answers the question every default-deny policy raises — what did I just break? — in seconds rather than in an hour of guessing. See Security Policies.
Directory Structure¶
cilium/ # CNI + Gateway API Controller
├── application.yaml # ArgoCD Application (Helm chart)
├── values.yaml # Helm values
├── lb-pools.yaml # CiliumLoadBalancerIPPool + L2 Policy
├── rbac-gateway-fix.yaml # RBAC fix for Gateway API
└── httproute.yaml # Hubble UI route
Cilium is installed twice, sort of
make install-core installs it by Helm, because it has to exist before ArgoCD does, and ArgoCD then adopts it. There is only one number: the Makefile reads targetRevision straight out of application.yaml instead of keeping a pin of its own, so a rebuilt cluster cannot quietly land on a different Cilium than the one it replaced.