# Service With No Endpoints

> A Service whose EndpointSlice is empty has no pods behind it, so every request to it fails. ZopNight reads the EndpointSlice rather than the Service, because a selector can look correct and still match 0 pods, which is the failure this catches.

Source: https://zop.dev/integrations/kubernetes/recommendations/service-with-no-endpoints
Updated: 2026-08-19

---

## The Service looks fine; the EndpointSlice is empty

A Kubernetes Service is a selector plus a stable address. It does not verify that anything
matches. Apply a Service with a typo in one label and Kubernetes accepts it happily: the object
is valid, the ClusterIP is allocated, DNS resolves.

What is missing is the EndpointSlice: the list of pod IPs the Service actually routes to. Reading
the Service tells you what it *intends* to match. Reading the EndpointSlice tells you what it
found, which is why this rule looks there.

## What callers experience

Connection refused, immediately, on a hostname that resolves. Not a timeout, not a 503 from a
proxy. The failure is at the TCP level, and it looks like a network problem rather than a
configuration one.

Teams typically debug DNS first, because the name resolves and the address exists, and only later
discover the Service was matching nothing all along.

## The usual causes

**Label drift.** The Deployment's pod template labels changed and the Service selector did not.
Both objects are individually valid.

**Namespace mismatch.** A Service only selects pods in its own namespace. Copying a manifest
between namespaces breaks the pairing silently.

**Zero ready pods.** The selector is correct but every pod is failing readiness, so none are
added to the EndpointSlice. Here the Service is not the problem. It is reporting one accurately.

That third case is worth separating before changing anything: an empty EndpointSlice may be a
symptom rather than a misconfiguration.

## Empty EndpointSlices, then selector versus labels

```bash
kubectl get endpointslices -A -o json | jq -r '
  .items[] | select((.endpoints // []) | length == 0)
  | "\(.metadata.namespace)/\(.metadata.labels["kubernetes.io/service-name"])"'
```

Then compare the selector against the actual pod labels:

```bash
kubectl get svc <name> -n <ns> -o jsonpath='{.spec.selector}'
kubectl get pods -n <ns> --show-labels
```

## The cost consequence

A `ClusterIP` Service costs nothing, so an empty one is free. A `LoadBalancer` Service is not.
It provisions a real cloud load balancer that bills continuously whether or not any pod sits
behind it. Empty LoadBalancer Services are the ones worth finding first.
