Services In Depth
A Kubernetes Service is a stable abstraction that decouples clients from the ever-changing set of Pods actually doing the work; it groups Pods using a label selector and hands out a single virtual IP and DNS name that stays constant across deployments, restarts, and autoscaling events. Under the hood a Service is really just a specification that the EndpointSlice controller and kube-proxy turn into live, continuously updated routing rules.
Cricket analogy: It's like a franchise's 'opening batter' role staying on the team sheet permanently, even though the actual player filling it, Rohit one series, Gill the next, changes based on form and fitness.
The Four Service Types
ClusterIP, the default type, allocates a virtual IP reachable only from inside the cluster and is the building block every other type is layered on top of. NodePort opens a fixed port, in the 30000-32767 range by default, on every node's own IP so external traffic can reach the Service by hitting any node directly, which is simple but exposes node IPs and requires you to manage your own external load balancing. LoadBalancer asks the cloud provider's controller to provision an actual external load balancer, an AWS NLB or GCP Network Load Balancer for example, and wire it to the NodePort underneath. Finally, ExternalName is a special case that returns a CNAME DNS record pointing at an external hostname, performing no proxying at all.
Cricket analogy: It's like the tiered structure of Indian domestic cricket, club matches, that only local scouts see, ranji trophy games visible statewide, and finally IPL matches broadcast globally, each tier exposes the same underlying talent to a progressively wider audience.
apiVersion: v1
kind: Service
metadata:
name: checkout-api
spec:
type: LoadBalancer
selector:
app: checkout
tier: backend
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
externalTrafficPolicy: Local # preserves client source IP, avoids extra hop
Endpoints, EndpointSlices, and Readiness
A Service never talks to Pods directly by label at request time; instead the EndpointSlice controller continuously watches which Pods match the selector and are Ready according to their readiness probes, and publishes that list as one or more EndpointSlice objects, each capped at 100 endpoints by default to keep updates cheap at scale. kube-proxy watches these EndpointSlices, not the Pods or the selector, which is why a Pod that fails its readiness probe is instantly pulled out of rotation without being deleted, and why traffic never touches a Pod that is still starting up.
Cricket analogy: It's like the twelfth man list a captain keeps updated ball by ball, a fielder who pulls up injured is instantly flagged unavailable for the next over's field placement without being sent off the ground entirely.
You can inspect the live routing state directly with kubectl get endpointslices -l kubernetes.io/service-name=checkout-api, this is the ground truth kube-proxy is actually programming from, independent of whether the Service's selector still matches the Pods you expect.
Headless Services and Session Affinity
Setting clusterIP: None creates a headless Service, which skips virtual-IP load balancing entirely and instead makes the Service's DNS name resolve directly to the individual Pod IPs behind it, this is essential for stateful workloads like Kafka brokers or Cassandra nodes where clients need to address a specific replica rather than a random one. Separately, sessionAffinity: ClientIP can be set on any Service type to pin a given client's requests to the same backend Pod for a configurable timeout, trading load distribution evenness for the ability to keep a client sticky to whichever Pod holds its in-memory session state.
Cricket analogy: It's like a fan who wants to watch a specific player, not just 'a batter', a headless Service is choosing to follow Virat Kohli's individual scorecard directly rather than accepting whichever batter the broadcast randomly cuts to.
Headless Services with StatefulSets rely on stable Pod DNS names like kafka-0.kafka.default.svc.cluster.local, but that DNS entry only exists while the specific Pod is Running and Ready; if a StatefulSet Pod crash-loops, dependent clients doing their own DNS-based discovery will see resolution failures rather than automatic failover to a healthy replica, unlike a normal load-balanced Service.
- A Service is a label-selector-driven abstraction that gives a stable virtual IP and DNS name to a dynamic set of Pods.
- ClusterIP (internal only), NodePort (fixed port on every node), LoadBalancer (cloud-provisioned external LB), and ExternalName (DNS CNAME, no proxying) are the four Service types.
- kube-proxy routes based on EndpointSlice objects, not directly on the Service selector, so only Ready Pods receive traffic.
- EndpointSlices cap at 100 endpoints each by default, replacing the older single-Endpoints-object model for scalability.
- Headless Services (clusterIP: None) return individual Pod IPs via DNS instead of load balancing, critical for stateful, identity-aware clients.
- sessionAffinity: ClientIP pins a client to one backend Pod, trading even load distribution for session stickiness.
Practice what you learned
1. Which Service type performs no proxying at all and simply returns a DNS CNAME record?
2. What does kube-proxy actually watch to determine which Pods should receive traffic for a Service?
3. What is the effect of setting clusterIP: None on a Service?
4. Why might a Pod be excluded from receiving Service traffic even though it matches the Service's label selector?
5. What tradeoff does sessionAffinity: ClientIP introduce?
Was this page helpful?
You May Also Like
The Kubernetes Networking Model
How Kubernetes guarantees flat, NAT-free Pod-to-Pod connectivity and how CNI plugins and kube-proxy implement that contract.
Ingress Controllers
How Ingress resources describe HTTP routing rules and how Ingress controllers like NGINX, Traefik, and cloud ALB controllers actually fulfill them, including TLS and multi-controller setups.
Network Policies
How Kubernetes NetworkPolicy locks down the default flat, open Pod network using default-deny baselines, selector-based rules, and CNI-dependent enforcement.