# StatefulSet Has No Headless Service

> Flags StatefulSets with no serviceName, whose pods get no stable per-pod DNS names.

Source: https://zop.dev/integrations/kubernetes/recommendations/statefulset-has-no-headless-service

---

## Stable pod identity depends on a governing service

Each StatefulSet pod gets a predictable hostname, `<statefulset>-<ordinal>`, such as `web-0`.
The DNS record that makes that name reachable comes from a headless Service. The
[StatefulSets concept page](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)
describes the domain it manages as `<service>.<namespace>.svc.cluster.local`, with each pod
getting `<pod>.<service domain>`, where the governing service is the one named in `serviceName`.
The same page is explicit that StatefulSets currently need a headless Service for their pods'
network identity and that you are responsible for creating it.

Clustered software leans on those names. Database replicas, consensus members and brokers often
find each other by stable per-pod addresses, so a StatefulSet without a governing service can
start its pods and still fail to form a cluster.

## Checking your StatefulSets

```bash
kubectl get statefulsets -A -o json | jq -r '
  .items[] | select((.spec.serviceName // "") == "")
  | "\(.metadata.namespace)/\(.metadata.name)"'
```

For those that do set it, confirm the named Service exists and is headless:

```bash
kubectl get service <service-name> -n <namespace> -o jsonpath='{.spec.clusterIP}'
```

A headless Service prints `None`.

## An empty field is the only trigger

ZopNight reads the `serviceName` value collected for each StatefulSet and fires when it is empty
or absent. It does not look up the Service itself, so a StatefulSet naming a Service that was
later deleted, or one that is not headless, passes this check. There is no threshold or waiting
period.

## Why the fix means recreating the object

The Kubernetes API server treats `serviceName` as immutable once a StatefulSet exists, alongside
the selector, volume claim templates and pod management policy. You cannot patch it in, which is
why the check is worth catching early. The API reference also says the service must exist before
the StatefulSet, so create it first.

## A reliability gap without a price tag

The finding carries no saving. The risk is clustered applications that cannot discover their
peers, and clients hard-coded to per-pod names that will not resolve.

## Adding the headless service

1. Create a Service with `clusterIP: None` whose selector matches the StatefulSet's pod labels.
2. Update the StatefulSet manifest to set `serviceName` to that Service.
3. Delete the old object without its pods:
   `kubectl delete statefulset <name> -n <namespace> --cascade=orphan`.
4. Apply the updated manifest. Existing PersistentVolumeClaims are not deleted with the
   StatefulSet, so the pods keep their data.
5. Restart the pods one ordinal at a time, highest first, and check each resolves under the new
   domain before moving on.

**Warning**
Between steps 3 and 4 the pods have no controller. If one fails in that window, nothing recreates it.
