Pods on cloud agents cannot reach the API server (missing hcloud network routes)¶
Symptom¶
A pod-networked API client (e.g. kured) on a cloud agent node crashloops with
dial tcp 10.43.0.1:443: i/o timeoutorcontext deadline exceeded.Direct curl from a pod on that node to any control-plane node IP (
https://10.25x.0.101:6443) times out, while pod→pod (even cross-node) and pod→internet work.Variant: the aggregated metrics API flaps (
kubectl topfails intermittently) after a control plane node was replaced — the API server cannot reach pods (metrics-server) on the new node.hostNetwork pods on the same node are unaffected (they do not use the pod-source path).
Root cause¶
Cilium runs in native routing mode with WireGuard node encryption (encrypt-node=true), but it intentionally excludes API-server nodes from node-to-node encryption (bootstrap chicken-and-egg; visible as encryptkey=0 for the control-plane node IPs in cilium-dbg bpf ipcache list, identity 7).
Pod→control-plane-host traffic therefore leaves the node unencrypted with the pod IP as source and relies on the Hetzner cloud network to route it — which requires a route entry per pod CIDR (10.42.X.0/24 → node IP).
Those routes were historically created by the hcloud cloud-controller-manager, but since the Robot/Helm CCM setup it runs with HCLOUD_NETWORK_ROUTES_ENABLED=false: existing routes are frozen leftovers, and new or replaced cloud nodes never get a route (and stale routes keep pointing at deleted gateways, blackholing traffic).
Dedicated (Robot) workers are not affected: their raw pod-source traffic reaches the control planes over the vSwitch L2 coupling.
Diagnosis¶
# 1. Compare pod CIDRs with the cloud network routes
kubectl get nodes -o custom-columns='NAME:.metadata.name,IP:.status.addresses[?(@.type=="InternalIP")].address,PODCIDR:.spec.podCIDR'
source kube-hetzner/clusters/<cluster>/.env
hcloud network describe <network> -o json | jq -r '.routes[] | "\(.destination) -> \(.gateway)"'
# 2. Every CLOUD node's pod CIDR needs a route to its InternalIP.
# Robot workers (10.6.x) need none. Watch for routes whose gateway is no longer a node.
# 3. Confirm the encryption exclusion (optional)
kubectl exec -n kube-system <cilium-pod> -c cilium-agent -- cilium-dbg bpf ipcache list | grep <cp-node-ip>
# -> identity=7 encryptkey=0 = unencrypted raw path, routes required
Fix¶
Add or correct the route for each affected cloud node:
hcloud network remove-route <network> --destination 10.42.X.0/24 --gateway <stale-gw> # only if stale
hcloud network add-route <network> --destination 10.42.X.0/24 --gateway <node-internal-ip>
The effect is immediate (no pod or agent restart needed); restart a crashlooping consumer (e.g. the kured pod) to clear its backoff.
Prevention¶
Route reconciliation is disabled (HCLOUD_NETWORK_ROUTES_ENABLED=false in the CCM Helm values), so every cloud node replacement re-creates this problem.
The durable fix — re-enabling CCM route reconciliation and validating it against Robot nodes — is tracked in plans_epics/backlog/hcloud-ccm-route-reconciliation.md.
Until then: after replacing any cloud node, check and fix the routes as above (incident references: helix-prod witness kured crashloop + kup6s metrics-server flapping, 2026-07-07).