Kubernetes · advanced
Kubernetes service has no endpoints: compare selectors and pod labels
A Kubernetes Service is reachable as a DNS name, but the Endpoints (or EndpointSlice) object behind it is empty. The service selector syntactically parses, yet no Pod labels match it, so the kube-proxy data plane has nothing to program and clients see connection refused or timeouts. This guide frames the failure as a label/selector mismatch and walks through disciplined triage before any edit.
The symptoms
- •kubectl describe service <svc> reports Endpoints: <none> or an empty endpoint list while the service exists and resolves via in-cluster DNS.
- •curl or application calls to the service ClusterIP hang or receive connection refused, while direct Pod IPs from the same namespace still answer.
- •kubectl get endpoints <svc> -o yaml shows no subsets/addresses, or EndpointSlice lists no endpointslices for the matching label set.
- •Pods are Running and Ready, but their labels do not include the keys/values named by the service spec.selector.
- •Events on the service show no Endpoints-related warnings, yet newly created Pods with matching labels only show up after several seconds, suggesting label churn rather than misconfiguration.
Likely causes
- •Spec.selector keys differ from pod template metadata.labels, including a missing app.kubernetes.io/name versus an app label, or a typo such as app vs apps.
- •Selector values disagree, for example service expects tier=api while the Deployment template sets tier=web, or values are uppercase where the selector is lowercase.
- •Selector names extra keys the pod template does not set, so no Pod satisfies every equality constraint in the AND-of-equalities match.
- •Pods exist but are not Ready: failing readinessProbe, imagePullBackOff, CrashLoopBackOff, or pending scheduling keep them out of the Endpoints subset.
- •Headless service with clusterIP: None intentionally has no Endpoints object; the diagnostic is checking the wrong resource type for a headless service.
- •Namespace mismatch: service in ns-a while Pods carrying matching labels live in ns-b, so cross-namespace label equality is never evaluated.
First ten minutes
- 01Confirm the service exists in the same namespace as the candidate Pods with kubectl get svc <svc> -n <ns> and capture spec.selector and spec.clusterIP.
- 02Inspect the Endpoints object with kubectl get endpoints <svc> -n <ns> -o yaml and note whether subsets is empty or absent; if the object itself is missing, the service is headless and that is expected.
- 03List Pods in the namespace with kubectl get pods -n <ns> -l <selector-from-service> using the service selector verbatim; if this returns zero rows, the selector is the suspect.
- 04Compare labels of Running Pods with the service selector by running kubectl get pods -n <ns> --show-labels and visually diffing keys and values.
- 05Verify Pod readiness with kubectl get pods -n <ns> -o wide; any Pod not 1/1 Ready cannot contribute endpoints even if labels match.
- 06Check service-related Events with kubectl describe svc <svc> -n <ns> for messages about no endpoints, and tail controller logs only if Events are silent.
Evidence to collect
- •Service spec.selector captured from kubectl get svc <svc> -o yaml -n <ns>, including every key and value pair.
- •EndpointSubset and EndpointSlice contents from kubectl get endpoints <svc> -o yaml and kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc>.
- •Pod labels from kubectl get pods -n <ns> --show-labels for each candidate Pod, plus the Deployment/StatefulSet/ReplicaSet template labels.
- •Pod readiness and phase columns from kubectl get pods -n <ns> -o wide, including RESTARTS and the ready column.
- •Service events from kubectl describe svc <svc> -n <ns>, focusing on messages mentioning NoEndpoints or selector mismatches.
Where to look
- •Boundary between Kubernetes control plane and data plane: the endpoints controller, which translates selector matches into Endpoints/EndpointSlice objects.
- •Workload API boundary: the Pod template metadata.labels inside Deployments, StatefulSets, DaemonSets, and ReplicaSets, which is the source of truth for runtime Pod labels.
- •Service API boundary: spec.selector on Service and spec.publishNotReadyAddresses, plus spec.clusterIP for headless detection.
- •Pod readiness boundary: status.conditions with type=Ready on each Pod, and any failing readinessProbe, image pull, or scheduling condition that keeps Ready=False.
- •Namespace boundary: the metadata.namespace of the Service versus the candidate Pods, since selectors do not span namespaces.
Diagnostic steps
- 01Diff selector against labels: write the service spec.selector as a key=value set and intersect it with kubectl get pods --show-labels; an empty intersection proves the selector is wrong, not the pods.
- 02Detect headless service: if spec.clusterIP is None, confirm the workload uses DNS A records directly; do not look for an Endpoints object because Kubernetes intentionally omits it.
- 03Validate Pod readiness: filter Pods whose labels satisfy the selector and check status.conditions for type=Ready status=True; any Pod with Ready=False is excluded from subsets.
- 04Resolve label churn: re-run kubectl get pods --show-labels a minute later and confirm labels are stable; transient labels from mutating webhooks or pod-template drift break selector matching intermittently.
- 05Confirm namespace alignment: ensure kubectl get svc and kubectl get pods target the same namespace; cross-namespace services are not modeled by Service selectors.
- 06Test data plane: from a Pod in the cluster, resolve the service DNS name and send a request; if the name resolves but connections fail, the empty Endpoints object is confirmed as the cause.
Common mistakes
- •Editing the service selector to a wildcard or empty value to force a match, which silently attaches unintended Pods and bypasses the workload contract.
- •Restarting Pods to fix a label mismatch, since the Pod template is regenerated from the controller and will carry the same wrong labels.
- •Trusting the Service status block in kubectl get svc, which does not surface endpoint emptiness; only describe and get endpoints show the subset state.
- •Assuming an empty Endpoints object means a networking problem, when the actual root cause is a label or readiness problem at the workload boundary.
- •Forgetting that equality-based selectors require all listed keys to be present on the Pod; a missing app.kubernetes.io/component label alone disqualifies a Pod.
Safe fixes
- •If selector and template labels disagree, change spec.selector to match the existing Deployment/StatefulSet template labels, then re-check kubectl get endpoints within the controller reconcile window rather than restarting workloads.
- •If the workload template is wrong, patch metadata.labels on the Pod template with kubectl edit deploy/<name> -n <ns> and trigger a rollout so new Pods carry the corrected labels, then verify the Endpoints subset grows.
- •If Pods are not Ready, fix the underlying readinessProbe, image, or scheduling condition first; only Ready Pods appear in subsets, so no selector change will help until Ready=True.
- •If the service is headless, treat empty Endpoints as expected and validate discovery via DNS A records returned for the headless service name; do not add a non-headless Service to force an Endpoints object.
- •If a label is being stripped by a mutating admission webhook, correct the webhook or the template labels so the two agree, then confirm endpoints reconcile without further Pod restarts.
Prove the fix
- 01kubectl get endpoints <svc> -n <ns> -o yaml shows a non-empty subsets block with one address per Ready Pod that satisfies the selector.
- 02kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc> returns at least one EndpointSlice with conditions.ready listing the Pod targets.
- 03From a Pod in the cluster, the service DNS name resolves and an in-cluster HTTP request to <svc>.<ns>.svc.cluster.local:<port> returns a 2xx/3xx response rather than connection refused.
- 04After a label edit on the workload template, kubectl rollout status deploy/<name> -n <ns> reaches Complete and the Endpoints subset count equals the number of Ready replicas.
Prevention and next steps
- •Adopt a single label vocabulary such as app.kubernetes.io/name, app.kubernetes.io/instance, and app.kubernetes.io/component, and reference it from a shared selector library in manifests.
- •Add a CI policy that compares Deployment template labels against the matching Service spec.selector on every change, failing the build on drift before it reaches a cluster.
- •Run an admission or GitOps check that requires every Service to have a non-empty selector and that the selector resolves to at least one Pod template in the same namespace.
- •Monitor endpoint emptiness as a first-class signal with an alert on Endpoints subsets being absent for longer than the controller reconcile interval, so label drift is caught before users see connection failures.
Safe commands and checks
kubectl get svc <svc> -n <ns> -o yaml kubectl get endpoints <svc> -n <ns> -o yaml kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc> kubectl get pods -n <ns> -l <selector-from-service> kubectl get pods -n <ns> --show-labels kubectl describe svc <svc> -n <ns> kubectl rollout status deploy/<name> -n <ns>