Build1 publisher3 min readPublished
Cilium's kube-proxy replacement empties the chain your Service runbook greps
On a kind cluster where kube-proxy was never installed, Cilium 1.17.3 forwards Services with no KUBE-SERVICES chain at all, so the grep that returns 13 rules elsewhere returns nothing while the app stays up.
The Engineer · Build desk

What happened
- On a kind node running Cilium 1.17.3 with kube-proxy never installed, iptables-save | grep KUBE-SERVICES returns zero lines, while the same command on a kube-proxy cluster returns 13.
- The author tried a different table and a different node, found KUBE-SERVICES nowhere, and confirmed the application was up and its Services reachable.
- Setting kubeProxyReplacement=true only makes Cilium assume kube-proxy is absent; Cilium does not delete it, so an existing cluster needs kube-proxy removed as a separate step.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The two commands that covered Service forwarding, iptables-save for rules and tcpdump filtered on a ClusterIP for packets, both stop producing data, so a runbook whose first step is either of them has no first step.
- decision Choosing kubeProxyMode: none at cluster creation decides how your on-call engineers will debug Services for the life of that cluster, and it is made in a config file weeks before anyone pages.
- exposure Clusters mid-migration are the exposed case: kube-proxy has not been removed yet, so the chains an engineer dumps are still there while Cilium is the component actually forwarding the Service.
- cost The evidence is a pair of single-node kind clusters on pinned versions, so anyone budgeting this migration has to pay for their own multi-node verification before trusting the debug procedure in production.
A ClusterIP is not an address on any machine [10]. When a Pod sends a packet to one, something in the forwarding path rewrites the destination to a real Pod IP, and kube-proxy was the component that registered those rewrite rules in netfilter, the Linux kernel's packet-processing framework [10]. iptables is the command that reads and writes netfilter rules [10]. Because the forwarding decision lived in the kernel as a text rule, dumping everything was enough to find it [11]. On the kube-proxy cluster the chain sequence runs KUBE-SERVICES to KUBE-SVC-xxxx to KUBE-SEP-xxxx, and the destination 10.96.111.7:80 becomes 10.244.0.6:80 at the last step [12].
The same structure is what made tcpdump useful. Early in that path the packet still carries the ClusterIP as its destination, so a capture filtered on the ClusterIP caught it, and capturing before and after the rewrite showed which Pod IP the destination changed to [13]. Rules from iptables-save, live packets from tcpdump: those two tools covered Service forwarding [14]. Under Cilium's kube-proxy replacement both go quiet. KUBE-SERVICES, the chain kube-proxy creates as the entry point for ClusterIP traffic, does not exist [7], and the author reports that tcpdump on a ClusterIP produces nothing and conntrack -L returns no matches [15].
A loose grep is worse than an empty one. Widen the pattern to KUBE- and lines come back, but they are KUBE-FIREWALL and KUBE-KUBELET-CANARY, kubelet's chains, and the nat table itself still holds rules [8]. All 13 KUBE-SERVICES lines are gone, and none of the KUBE- chains still present has anything to do with Service forwarding [18]. An on-call engineer who stops at seeing rules in iptables has read a populated table and learned nothing about where the packet went.
Two conditions decide whether that result transfers. The first is versions: kind v0.27.0, node image kindest/node:v1.32.2, Cilium 1.17.3, cilium-cli v0.20.0, kernel 6.8, both clusters single-node, with multi-node-only behaviors out of scope [16]. The second is that kube-proxy is genuinely absent. The test cluster used kind's kubeProxyMode: none, so it was never installed [5], while on a running cluster kubeProxyReplacement=true only tells Cilium to assume absence, and you have to remove kube-proxy yourself [4]. I would expect the half-migrated cluster to be the harder one to debug: the dump still returns chains, and Cilium is the thing forwarding the Service [3].
So the runbook edit belongs before the migration, while every Service is healthy and nobody is timing you. The article says it documents where Cilium keeps the forwarding rules that used to live in iptables, and what to check in what order when connectivity fails [15]. The published reproduction runs two kind clusters side by side, one with kube-proxy and one with Cilium, and issues the same commands against the same Service on both [17].
What to watch
- A run of the published repro on a cluster where kube-proxy was installed first and removed afterwards, to show what the half-migrated iptables dump reports.
- Multi-node results: both test clusters were single-node and the author put multi-node-only behaviors out of scope.
- Whether tcpdump and conntrack stay silent on kernels other than 6.8 and on Cilium releases after 1.17.3.