By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
A headless Service in Kubernetes is a Service without a ClusterIP. Instead of load-balancing traffic, it returns the Pod IPs directly via DNS. This is critical when: - You need direct Pod-to-Pod communication (e.g., stateful apps like databases, Kafka, or Redis clusters).- You want custom discovery logic (e.g., a leader election system where clients need to know which Pod is the leader).- You’re running StatefulSets (where each Pod has a stable identity, like db-0, db-1).
db-0
db-1
Real-world scenario:You’re deploying a PostgreSQL cluster with Patroni (a high-availability tool). Each Pod (pg-0, pg-1, pg-2) needs to: 1. Discover its peers by name (not just a load-balanced IP).2. Communicate directly (e.g., pg-1 needs to talk to pg-0 for replication).3. Survive restarts (Pod IPs change, but DNS names like pg-0.postgres.default.svc.cluster.local must stay stable).
pg-0
pg-1
pg-2
pg-0.postgres.default.svc.cluster.local
What breaks if you ignore this?- Your StatefulSet apps fail (e.g., databases can’t replicate).- Service meshes (Istio, Linkerd) misbehave (they rely on DNS for sidecar injection).- Debugging becomes a nightmare (you can’t nslookup individual Pods).
nslookup
clusterIP: None
pod-0.service.namespace.svc.cluster.local
pod-0
pod-1
_postgres._tcp.postgres.default.svc.cluster.local
my-db.example.com
Default
ClusterFirst
None
kubectl exec -it pod -- cat /etc/resolv.conf
.)
5
postgres
kubectl
Deploy a 3-node Redis cluster where: - Each Pod (redis-0, redis-1, redis-2) can discover its peers via DNS.- Clients can connect to any Pod directly (e.g., redis-0.redis.default.svc.cluster.local:6379).
redis-0
redis-1
redis-2
redis-0.redis.default.svc.cluster.local:6379
# redis-headless-service.yaml apiVersion: v1 kind: Service metadata: name: redis labels: app: redis spec: clusterIP: None # ⚠️ This makes it headless! ports: - port: 6379 name: redis selector: app: redis
Apply it:
kubectl apply -f redis-headless-service.yaml
Verify:
kubectl get svc redis
Expected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE redis ClusterIP None <none> 6379/TCP 5s
✅ Key check: CLUSTER-IP must be None.
CLUSTER-IP
# redis-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: serviceName: redis # ⚠️ Must match the headless Service name! replicas: 3 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7 ports: - containerPort: 6379 name: redis command: ["redis-server", "--cluster-enabled", "yes", "--cluster-config-file", "/data/nodes.conf"] volumeMounts: - name: redis-data mountPath: /data volumeClaimTemplates: - metadata: name: redis-data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi
kubectl apply -f redis-statefulset.yaml
kubectl get pods -w
Wait until all Pods are Running:
Running
NAME READY STATUS RESTARTS AGE redis-0 1/1 Running 0 30s redis-1 1/1 Running 0 20s redis-2 1/1 Running 0 10s
Launch a debug Pod to test DNS:
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- sh
Inside the Pod, run:
nslookup redis
Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: redis Address 1: 10.244.1.5 redis-0.redis.default.svc.cluster.local Address 2: 10.244.2.4 redis-1.redis.default.svc.cluster.local Address 3: 10.244.1.6 redis-2.redis.default.svc.cluster.local
✅ Key check: You see all 3 Pod IPs (not a single ClusterIP).
Now test individual Pod DNS:
nslookup redis-0.redis
Name: redis-0.redis Address 1: 10.244.1.5 redis-0.redis.default.svc.cluster.local
✅ Key check: Each Pod has a stable DNS name (redis-0.redis, redis-1.redis, etc.).
redis-0.redis
redis-1.redis
Exec into redis-0:
kubectl exec -it redis-0 -- redis-cli
Inside Redis, run:
CLUSTER NODES
Expected output (shows all 3 nodes):
3e3a6cb0d9a9a87168e266b0a0b24026c0aae377 10.244.1.5:6379@16379 myself,master - 0 1650982452000 1 connected 0-5460 a211e24273170ff94509829de567aece3340430a 10.244.2.4:6379@16379 master - 0 1650982451000 2 connected 5461-10922 292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 10.244.1.6:6379@16379 master - 0 1650982450000 3 connected 10923-16383
✅ Key check: All 3 Pods are connected in a cluster.
redis
requests
limits
yaml resources: requests: cpu: "500m" memory: "1Gi" limits: cpu: "1" memory: "2Gi"
gp2
standard
redis-headless
postgres-headless
app: redis
env: prod
yaml readinessProbe: exec: command: ["redis-cli", "ping"] initialDelaySeconds: 5 periodSeconds: 5
coredns_dns_requests_total
kubectl logs -l app=redis
kube_pod_status_ready
coredns_dns_requests_failed_total
serviceName
nslookup redis-0
spec.serviceName
selector
NXDOMAIN
spec.selector
google.com
ndots
/etc/resolv.conf
ndots: 2
dnsConfig
Headless Service: Returns Pod IPs directly (no load balancing).
"How do you make a StatefulSet Pod discoverable by DNS?"
Pods get DNS names like pod-0.service.namespace.svc.cluster.local.
"What happens if you delete a headless Service?"
StatefulSet Pods keep running, but they can’t discover each other.
"How do you debug DNS resolution in Kubernetes?"
bash kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- sh
bash cat /etc/resolv.conf
bash nslookup redis-0.redis
ExternalName
kube-dns
Deploy a 2-node MongoDB replica set where: - Each Pod (mongo-0, mongo-1) can discover its peer via DNS.- The replica set initializes automatically.
mongo-0
mongo-1
bash kubectl exec -it mongo-0 -- mongosh --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongo-0.mongo:27017'}, {_id: 1, host: 'mongo-1.mongo:27017'}]})"
mongo-0.mongo
mongo-1.mongo
--replSet
rs.initiate()
kubectl apply -f service.yaml
kubectl run dns-test --image=busybox --rm -it -- nslookup redis
kubectl logs -n kube-system -l k8s-app=kube-dns
dig SRV _postgres._tcp.postgres.default.svc.cluster.local
dnsPolicy: ClusterFirst
/etc/coredns/Corefile
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.