A Pod calls http://payments-svc. Cluster DNS (CoreDNS) resolves this to the Service's ClusterIP — a stable virtual IP allocated when the Service was created.
No process anywhere is actually bound to the ClusterIP — no network interface owns it. It exists purely as an entry in packet-filtering rules on every node. This is the single most important fact about how Services work.
Virtual IPIndependently, the Endpoints controller continuously watches which Pods match the Service's label selector and are currently Ready, and maintains an EndpointSlice — the up-to-date list of actual Pod IP:port pairs backing this Service.
EndpointsOn every node, kube-proxy watches Services and EndpointSlices, and continuously writes local packet-filtering rules (iptables or IPVS) that say, in effect: "packets to ClusterIP:port → rewrite destination to one of these Pod IP:ports." This is the entire mechanism — there is no central load balancer process.
CheckpointThe calling Pod sends a packet addressed to the ClusterIP. It leaves the Pod's network namespace completely normally — as far as the client's TCP/IP stack is concerned, it's just sending to a regular IP address.
SendBefore the packet is routed anywhere, the Linux kernel's netfilter hooks on the node evaluate the rules kube-proxy programmed in step 4, and match this packet against the ClusterIP:port.
InterceptThe rule performs DNAT (Destination Network Address Translation), rewriting the packet's destination from the virtual ClusterIP to one specific, real Pod IP — chosen essentially at random (iptables mode) or via a configured algorithm (IPVS mode, which supports real load-balancing policies).
DNATNow that the destination is a genuine Pod IP, normal cluster networking (the CNI plugin's routes/overlay) delivers it — potentially to a Pod on a completely different node than either the client or the packet's current location.
RouteFrom the destination Pod's perspective, this looks exactly like a direct connection — it has no visibility into the ClusterIP or the DNAT rewrite that happened upstream. The application never needs to know Services exist at all.
ReceiveNetfilter's connection tracking (conntrack) remembers the original DNAT, so the response packet gets its source address rewritten back to the ClusterIP before reaching the client — the client never sees the real Pod IP either.
Return| Term | Meaning |
|---|---|
| ClusterIP | Stable virtual IP for a Service — not bound to any real interface, exists only as filter rules |
| kube-proxy | Per-node agent that programs local packet-filtering rules implementing Service routing |
| EndpointSlice | Controller-maintained list of actual, ready Pod IP:port pairs backing a Service |
| DNAT | Destination Network Address Translation — rewrites a packet's destination address |
| iptables mode | kube-proxy backend using Linux netfilter rules, roughly random Pod selection |
| IPVS mode | kube-proxy backend supporting real load-balancing algorithms (least-conn, etc.) |
| conntrack | Kernel connection tracking that lets return traffic be automatically un-translated |
| CoreDNS | Cluster DNS server resolving Service names to ClusterIPs |
| Headless Service | Service with clusterIP: None — DNS returns individual Pod IPs instead |
| sessionAffinity | Optional Service setting to pin a client to the same backing Pod |