Services — stable endpoints for unstable pods

Pods come and go. A Service stays put. It gives you one permanent IP and DNS name that keeps answering even while the pods behind it are dying and being replaced.

The problem, one more time

Say you're running three replicas of a database pod, sitting at 10.244.2.4, 10.244.2.5, and 10.244.2.6. Any one of them can die and come back at a completely different IP. So if your web app has an IP — or a name that resolves straight to a single pod — baked into it, that connection snaps the instant the pod restarts.

What a Service does

A Service is a stable virtual endpoint — an IP plus a DNS name — that spreads traffic across a group of pods it picks out by label. As pods appear and disappear, Kubernetes keeps the Service's backend list up to date behind the scenes, and the front IP never budges.

Service in front of a set of pods
web pod talks to db-svc Service: db-svc 10.96.0.42:5432 selector: app=db round-robin across ready pods db pod (10.244.2.4) label: app=db db pod (10.244.2.5) label: app=db db pod (10.244.2.9) label: app=db

The web pod just talks to db-svc. Kubernetes' internal DNS turns that name into the Service's stable IP, and then kube-proxy on the node hands the connection off to one of the healthy pods sitting behind it.

Service types

TypeReachable fromTypical use
ClusterIP (default)Only inside the clusterInternal services — DB, cache, other microservices.
NodePortAny node's IP on a specific high port (30000-32767)Ad-hoc external access, dev clusters, MetalLB base.
LoadBalancerA cloud-provisioned public IPProduction external services on cloud (AWS ELB, GCP LB).
ExternalNameResolves to an external DNS nameAliasing an in-cluster name to an external one.
Headless (clusterIP: None)DNS returns the raw pod IPsStatefulSets — direct pod addressing, custom load balancing.

Internal DNS — how one pod finds another

Every Service you create automatically picks up an in-cluster DNS entry, courtesy of CoreDNS:

<service>.<namespace>.svc.cluster.local

# From a pod in the same namespace, this shortens to:
db-svc
# Or the fully qualified form:
db-svc.default.svc.cluster.local
Talk to services by name, never by IP

Pod IPs are here-today-gone-tomorrow, and even a Service IP is only dependable inside the cluster. So reach for the DNS name every single time — it'll always point at whatever the right target is right now, and you never have to think about it again.

A minimal Service manifest

apiVersion: v1
kind: Service
metadata:
  name: db-svc
spec:
  selector:
    app: db          # picks all pods with this label
  ports:
    - name: postgres
      port: 5432     # port on the Service
      targetPort: 5432  # port on the pod
  type: ClusterIP

Under the hood — how kube-proxy delivers

kube-proxy runs on every node, watching Services and rewriting the node's iptables (or IPVS, or eBPF) rules so that any packet aimed at a Service IP gets DNAT'd to one of the backing pod IPs. Notice what's missing: there's no separate load-balancer process sitting in the traffic path. It all happens down in the kernel.

Readiness — why healthy backends matter

A Service will only send traffic to pods that pass their readiness probe. A pod that's up but still booting sits on the bench until its readiness check goes green. That's what makes deploys safe: kube-proxy quietly adds new pods to the pool and pulls old ones out as their readiness flips back and forth.

For the support team — "the app can't reach the database"

Before you go digging into the database itself, check what the app is actually pointing at. If someone wired it up with a pod IP, it'll work right up until that pod restarts and then quietly break. The rule to hand back: talk to services by their DNS name — db-svc — never by a pod IP. And if the DNS name resolves but connections still get refused, it's usually the Service's readiness probe holding pods out of the pool because they haven't finished booting — not a networking fault at all.

Key takeaways

  • A Service is a stable virtual IP + DNS name that fronts a moving set of pods.
  • Types: ClusterIP (internal), NodePort, LoadBalancer, ExternalName, headless.
  • Pods find each other by Service DNS name — db-svc.default.svc.cluster.local.
  • kube-proxy programs the kernel to load-balance to healthy backends.
  • Readiness probes decide which pods a Service will route to.