7.2 Cluster DNS & Service Discovery with CoreDNS
Key Takeaways
- CoreDNS is the CNCF graduated project that serves cluster DNS, running as a Deployment in kube-system and exposed through the kube-dns Service.
- Every Service gets an A/AAAA record at <service>.<namespace>.svc.cluster.local resolving to the Service ClusterIP.
- The kubelet writes a search list into each Pod's /etc/resolv.conf so a bare Service name resolves within the Pod's own namespace, while cross-namespace calls need at least <service>.<namespace>.
- A headless Service (clusterIP: None) returns the individual Pod IPs instead of a single virtual IP, which is what gives StatefulSet Pods stable per-Pod DNS names.
- Environment-variable service discovery exists but only covers Services created before the Pod, so DNS is the correct mechanism in every modern cluster.
7.2 Cluster DNS & Service Discovery with CoreDNS
Quick Answer: Kubernetes ships an in-cluster DNS server — CoreDNS, a CNCF graduated project — running as a Deployment in
kube-systemand fronted by a Service conventionally namedkube-dns. Every Service receives an A/AAAA record at<service>.<namespace>.svc.cluster.localpointing at its ClusterIP. The kubelet writes a search list into every Pod's/etc/resolv.conf, which is whycurl http://paymentsworks inside the same namespace but a cross-namespace call needspayments.finance.
Section 7.1 explained that a Service provides a stable virtual IP. This section explains how a Pod ever learns that IP in the first place — and why "it's always DNS" is the oldest joke in platform engineering.
1. Why Discovery Must Be Dynamic
Pod IPs change on every reschedule. Hard-coding one is meaningless. Kubernetes therefore layers two stable abstractions on top:
Pod ──asks──► CoreDNS ──returns──► Service ClusterIP
│
kube-proxy rules
▼
one of the healthy backing Pod IPs
The application only ever needs to know a name. CoreDNS turns the name into a ClusterIP; kube-proxy turns the ClusterIP into a live endpoint.
2. CoreDNS
CoreDNS replaced the older kube-dns add-on as the cluster DNS default in Kubernetes 1.13. It is a single Go binary built from chained plugins, configured through a Corefile stored in a ConfigMap:
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153 # exposes CoreDNS metrics for scraping
forward . /etc/resolv.conf # anything not cluster.local goes upstream
cache 30
loop
reload
loadbalance
}
Key facts KCNA expects:
- CoreDNS runs as a Deployment (typically two replicas) in
kube-system, not a DaemonSet. - It watches the Kubernetes API for Services and EndpointSlices and answers from that live view — it is not a zone file that needs reloading.
- The
forwardplugin sends non-cluster names (github.com) to the node's upstream resolvers. - The
prometheusplugin makes CoreDNS observable, which matters because DNS latency shows up as application latency everywhere at once. - NodeLocal DNSCache is an optional DaemonSet that puts a caching resolver on every node, cutting cross-node DNS traffic and conntrack pressure on large clusters.
3. The DNS Name Format
Services
<service-name>.<namespace>.svc.cluster.local
| Record type | Applies to | Returns |
|---|---|---|
| A / AAAA | Normal Service | The Service's ClusterIP |
| A / AAAA | Headless Service | The IP of every ready backing Pod |
| SRV | Named Service ports | _<port-name>._<protocol>.<service>.<namespace>.svc.cluster.local → port and target |
| CNAME | ExternalName Service | The external DNS name, with no proxying at all |
The Search List
The kubelet writes something like this into each Pod:
nameserver 10.96.0.10
search production.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
So, from a Pod in namespace production:
| What the app requests | What resolves | Works? |
|---|---|---|
payments | payments.production.svc.cluster.local | Yes, if payments is in production |
payments.finance | payments.finance.svc.cluster.local | Yes — cross-namespace |
payments.finance.svc.cluster.local | itself (FQDN) | Yes — most explicit |
payments for a Service in finance | payments.production… → NXDOMAIN | No |
The
ndots:5gotcha. Any name containing fewer than five dots is tried against every search-domain suffix first. Looking upapi.github.com(two dots) therefore generates three failing cluster queries before the correct one. On busy clusters this is a measurable source of DNS load and tail latency; the fix is a trailing dot (api.github.com.) or a per-PoddnsConfigloweringndots.
Pods
Pods also get records, with dots in the IP replaced by dashes: 10-244-1-15.production.pod.cluster.local. These are rarely used directly — except by StatefulSets.
4. Headless Services and StatefulSet Identity
Setting clusterIP: None creates a headless Service: no virtual IP is allocated and kube-proxy programs no rules. CoreDNS instead returns the A records of all ready backing Pods.
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
clusterIP: None # headless
selector:
app: redis
ports:
- port: 6379
Combined with a StatefulSet's serviceName, this produces the stable per-Pod names that clustered databases require:
redis-cluster-0.redis-service.production.svc.cluster.local
redis-cluster-1.redis-service.production.svc.cluster.local
redis-cluster-2.redis-service.production.svc.cluster.local
A Cassandra or Kafka node needs to address a specific peer, not "whichever replica the load balancer picks" — headless DNS is what makes that possible.
5. DNS Policies
spec.dnsPolicy controls what the kubelet writes into resolv.conf:
| Policy | Behaviour |
|---|---|
ClusterFirst (default) | Cluster DNS first; anything outside the cluster suffix is forwarded upstream |
Default | Inherit the node's resolver configuration; the Pod cannot resolve cluster names |
ClusterFirstWithHostNet | Required for Pods using hostNetwork: true that still need cluster DNS |
None | Ignore everything and use the explicit dnsConfig block instead |
Trap: a Pod with
hostNetwork: trueand the defaultClusterFirstsilently gets the node's resolver and cannot resolve Service names.ClusterFirstWithHostNetis the fix.
6. Environment-Variable Discovery
Kubernetes also injects Docker-link-style variables for every Service that existed before the Pod started:
PAYMENTS_SERVICE_HOST=10.96.31.7
PAYMENTS_SERVICE_PORT=80
This mechanism is legacy and has a fatal ordering flaw: a Service created after the Pod produces no variables, so it silently fails to the application. DNS is the correct answer for service discovery in any modern cluster; environment variables exist for backwards compatibility only.
7. Triaging DNS Failures
kubectl get pods -n kube-system -l k8s-app=kube-dns # are CoreDNS Pods Ready?
kubectl logs -n kube-system -l k8s-app=kube-dns # SERVFAIL? loop detected?
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- \
nslookup payments.finance.svc.cluster.local # resolve from inside
kubectl exec <pod> -- cat /etc/resolv.conf # right nameserver and search list?
kubectl get endpointslices -l kubernetes.io/service-name=payments
| Symptom | Usual cause |
|---|---|
| Nothing in the cluster resolves | CoreDNS Pods not Ready, or the CNI plugin is broken |
| One Service does not resolve | Name or namespace typo, or the Service selector matches no Pods |
| Resolves but connections fail | DNS is fine — check the Service port, readiness probes, or a NetworkPolicy |
| Intermittent timeouts under load | ndots amplification, conntrack exhaustion; deploy NodeLocal DNSCache |
| Works in one namespace only | A bare name was used across namespaces — qualify it |
A Pod in the production namespace runs curl http://payments but the payments Service lives in the finance namespace. What happens?
What does cluster DNS return for a headless Service (clusterIP: None)?
How does CoreDNS learn about newly created Services?