~13 min
A Service gives a stable virtual IP and DNS name to a set
of Pods selected by label, so clients never track individual
Pod addresses that change on every restart or reschedule. The
control plane watches for Pods matching a Service's
spec.selector and records them — as of Kubernetes v1.33, the
classic v1 Endpoints object is officially deprecated in
favor of discovery.k8s.io/v1 EndpointSlice, stable since
v1.21; the API server now warns on kubectl get endpoints.
Four Service types build on each other. ClusterIP, the
default, is reachable only inside the cluster. NodePort
builds on ClusterIP by additionally opening the same port on
every node. LoadBalancer builds on NodePort by provisioning
an external cloud load balancer in front of it. ExternalName
is different in kind — no selector, no proxying, just a DNS
CNAME alias to an external name.
bash
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080A headless Service (clusterIP: None) skips the shared
virtual IP entirely: DNS returns each matching Pod's own IP
individually instead of one load-balanced address — the fit
when clients need to reach a specific replica directly, such
as one member of a StatefulSet, rather than whichever Pod a
Service happens to route to.
When a Service routes to nothing, the most common cause is a
selector that no longer matches any Pod's labels — a typo, or
a label that changed on one side without the other.
kubectl get endpointslices (or the deprecated kubectl get endpoints) for that Service shows the mismatch directly:
zero entries despite Pods visibly Running.
The Service's selector doesn't match the Pods' current labels — check both sides for a typo or a recent label change.