DNS and Service Discovery
The idea in one sentence
Section titled “The idea in one sentence”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 and the Service DNS name
Section titled “CoreDNS and the Service DNS name”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# From inside any Pod in the clusternslookup payments.default.svc.cluster.localWithin 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:
# From a Pod that lives in the same namespace as the "payments" Servicenslookup paymentscurl http://payments/healthFor 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: v1kind: Servicemetadata: name: cachespec: clusterIP: None selector: app: cache ports: - port: 6379A 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.
Why StatefulSets rely on this
Section titled “Why StatefulSets rely on this”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# Reach one specific ordinal Pod directly, not a load-balanced peernslookup web-0.cache.default.svc.cluster.localThis 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"]