Skip to content

Services: ClusterIP, NodePort, LoadBalancer

A Service gives a shifting set of Pods a single stable virtual IP and DNS name, load-balancing traffic across whichever matching Pods are currently healthy.

Pods are disposable. A Deployment reschedules a crashed Pod onto a different node, a rollout replaces every Pod with new ones, and the cluster autoscaler adds and removes nodes — every one of these events can give a Pod a brand new IP address. Anything that hardcodes a Pod IP breaks the moment that Pod is replaced.

A Service solves this by sitting in front of a group of Pods. It picks its members using a label selector, and it tracks which of those Pods are currently ready through an Endpoints object (or the newer, scalable EndpointSlice objects). As Pods come and go, the controller updates the Endpoints/EndpointSlice automatically — clients never need to know.

apiVersion: v1
kind: Service
metadata:
name: payments
spec:
selector:
app: payments
ports:
- port: 80
targetPort: 8080
Terminal window
# See which Pods this Service is currently routing to
kubectl get endpoints payments
kubectl get endpointslices -l kubernetes.io/service-name=payments

Any Pod whose labels match app: payments and that is passing its readiness probe is added to that list automatically. Delete the Pod and it drops out; a replacement Pod with the same labels is added back in — the Service’s IP and DNS name never change.

Three Service types, each building on the last

Section titled “Three Service types, each building on the last”

spec.type controls how the Service is reachable, and the three common types form a ladder — each one builds on the one before it.

  • ClusterIP (the default) — a virtual IP reachable only from inside the cluster. This is what most Pod-to-Pod communication uses.
  • NodePort — everything ClusterIP does, plus the same port is opened on every node in the cluster, in the 3000032767 range by default. Hitting <any-node-ip>:<nodePort> reaches the Service from outside the cluster.
  • LoadBalancer — everything NodePort does, plus the cluster asks the cloud provider to provision an external load balancer that forwards to the NodePort.
apiVersion: v1
kind: Service
metadata:
name: payments-public
spec:
type: LoadBalancer
selector:
app: payments
ports:
- port: 80
targetPort: 8080
nodePort: 30080
Terminal window
kubectl get svc payments-public
# EXTERNAL-IP column is populated once the cloud provider finishes provisioning

A Service’s ClusterIP is not backed by a real network interface anywhere — it is a virtual address. kube-proxy, running on every node, watches the API server for Services and Endpoints/EndpointSlices and programs local rules (typically iptables or IPVS, depending on the configured mode) so that any packet sent to the virtual IP gets transparently rewritten and delivered to one of the backing Pods. This is why a Service works identically no matter which node a client Pod happens to be on.

flowchart LR
  client["Client Pod"] --> vip["Service virtual IP\n(ClusterIP)"]
  vip --> kp["kube-proxy rules\n(iptables / IPVS)"]
  kp --> p1["Pod 1"]
  kp --> p2["Pod 2"]
  kp --> p3["Pod 3"]
A client reaching a Service's virtual IP, routed by kube-proxy to a backing Pod
Why does Kubernetes need a Service object at all, given that every Pod already has an IP address?
What is the practical difference between NodePort and ClusterIP?
How does a Service decide which Pods are currently backing it
What component actually implements a Service ClusterIP as reachable traffic on each node