15.1 CoreDNS: The Kubernetes Cluster DNS
Right, let’s talk about CoreDNS. You’ve probably heard it’s the “DNS for Kubernetes,” which is true, but that undersells it. It’s less like a simple phone book and more like the hyper-intelligent, slightly overworked switchboard operator for your entire cluster. It’s the reason your my-app-service.default.svc.cluster.local doesn’t just resolve to a hopeful dream, but to an actual IP address that other pods can talk to.
Before we dive in, a moment of silence for its predecessor, kube-dns. It worked, mostly, but it was a bit of a Rube Goldberg device—a dnsmasq container, a kube-dns container, and a sidecar for metrics, all held together with hope and YAML. CoreDNS is a single, blessedly simple Go binary that does everything with far more grace and configurability. The Kubernetes designers made a great call ditching the franken-service.
How CoreDNS Actually Works (It’s Not Magic)
CoreDNS isn’t polling the Kubernetes API constantly. That would be inefficient and frankly, a bit rude. Instead, the Kubernetes API itself pushes service and endpoint information to CoreDNS. CoreDNS watches for changes to Services and EndpointSlices (the modern, more efficient replacement for Endpoints) and updates its in-memory database accordingly.
When your pod sends a DNS query to the CoreDNS Pod IP (which it gets from the kubelet at startup), CoreDNS checks its loaded zones. The key one for you is the cluster.local zone. This is where the magic happens. A query for nginx-service.default.svc.cluster.local gets broken down:
nginx-service: The Service name.default: The namespace it lives in.svc: This tells CoreDNS you’re looking for a Service (it can also handle Pod DNS records).cluster.local: The base domain for the cluster.
CoreDNS matches this, finds the corresponding ClusterIP for that Service, and sends it back. Done. For a headless Service (one without a ClusterIP, defined with clusterIP: None), it returns the set of IPs of the individual Pods backing the service. This is incredibly useful for stateful applications that need direct peer discovery.
The All-Important Corefile
This is CoreDNS’s configuration file, its brain, its entire personality. It’s defined in a ConfigMap, because of course it is. Let’s see what the default one looks like. Go ahead, run this in your cluster:
kubectl get configmap -n kube-system coredns -o yaml
You’ll see something gloriously sensible. No XML, no JSON, just a clean, directive-based config called the Corefile.
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
Let’s break this down like a good joke:
errors: Logs… well, errors. Useful.health: Provides a health endpoint on port 8080. If this fails, the pod is deemed unhealthy.ready: An endpoint on port 8181 that returns 200 OK after CoreDNS is fully up and has loaded its plugins. This is crucial for startup sequencing.kubernetes cluster.local ...: This is the big one. This plugin is what makes CoreDNS understand Kubernetes Services and Pods. Thepods insecureoption enables pod name-to-IP resolution (e.g.,1-2-3-4.default.pod.cluster.local), but it’s generally considered a security risk for production. Thefallthroughdirective tells CoreDNS to pass unmatched requests down the chain.prometheus: Exposes metrics on port 9153. Feed this to your monitoring stack.forward . /etc/resolv.conf: This is your escape hatch to the outside world. Any query that doesn’t match thecluster.localdomain (likegoogle.com) gets forwarded to the nameservers listed in the pod’s/etc/resolv.conffile, which are typically your node’s resolvers or something upstream.cache: Reduces load by caching responses. Thettlin thekubernetesplugin helps manage this.loop: Detects simple forwarding loops and nukes the process if it finds one (which sounds dramatic, but it’s better than a melting cluster).reload: Allows automatic reload of the Corefile if the ConfigMap changes without restarting the pod. A fantastic quality-of-life feature.loadbalance: Does a round-robin shuffle of identical A, AAAA, or MX records. This is why subsequent DNS queries for a service might return the IPs in a different order, providing a basic form of client-side load balancing.
When It Goes Sideways: Common Pitfalls
The Most Common Offender:
ndots. This is the culprit 90% of the time. Your pod’s/etc/resolv.confhas aoptions ndots:5line. This means any DNS query with fewer than five dots in the name will first try appending all the search domains (default.svc.cluster.local,svc.cluster.local,cluster.local) before trying the absolute name. A query forredisinside a pod becomesredis.default.svc.cluster.local., which works. A query formy.database.combecomesmy.database.com.default.svc.cluster.local.(which fails) before finally tryingmy.database.com.(which works). This generates massive, unnecessary load on CoreDNS. The fix? For external calls, use fully qualified domain names (end them with a dot), or, for heavy external callers, lower thendotsvalue in your pod spec.dnsConfig: options: - name: ndots value: "2"Pod Restart Loops: If your
readinessProbefor CoreDNS is just a TCP check on port 53, it might start serving traffic before it’s actually ready to answer Kubernetes queries. This causes other pods that depend on service discovery to fail on startup, which… well, you see the loop. Always use thereadyplugin’s endpoint (port 8181) for your readiness probe in your CoreDNS Deployment.Hitting the Default Limits: CoreDNS defaults to a relatively low cache size and can be memory constrained. In a large cluster with thousands of pods and services, you might see
SERVFAILresponses under load. You need to tune this. You can increase cache size, add theautopathplugin for smarter ndots handling, or scale CoreDNS horizontally.