Architecture, Corefile, Plugins, and Troubleshooting
CoreDNS is the cluster DNS server. The kubelet programs each Pod’s /etc/resolv.conf so application DNS queries reach the cluster DNS Service. CoreDNS Pods usually run in kube-system and are exposed through the kube-dns Service (ClusterIP is commonly 10.96.0.10 on kubeadm-style clusters—yours may differ).
nameserver with the cluster DNS IP, a search list (namespace + svc.cluster.local + cluster.local), and options ndots:5 for Pods using dnsPolicy: ClusterFirst (default).kube-dns is historical; endpoints still target CoreDNS today. Labels like k8s-app=kube-dns remain common in manifests.--cluster-dns on the kubelet or equivalent; always verify with cat /etc/resolv.conf inside a Pod.The Corefile is CoreDNS configuration. In Kubernetes it is usually stored in the coredns ConfigMap in kube-system and mounted into each CoreDNS Pod. Edits require a rolling restart (or reload plugin) to take effect; invalid syntax can prevent Pods from becoming Ready.
Server blocks start with a zone and port (here .:53 for the root zone on port 53). Plugins run in a chain; order matters for which plugin handles a query first.
.: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 { max_concurrent 1000 }
cache 30
loop
reload
loadbalance
}
errors logs DNS errors. health exposes a health HTTP endpoint; lameduck delays shutdown. ready signals readiness when plugins are ready—used with readiness probes.
Authoritative for cluster.local and reverse zones. pods insecure enables Pod DNS names. fallthrough passes unmatched reverse queries on. ttl sets record TTL.
Exposes metrics on :9153 for scraping (not DNS traffic).
Forwards queries not answered by kubernetes to resolvers listed in the Pod’s /etc/resolv.conf (often node DNS). max_concurrent limits in-flight upstream queries.
Caches responses (here 30 seconds) to reduce load and latency.
loop detects forwarding loops. reload reloads Corefile on change. loadbalance randomizes answer order for A/AAAA records.
DEFAULT appears in the stock Kubernetes Corefile unless you removed it.
| Plugin | Purpose | When to use | Example | |
|---|---|---|---|---|
| kubernetes | Cluster DNS names for Services, Pods, endpoints | Always for in-cluster names | kubernetes cluster.local { pods verified } | DEFAULT |
| forward | Upstream resolution | External names, stub zones | forward . 8.8.8.8 | DEFAULT |
| cache | Response cache | Reduce latency/load | cache 30 | DEFAULT |
| errors | Error logging | Operations visibility | errors | DEFAULT |
| health | Liveness-style health | Kubelet probes | health { lameduck 5s } | DEFAULT |
| ready | Readiness endpoint | Rollouts | ready | DEFAULT |
| log | Query logging | Debug (noisy in prod) | log | |
| loop | Detect resolution loops | Safety with custom forwards | loop | DEFAULT |
| prometheus | Metrics | Observability | prometheus :9153 | DEFAULT |
| reload | Hot reload Corefile | ConfigMap edits | reload | DEFAULT |
| loadbalance | Shuffle A/AAAA | Simple client-side spread | loadbalance | DEFAULT |
| rewrite | Name translation | Legacy names, migrations | rewrite name exact a.internal b.svc.cluster.local | |
| hosts | Static hosts file | Overrides, test records | hosts { 10.0.0.5 api.local fallthrough } | |
| template | Pattern-based answers | Custom PTR/SRV logic | template IN A { answer "{name} 60 IN A 10.1.2.3" } |
CoreDNS (via the kubernetes plugin) synthesizes records from the API. Examples use placeholder IPs and names.
| Type | Example | Resolves to |
|---|---|---|
| A (Service) | my-svc.my-ns.svc.cluster.local | ClusterIP of the Service (or EndpointSlice-backed virtual IP) |
| A (headless) | my-headless.my-ns.svc.cluster.local | All ready Pod IPs for that Service (no single ClusterIP) |
| A (Pod) | 10-244-1-5.my-ns.pod.cluster.local | Pod IP (when enabled; dashed IPv4 in name) |
| SRV | _http._tcp.my-svc.my-ns.svc.cluster.local | Port and target hostnames for named ports |
| CNAME | external.my-ns.svc.cluster.local (ExternalName) | CNAME to external hostname per Service externalName |
Click each step to expand details. Example query: my-svc.my-ns.svc.cluster.local.
my-ns.svc.cluster.local) before treating the name as absolute. A name with 5+ dots is queried as FQDN first. This is why my-svc becomes my-svc.my-ns.svc.cluster.local when your namespace is my-ns and search list includes svc.cluster.local.Search expansion: With search my-ns.svc.cluster.local svc.cluster.local cluster.local, a lookup for my-svc tries my-svc.my-ns.svc.cluster.local, then my-svc.svc.cluster.local, then my-svc.cluster.local until one succeeds.
example.com:53 {
hosts {
10.0.0.99 api.example.com
fallthrough
}
forward . /etc/resolv.conf
}
corp.internal:53 {
forward . 10.10.10.10 10.10.10.11
}
rewrite stop {
name regex (.*)\.old\.svc\.cluster\.local {1}.new.svc.cluster.local
}
lab.local:53 {
file /etc/coredns/zones/lab.db
}
k8s/labs/networking/):
dnsdefault.yaml — Pod with dnsPolicy: Default (uses node DNS)dnspolicy.yaml — Pod with hostNetwork: true + dnsPolicy: ClusterFirstWithHostNetdnsconfig.yaml — Pod with dnsPolicy: None + custom dnsConfig (nameservers, searches, ndots)apiVersion: v1
kind: Pod
spec:
dnsPolicy: ClusterFirst
dnsConfig:
nameservers: ["169.254.20.10"]
searches: ["my-ns.svc.cluster.local", "svc.cluster.local"]
options:
- name: ndots
value: "2"
- name: single-request-reopen
value: ""
With dnsPolicy: None, you must supply a full resolver picture yourself (nameservers required):
spec:
dnsPolicy: None
dnsConfig:
nameservers: ["10.96.0.10"]
searches: ["default.svc.cluster.local", "svc.cluster.local", "cluster.local"]
options:
- name: ndots
value: "5"
forward.dnsConfig (must set nameservers).hostNetwork: true, still use cluster DNS like ClusterFirst.Inspect CoreDNS configuration, traffic path, and rollout. Replace debug with a Pod name in your namespace that has shell tools (nslookup, dig, or getent).
Tip: kubectl get svc -n kube-system kube-dns -o wide shows the ClusterIP your nodes and Pods should reference.
kubectl get configmap coredns -n kube-system -o yaml
kubectl logs -n kube-system -l k8s-app=kube-dns
kubectl exec -it debug -- nslookup my-svc.my-ns.svc.cluster.local
kubectl exec -it debug -- cat /etc/resolv.conf
kubectl get endpoints kube-dns -n kube-system
kubectl rollout restart deployment coredns -n kube-system
kube-dns Service exists, Endpoints point to CoreDNS, and Pod can reach ClusterIP:53 (CNI / NetworkPolicy).forward targets (often node resolv.conf). Test upstream from a node. Corporate DNS or firewall may block.loop plugin detects mis-forwarding. Check CoreDNS replica count and node resource pressure.cache TTLs; reduce excessive lookups by fixing ndots or using FQDNs; on some systems options single-request-reopen in dnsConfig helps glibc dual-stack timeouts.k8s/labs/security/deny-all-ingress.yaml and network-policy.html for ready-made patterns.