Design Patterns · Principal Track
CoreDNS is per cluster and the resolution edge is per estate; the ceiling that ends most DNS incidents is a per-interface packet allowance shared with two other services. Checked against the Kubernetes, CoreDNS and AWS documentation · August 2026.
cluster.local is answered by CoreDNS from the API server and never appears in Route 53. corp.example is a Route 53 private hosted zone and never appears in the Corefile. Records reach that zone from ExternalDNS or your IaC — CoreDNS is a resolver, not a registrar, and holds no AWS credentials.max(nodes/16, cores/256).| # | Resolution | Decision | Recommendation | Ref |
|---|
Conceptual and Requirements deliberately break the shared topology, because a bar and a capability model have no deployment shape. That is what this table owes the reader — and the mapping is many-to-many.
| Capability | What implements it | Where it is configured |
|---|---|---|
| Who owns a name | The cluster domain vs the corporate zone — a written boundary | kubernetes cluster.local · Route 53 private hosted zones |
| Where it is registered | Service objects; zone records held elsewhere | Service.metadata.name · namespace |
| Who is asked first | The node-local cache, reached at a link-local address | node-local-dns DaemonSet · 169.254.0.0/16 |
| Who is asked next | CoreDNS, then the node’s own resolver | forward . /etc/resolv.conf |
| When to stop asking | An authoritative plugin, or an explicit default route | kubernetes · forward FROM TO |
| Where an answer may rest | Three layers: node cache, CoreDNS, the VPC resolver’s own cache | cache 30 · success / denial |
| How long, including denials | Positive and negative lifetimes, set deliberately | cache: 3600 max · 1800 denial · 5s floor |
| Who publishes a name outward | ExternalDNS, or your IaC for static records — never CoreDNS | Service / Ingress annotations · --txt-owner-id |
| Who may read the log | Query logging, scoped like security telemetry | Resolver query log configurations |
Follow the hardest one — how a corporate name resolves from inside a pod — and watch it resolve from a constraint into something an SRE could stand up.
| Resolution | The same concern at this level | The decision captured |
|---|---|---|
| Environment | Corporate zones are authoritative somewhere this team does not run, and reaching them needs a path that may not exist yet. | Build the path deliberately in both directions, and date the dependency |
| Requirements | Not a component — a bar: a pod resolves cluster, corporate and public names, each with a stated owner and a test. | Three paths, three owners, three alerts — not one line saying “DNS works” |
| Conceptual | Two capabilities: decide who is authoritative, then decide where a query goes when nobody here is. | Per-domain rules with an explicit default — one-way door |
| Logical | CoreDNS forwards to the node’s own resolver, and a rule matches the domain. It never points at on-premises directly. | Forward to the VPC resolver, so one rule set governs the whole estate |
| Physical | An outbound endpoint with addresses sized against measured peak, and the Corefile carried as add-on configuration so it survives an upgrade. | Measure before trusting 10,000 QPS; never edit the ConfigMap directly |
| Phase | What ships | Gate before moving on |
|---|---|---|
| 1 | Baseline measurement before any change: query rate per node, and the interface allowance counter. | You know the current number, so later you can tell whether anything improved |
| 2 | The resolution edge in a shared-services account — outbound and inbound endpoints, zones, rules. | Both directions tested, not just outbound |
| 3 | Rules associated to one non-production VPC. Corporate names resolve from a pod there. | No query for a private name reaches public recursion |
| 4 | The node-local cache as a DaemonSet on the same non-production cluster. | The interface allowance counter measurably drops |
| 5 | CoreDNS autoscaling enabled with a floor above two, and cache lifetimes set deliberately. | A denial window short enough that a late Service recovers on its own |
| 6 | Production, with the Corefile carried as add-on configuration from the first commit. | An add-on upgrade rehearsed, and the conditional forwarder observed to survive it |
| Team | Owns | The thing they get paged for |
|---|---|---|
| Platform | CoreDNS, the node cache, the add-on version and its configuration | Resolution failing inside a cluster |
| Network | Endpoints, forwarding rules, zone associations, the private link | Corporate names failing estate-wide |
| Whoever runs ExternalDNS | The records published into the private zone, and the owner id per cluster | A name that resolves to the wrong cluster’s load balancer |
| Application teams | The names their code asks for, and whether they are absolute | Their own latency, when the fix is a trailing dot |
| Security | Query-log access, and what leaves the estate when a rule is missing | Internal hostnames appearing in a log nobody here owns |
| Corporate DNS owners | The zones this design forwards to, and the rule on their resolver | An outage that breaks every cross-boundary call |
| Failure | Why it happens | What blunts it |
|---|---|---|
| A conditional forwarder disappears | On EKS the add-on fully manages data.Corefile, and the documentation’s own example of an overwritten edit is a conditional forwarder for on-premises resolution. | Carry it in the add-on’s configurationValues, and rehearse an upgrade before production |
| DNS times out under load with nothing obviously wrong | The link-local allowance is 1024 packets per second per interface, shared with the metadata and time services. Excess packets are rejected silently. | Watch linklocal_allowance_exceeded; run a node-local cache so hits never become packets |
| The endpoint saturates at a sixth of its rated capacity | 10,000 QPS per IP drops to as low as 1,500 where connection tracking is enforced or queries arrive via an NLB. | Measure the real rate; add an address once peak passes half of any one interface |
| A pod stays broken after its Service exists | Denials cache for up to 1800 seconds. The pod asked too early and the negative answer outlived the fix. | Shorten the denial window deliberately, and treat it as a design value rather than a default |
| One call becomes five queries | ndots:5 counts dots, not intent, so a complete external hostname is treated as a fragment and the search list runs first. | A trailing dot, or a lower ndots per workload via dnsConfig |
| Private names leak to public recursion | A forwarding chain with no explicit default sends unmatched queries out to the internet. | Per-domain rules with a stated default, reviewed as a set |
| A DNS controller can rewrite any zone in the account | The EKS community add-on ships with AmazonRoute53FullAccess, which is far wider than publishing records for one cluster. | Reduce it to route53:ChangeResourceRecordSets, ListHostedZones and ListResourceRecordSets, scoped to the zones that cluster may manage |
| Two clusters fight over one record | ExternalDNS uses a TXT record to mark ownership, and the owner id must be unique per deployment. Two clusters sharing a zone with the same id will each try to own the same name. | Set --txt-owner-id per cluster, and treat it as immutable once set |
| A hosted zone stops accepting associations | A private hosted zone associates to 300 VPCs, and an estate reaches that quietly. | Move to Route 53 Profiles before the wall, not after |
ndots:5Behaviour, defaults and limits checked against these sources in August 2026. Recommendations are marked as such: where the documentation prescribes nothing — how many resolution edges to run, whether to deploy a node-local cache — the page says so rather than implying a rule exists. Sizing figures on the canvases are illustrative.