Headless & Discovery
A Headless Service is a Service with clusterIP: None, which means it doesn’t get a virtual IP address. Instead of routing traffic through a Service IP, Headless Services return individual pod IPs directly via DNS. This enables direct pod-to-pod communication and custom service discovery patterns, making it essential for StatefulSets and applications that need to know about individual pod instances.
What is a Headless Service?
A Headless Service is created by setting clusterIP: None in the Service spec. Unlike regular Services that provide a single IP for load balancing, Headless Services return all pod IPs that match the selector, allowing clients to connect directly to specific pods.
How Headless Services Work
When you create a Headless Service:
- No ClusterIP allocated - The Service doesn’t get a virtual IP
- DNS returns pod IPs - DNS queries return individual pod IP addresses
- Direct pod access - Clients can connect directly to specific pods
- StatefulSet integration - Works seamlessly with StatefulSets for stable pod identities
Headless Service Example
Here’s a basic Headless Service:
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None # This makes it headless
selector:
app: web
ports:
- port: 80
targetPort: 8080
Key difference: clusterIP: None instead of type: ClusterIP (or omitting type).
DNS Behavior
The DNS behavior differs significantly between regular and Headless Services:
Regular Service DNS
# DNS query returns Service IP
$ nslookup web-service.default.svc.cluster.local
Name: web-service.default.svc.cluster.local
Address: 10.96.0.1 # Single Service IP
Headless Service DNS
# DNS query returns all pod IPs
$ nslookup web-headless.default.svc.cluster.local
Name: web-headless.default.svc.cluster.local
Address: 10.244.1.5 # Pod 1 IP
Address: 10.244.1.6 # Pod 2 IP
Address: 10.244.2.3 # Pod 3 IP
StatefulSet Integration
Headless Services are essential for StatefulSets, which provide stable network identities for pods:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web-headless # References Headless Service
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.21
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
targetPort: 80
With StatefulSets, each pod gets a stable DNS name:
web-0.web-headless.default.svc.cluster.local→ Pod 0web-1.web-headless.default.svc.cluster.local→ Pod 1web-2.web-headless.default.svc.cluster.local→ Pod 2
Direct Pod Access
Headless Services enable direct access to specific pods, which is useful for:
Database Replicas
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
Clients can connect to specific database replicas:
- Primary:
postgres-0.postgres-headless.default.svc.cluster.local:5432 - Replica:
postgres-1.postgres-headless.default.svc.cluster.local:5432
Leader Election
Applications can use Headless Services for leader election patterns:
apiVersion: v1
kind: Service
metadata:
name: app-headless
spec:
clusterIP: None
selector:
app: my-app
ports:
- port: 8080
targetPort: 8080
Each pod instance can discover all other instances via DNS and implement leader election logic.
SRV Records
Headless Services also create SRV (Service) records that provide port information:
# Query SRV records
$ dig SRV app-headless.default.svc.cluster.local
# Returns:
app-headless.default.svc.cluster.local. 30 IN SRV 10 50 8080 web-0.app-headless.default.svc.cluster.local.
app-headless.default.svc.cluster.local. 30 IN SRV 10 50 8080 web-1.app-headless.default.svc.cluster.local.
app-headless.default.svc.cluster.local. 30 IN SRV 10 50 8080 web-2.app-headless.default.svc.cluster.local.
SRV records include:
- Priority (10)
- Weight (50)
- Port (8080)
- Target (pod DNS name)
Use Cases
1. StatefulSets
Headless Services are required for StatefulSets to provide stable network identities:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless # Must be Headless
replicas: 3
# ... rest of spec
2. Database Clusters
Database clusters (MongoDB, Cassandra, etc.) need to know about all members:
apiVersion: v1
kind: Service
metadata:
name: cassandra-headless
spec:
clusterIP: None
selector:
app: cassandra
ports:
- port: 9042
targetPort: 9042
Each Cassandra node can discover all other nodes via DNS.
3. Service Mesh
Service meshes often use Headless Services for direct pod-to-pod communication:
apiVersion: v1
kind: Service
metadata:
name: app-headless
spec:
clusterIP: None
selector:
app: my-app
4. Custom Service Discovery
Applications implementing custom service discovery can query Headless Service DNS to get all pod IPs:
import socket
# Get all pod IPs
pod_ips = socket.gethostbyname_ex('app-headless.default.svc.cluster.local')[2]
# Returns: ['10.244.1.5', '10.244.1.6', '10.244.2.3']
Headless vs Regular Service
Regular Service:
- ✅ Automatic load balancing
- ✅ Single stable endpoint
- ✅ Simpler for most use cases
- ❌ Can’t access specific pods
- ❌ No pod-level DNS names
Headless Service:
- ✅ Direct pod access
- ✅ Pod-level DNS names (with StatefulSets)
- ✅ Custom service discovery
- ✅ Required for StatefulSets
- ❌ No automatic load balancing
- ❌ Client must handle pod selection
Complete Example: StatefulSet with Headless Service
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web-headless
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.21
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
targetPort: 80
DNS names created:
web-0.web-headless.default.svc.cluster.localweb-1.web-headless.default.svc.cluster.localweb-2.web-headless.default.svc.cluster.localweb-headless.default.svc.cluster.local(returns all pod IPs)
When to Use Headless Services
Use Headless Services when:
✅ StatefulSets - Required for StatefulSet stable network identities
✅ Direct pod access - Need to connect to specific pod instances
✅ Database clusters - Database replicas that need to discover each other
✅ Custom discovery - Implementing custom service discovery logic
✅ Leader election - Applications that need to know about all instances
✅ Service mesh - Direct pod-to-pod communication in service meshes
Don’t use Headless Services when:
- You need automatic load balancing (use regular Service)
- Simple service-to-service communication (use ClusterIP)
- You don’t need pod-level access
- Standard microservices patterns work fine
Best Practices
- Use with StatefulSets - Headless Services are designed for StatefulSets
- Document pod selection - If clients choose pods, document the selection logic
- Handle pod failures - Clients must handle cases where specific pods are unavailable
- Use SRV records - Leverage SRV records for port information
- Monitor pod health - Since there’s no load balancing, monitor individual pods
- Consider readiness - Use readiness probes to ensure pods are ready before DNS inclusion
- Test DNS resolution - Verify DNS returns expected pod IPs
- Use stable pod names - With StatefulSets, pod names are stable and predictable
Troubleshooting
DNS Not Returning Pod IPs
- Check Service selector:
kubectl get service <service-name> -o yaml | grep selector - Verify pods match selector:
kubectl get pods -l app=my-app - Check clusterIP is None:
kubectl get service <service-name> -o jsonpath='{.spec.clusterIP}' - Test DNS resolution:
kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup <service-name> - Check CoreDNS:
kubectl get pods -n kube-system -l k8s-app=kube-dns
Pod-Specific DNS Not Working
- Verify StatefulSet: Pod-specific DNS only works with StatefulSets
- Check pod name format: Should be
<statefulset-name>-<ordinal> - Verify serviceName: StatefulSet
serviceNamemust match Headless Service name - Test FQDN: Use full FQDN:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
Cannot Connect to Specific Pod
- Verify pod is running:
kubectl get pods - Check pod IP:
kubectl get pods -o wide - Test connectivity:
kubectl exec <pod-name> -- wget -O- http://<target-pod-ip>:<port> - Check Network Policies: Verify Network Policies allow pod-to-pod communication
- Verify port: Ensure target port is correct
SRV Records Not Available
- Check DNS server: Ensure CoreDNS supports SRV records
- Use dig or nslookup: Some tools don’t query SRV records by default
- Verify port in Service: SRV records require port specification
- Test with dig:
kubectl run -it --rm debug --image=busybox --restart=Never -- dig SRV <service-name>
See Also
- Services Overview - Introduction to Kubernetes Services
- ClusterIP - Regular Services with load balancing
- Endpoints & EndpointSlice - How Services track pods
- StatefulSets - Stateful workloads that use Headless Services
- DNS & CoreDNS - Kubernetes DNS service