Why Pods Need Services
Summary
- A Service is a stable virtual IP and DNS name placed in front of a churning set of pod IPs.
- Insight: Pod IPs are cattle, not pets. They are assigned at creation and gone at deletion, so nothing should ever dial them directly.
- Key Concepts: ClusterIP, selector, EndpointSlice, kube-proxy, cluster DNS.
- When to Use: Any time one workload needs to reach another, or traffic needs to enter the cluster from outside.
- Limitations/Trade-offs: A Service adds a layer of indirection and, in
iptablesmode, does connection-level load balancing only - not request-level. - Related Topics: Life of a request, ClusterIP and headless, EndpointSlices.
Foundations
Every pod gets its own IP address from the cluster's pod CIDR.
That IP is real and routable inside the cluster, but it is also disposable.
Pods restart, get rescheduled to other nodes, scale up and down, and roll during deployments.
Each of those events can hand the workload a brand-new IP.
If clients hardcoded a pod IP, every rollout would break them.
A Service solves this by publishing one stable identity that never changes for the life of the Service object.
That identity is a virtual IP (the ClusterIP) plus a DNS name like payments.prod.svc.cluster.local.
Behind the identity, Kubernetes tracks the current healthy pods and steers traffic to them.
The mental model: the Service is a phone number that stays constant while the people answering it rotate.
Mechanics & Interactions
A Service selects pods with a label selector, for example app: payments.
The control plane continuously watches which pods match that selector and are Ready.
It records their IPs and ports into EndpointSlice objects associated with the Service.
The ClusterIP itself is not owned by any interface - it is a virtual target.
On every node, kube-proxy (or a CNI dataplane like Cilium) programs the kernel so that packets sent to the ClusterIP are rewritten to a real endpoint IP.
In the default iptables mode this is done with DNAT rules; IPVS and eBPF modes use faster data structures for the same job.
DNS ties it together.
CoreDNS runs in the cluster and resolves the Service name to its ClusterIP.
So a client calls http://payments, DNS returns the ClusterIP, and kube-proxy load-balances the connection to a live pod.
When pods come and go, only the EndpointSlices change; the ClusterIP and DNS name are untouched.
That decoupling is the entire point.
Advanced Considerations & Applications
Load balancing in iptables mode is per-connection, not per-request.
A single long-lived TCP connection, or an HTTP/2 or gRPC channel that multiplexes many requests over one connection, pins to one backend pod.
This is a common surprise for teams whose gRPC traffic lands unevenly.
The fixes are client-side load balancing, a service mesh, or a proxy that speaks the protocol.
Not every Service needs a ClusterIP.
A headless Service (clusterIP: None) skips the VIP and returns pod IPs directly in DNS, which StatefulSets and some databases rely on.
Services also govern traffic entering the cluster.
NodePort exposes a port on every node, and LoadBalancer provisions a cloud load balancer that points at those NodePorts.
externalTrafficPolicy: Local preserves the client source IP and avoids an extra hop, at the cost of uneven spreading.
Readiness probes matter here: an unready pod is removed from EndpointSlices, so it stops receiving traffic without being killed.
That is how Services give you zero-downtime rollouts and graceful draining.
Services also compose with the CNI's job rather than replacing it.
Pod-to-pod routing across nodes is the CNI's responsibility, while the Service abstraction only adds the VIP and endpoint tracking on top.
That layering is why swapping kube-proxy for an eBPF dataplane like Cilium changes performance without changing your manifests.
The Service object you write stays identical no matter which dataplane implements it.
Common Misconceptions
"A Service is a proxy process that traffic flows through." No. In the default mode there is no userspace proxy in the data path - kube-proxy only programs kernel rules, and packets are rewritten in the kernel.
"The ClusterIP lives on a network interface somewhere." It does not. It is a virtual address that only exists as forwarding rules; you cannot ping a route to a physical NIC for it.
"Services do request-level HTTP load balancing." Not by default. They balance connections at L4. For per-request L7 balancing you need an Ingress, Gateway, or mesh.
"Adding pods automatically balances existing traffic." Existing connections stay pinned to their current pod. Only new connections can pick the new pods.
"kube-proxy handles pod-to-pod networking." No. Pod-to-pod routing is the CNI's job. kube-proxy only implements the Service abstraction.
FAQs
Do I still need a Service if I only have one pod? Usually yes, because that pod can be rescheduled and change IP, and other workloads need a stable name to reach it.
Is the ClusterIP reachable from outside the cluster? No. It is only routable inside the cluster. Use NodePort, LoadBalancer, Ingress, or Gateway API for external access.
What happens to traffic during a rolling update? Ready pods stay in the endpoint set and serve traffic; new pods join only after passing readiness, and terminating pods are removed first.
Why is my gRPC traffic hitting only one replica? gRPC reuses one HTTP/2 connection, and L4 Services pin a connection to one backend. Use client-side LB or a mesh to spread requests.
Can two Services select the same pods? Yes. Selectors are not exclusive, so several Services can front the same pods for different ports or purposes.
Does a Service without a selector work? Yes. You can create a selector-less Service and manage its EndpointSlices manually, which is how you front fixed external addresses behind a cluster name.
Related
- Services Basics
- Life of a Request
- ClusterIP & Headless
- NodePort & LoadBalancer
- EndpointSlices
- Services Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).