Interactive · Kubernetes DNS

One lookup, all the way out

The architecture stays put; only its state changes. Thick grey lines are paths that always exist — the coloured line that lights on top is the one carrying this query. Look up a cluster name and the query never leaves the cluster. Look up an external name and the same line of code costs five queries.

← → to step click a box to jump to it click the current step again for details

Why any of this exists

A pod’s IP is assigned when it starts and gone when it dies. A rollout replaces every pod and every address; scale up, drain a node, fail a probe, and the set of addresses behind payments is different from what it was a minute ago. No config file can hold an IP, and nothing can be reached by remembering where it was last time.

A Service fixes half of that, putting a stable ClusterIP in front of a changing set of pods. But a ClusterIP is still an address, allocated when the Service is created — and your code is written before that happens. So the actual product of cluster DNS is a name you can predict. payments.shop.svc.cluster.local follows mechanically from the Service name and the namespace, which means you can write it in a different repo, on a different team, before the Service exists.

Kubernetes does have a discovery API — EndpointSlice — and almost nothing calls it directly. DNS won because it is the one lookup every language and every binary already speaks. No SDK, no client library, no sidecar, no code change: connect("payments") behaves the same from Go, Python, a JVM service, curl, and a vendor binary you cannot recompile.

The cost is what this page traces. Choosing DNS means inheriting DNS’s semantics, including a resolver stub that assumes a name with few dots is a fragment needing a suffix. That assumption is free for payments. It is why db.corp.example costs five queries.

Look up
Lines always exists git traffic control plane cache to your cluster Marker work inside one component
One DNS lookup, traced from checkout to payments and out to on-premises A VPC holds the EKS control plane, which AWS manages, and the data plane, which is your nodes. Three nodes are drawn: checkout on the first, CoreDNS on the second, payments on the third. Every node runs a NodeLocal DNS cache because it is a DaemonSet; only one runs a CoreDNS pod because CoreDNS is a Deployment. checkout asks the cache on its own node, the query crosses to CoreDNS, CoreDNS answers from the payments Service it watches, and only then does checkout connect. Names the cluster does not own leave through the VPC resolver, a forwarding rule and an outbound endpoint to the on-premises resolver. Two panels down the right record what the pod actually sent and what is cached as a result. VPC 10.20.0.0/16 EKS CONTROL PLANE · AWS-MANAGED kube API watched, not queried Service · payments ClusterIP 10.0.5.12 DATA PLANE · YOUR NODES node-1 checkout ndots:5 NodeLocal 169.254.20.10 node-2 CoreDNS Deployment NodeLocal DaemonSet node-3 payments the callee NodeLocal DaemonSet VPC + 2 the node's resolver resolver rule one per domain outbound endpoint 10,000 QPS per IP on-prem DNS corp.example queries actually sent one call from your code what is cached now and for how long

The steps, with the knob that governs each

#HopWhat happensSetting

Verify it against your own cluster

Claim on this pageHow to check it
ndots is 5, and you did not set itkubectl exec <pod> -- cat /etc/resolv.conf
One short name becomes several querieskubectl exec <pod> -- dig +search +trace payments
A trailing dot skips the search listkubectl exec <pod> -- dig db.corp.example. +noall +stats
CoreDNS ships two replicas, whatever the node countkubectl get deploy coredns -n kube-system
Its config is a chain, in orderkubectl get cm coredns -n kube-system -o jsonpath='{.data.Corefile}'
NodeLocal runs on every node, or nonekubectl get ds node-local-dns -n kube-system
The interface allowance is being hitethtool -S eth0 | grep linklocal_allowance_exceeded