Skip to content

DNS and Service Discovery

CoreDNS gives every Service a predictable DNS name so Pods can find each other by name instead of by IP, and a headless Service flips that from one load-balanced IP to the individual IPs of each backing Pod.

CoreDNS runs as a Deployment inside the cluster (typically in kube-system) and serves as the cluster’s internal DNS server. Every Pod is configured, via its /etc/resolv.conf, to send DNS queries to CoreDNS. The moment a Service is created, CoreDNS starts answering for it with a fully qualified name of the form:

<service-name>.<namespace>.svc.cluster.local
Terminal window
# From inside any Pod in the cluster
nslookup payments.default.svc.cluster.local

Within the same namespace, the short form also works, because a Pod’s DNS search path automatically includes its own namespace’s svc.cluster.local suffix:

Terminal window
# From a Pod that lives in the same namespace as the "payments" Service
nslookup payments
curl http://payments/health

For a normal (non-headless) Service, that name resolves to a single A/AAAA record: the Service’s ClusterIP. Every request to that name still gets load-balanced across the backing Pods by kube-proxy — DNS just gets a client to the virtual IP.

Headless Services: skipping the load balancer

Section titled “Headless Services: skipping the load balancer”

Sometimes a client needs to reach a specific Pod, not “any healthy Pod behind the Service.” Setting clusterIP: None creates a headless Service:

apiVersion: v1
kind: Service
metadata:
name: cache
spec:
clusterIP: None
selector:
app: cache
ports:
- port: 6379

A headless Service gets no virtual IP at all. Instead, CoreDNS returns the individual IPs of every matching, ready Pod directly. A query still resolves the same name, cache.default.svc.cluster.local, but the answer is now a list of Pod IPs instead of one ClusterIP.

A StatefulSet gives each of its Pods a stable identity — web-0, web-1, web-2 — and pairs that with a headless Service (via spec.serviceName) so each Pod also gets its own individual, stable DNS record:

<pod-name>.<service-name>.<namespace>.svc.cluster.local
Terminal window
# Reach one specific ordinal Pod directly, not a load-balanced peer
nslookup web-0.cache.default.svc.cluster.local

This is exactly what distributed, stateful systems need: a database replica has to reconnect to the same peer it had before, not a random one a load balancer happens to pick. Without a headless Service, that per-Pod DNS record would not exist at all.

flowchart TB
  pod["Client Pod"] -->|"DNS query"| coredns["CoreDNS"]
  coredns -->|"normal Service"| vip["Service ClusterIP\n(load-balanced)"]
  coredns -->|"headless Service"| p0["web-0 IP"]
  coredns -->|"headless Service"| p1["web-1 IP"]
  coredns -->|"headless Service"| p2["web-2 IP"]
A Pod's DNS query resolved by CoreDNS: a Service ClusterIP, or individual Pod IPs for a headless Service
What is the full DNS name format Kubernetes automatically assigns to a Service
Why do StatefulSets need a headless Service for DNS purposes
What role does CoreDNS play in the cluster
What does a headless Service (clusterIP: None) return when queried by DNS