CoreDNS is your new DNS resolver, and it’s probably already running somewhere in your cluster.

Here’s a quick look at CoreDNS in action, resolving a DNS query for kubernetes.default.svc.cluster.local.

[root@master ~]# kubectl exec -it busybox -- nslookup kubernetes.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10

Name:      kubernetes.default.svc.cluster.local
Address 1: 10.96.0.1

[root@master ~]#

In this example, kubectl exec -it busybox -- drops you into a busybox pod, and nslookup queries the DNS server running at 10.96.0.10 (which is your CoreDNS service IP in this Kubernetes cluster). The response 10.96.0.1 is the ClusterIP for the kubernetes service, which is how pods discover the Kubernetes API server.

What CoreDNS Solves

Traditionally, DNS resolution has been a static, text-file-driven process. Managing DNS records for dynamic environments, like cloud-native applications where services spin up and down constantly, becomes a significant operational burden. CoreDNS tackles this by being a highly configurable, extensible DNS server that can dynamically serve records based on the environment it’s running in, most notably Kubernetes.

In Kubernetes, CoreDNS replaces kube-dns as the default DNS provider. It’s deployed as a Deployment and exposed as a Service, typically with a ClusterIP like 10.96.0.10. Pods are configured to use this IP for their DNS resolution.

How CoreDNS Works Internally

CoreDNS is built around a plugin architecture. It reads a configuration file, typically named Corefile, which defines zones and the plugins to be used for those zones. When a DNS query arrives, CoreDNS processes it through a chain of plugins. Each plugin can inspect, modify, or respond to the query.

Let’s break down a typical Corefile for a Kubernetes cluster:

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       upstream
       fallthrough in-addr.arpa ip6.arpa
    }
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}
  • .:53: This defines the default zone (.) and the port CoreDNS listens on (53).
  • errors: Logs errors encountered during DNS resolution.
  • health: Exposes a health check endpoint. lameduck 5s means it will consider itself healthy after 5 seconds of being started.
  • ready: Exposes a readiness probe endpoint.
  • kubernetes cluster.local in-addr.arpa ip6.arpa: This is the core plugin for Kubernetes. It’s responsible for serving DNS records for services and pods within the cluster.local domain.
    • pods insecure: Allows resolution of pod IPs without requiring DNSSEC.
    • upstream: If a record isn’t found within the Kubernetes cluster, it will try to resolve it via upstream DNS servers.
    • fallthrough in-addr.arpa ip6.arpa: For reverse DNS lookups (e.g., IP to name), if Kubernetes doesn’t have an answer, it will pass the query to the next plugin.
  • prometheus :9153: Exposes Prometheus metrics on port 9153.
  • forward . /etc/resolv.conf { max_concurrent 1000 }: For any queries not handled by the kubernetes plugin (i.e., external domains), this plugin forwards them to the DNS servers listed in /etc/resolv.conf (which are typically your cluster’s upstream DNS servers). max_concurrent 1000 limits the number of concurrent forwards.
  • cache 30: Caches DNS responses for 30 seconds. This significantly reduces load on upstream resolvers and speeds up subsequent queries.
  • loop: Detects and prevents DNS forwarding loops.
  • reload: Watches the Corefile for changes and reloads them automatically without restarting CoreDNS.
  • loadbalance: Distributes queries across multiple upstream servers if multiple are configured in the forward plugin.

The Levers You Control

The primary way to customize CoreDNS behavior is by modifying the Corefile. You can edit this file directly within the Kubernetes ConfigMap that stores it:

kubectl edit configmap coredns -n kube-system

You can adjust the ttl (Time To Live) values for caching, change upstream forwarders, add custom DNS zones, or integrate other plugins. For example, to change the cache duration to 60 seconds, you’d modify the cache plugin line:

cache 60

To add a custom zone for mydomain.local and have it handled by a specific plugin (e.g., serving static records), you would add a new block to your Corefile:

mydomain.local:53 {
    file /etc/coredns/mydomain.local.db
    loop
    reload
}

And then create the mydomain.local.db file within the CoreDNS pod’s filesystem, or mount it as a volume.

The Surprising Power of fallthrough

One of the most powerful, yet often overlooked, aspects of CoreDNS configuration is the fallthrough directive. When used within a specific zone block (like kubernetes), it tells CoreDNS to try resolving queries for that zone using the specified plugins. If none of those plugins can answer the query, fallthrough then passes the query to the next block in the Corefile. This is crucial for scenarios where you might have a custom DNS server for a specific domain, but still want CoreDNS to handle all other requests, including standard Kubernetes internal and external lookups. It allows for a layered approach to DNS resolution, where specific needs are met first, and general needs are handled by fallback mechanisms.

The next concept you’ll likely encounter is integrating external DNS providers or setting up more complex zone configurations using custom plugins.

Want structured learning?

Take the full Coredns course →