Services whose EndpointSlice lists zero ready or not-ready endpoints
What does ZopNight detect here?
ZopNight flags a Kubernetes Service on EKS, GKE or AKS whose EndpointSlice has 0 ready and 0 not-ready endpoints once the slice is at least 5 minutes old. Traffic sent to that Service has no pod to reach, which usually means its selector no longer matches any pod labels. Headless and ExternalName Services are skipped.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1742 · RC-1842 · RC-1942 |
| Category | orphan |
| Severity | medium |
| Metric | EndpointSlice endpoint count |
| Threshold | 0 ready and 0 not-ready endpoints, slice at least 5 minutes old |
| Evaluation window | 5m |
| Source | ZopNight |
| Permissions used | list endpointslices.discovery.k8s.io · list services |
Where it applies
A Service with no backends drops every request
A Service finds its pods through a label selector, and the control plane records the matching
pod addresses in EndpointSlices. Each slice carries a kubernetes.io/service-name label linking
it to its Service, as the
EndpointSlices documentation
describes. When the slice has no endpoints at all, not even pods that are running but failing
readiness, the Service is routing traffic to nothing.
This most often follows a relabel. A Deployment’s pod template gains app.kubernetes.io/name
while the Service still selects on app, or a workload is deleted and its Service is left behind.
Clients see connection failures, and if the Service is of type LoadBalancer, the cloud load
balancer created for it keeps existing for as long as the Service does.
Listing Services with empty slices
kubectl get endpointslices -A -o json | jq -r ' .items[] | select((.endpoints // []) | length == 0) | "\(.metadata.namespace)/\(.metadata.labels["kubernetes.io/service-name"] // .metadata.name)"'For each result, compare the selector with real pod labels:
kubectl get service <name> -n <namespace> -o jsonpath='{.spec.selector}'kubectl get pods -n <namespace> --show-labelsConditions for the finding
ZopNight evaluates the EndpointSlice and counts its ready and not-ready endpoints. It fires only when both counts are zero. A slice with pods that exist but fail their readiness probes is not an empty Service and is not flagged here. Because a fresh deploy, a crash-loop recovery or a scale-up from zero briefly produces an empty slice, the slice must also be at least 5 minutes old by its creation timestamp. If neither endpoint count was reported, or the creation time is unknown, ZopNight stays silent.
Services that are empty on purpose
Two Service shapes are excluded because an empty slice is normal for them. ExternalName Services
resolve to a DNS name and never select pods. Headless Services, those with clusterIP: None, are
also skipped. The rule does not know whether a workload was scaled to zero deliberately; see
Stopped deployment for that side.
Filed as an orphan without a price
The finding does not carry a savings amount. When the Service is a LoadBalancer, the cloud load
balancer behind it is the part that costs money, and removing the Service is what releases it.
Reconnecting or removing the Service
- Run
kubectl describe service <name>and read theSelectorline. - Compare it with the labels in the pod template of the workload that should serve it, and fix whichever side is wrong.
- Confirm the slice fills in with
kubectl get endpointslices -l kubernetes.io/service-name=<name>. - If no workload should serve it any more, delete the Service with
kubectl delete service <name>. For aLoadBalancerService, a finalizer holds the deletion until the cloud load balancer is cleaned up, as described in Create an External Load Balancer.