coredns - beginner guide

To get started with coredns, we can edit config map in kube-system. 

kubectl edit cm/coredns -n kube-system

And then we are going to add the following 


apiVersion: v1
data:
  Corefile: |2

    hello.test:53 {
      errors
      log
      hosts {
        10.0.0.42 hello.test
      }
      reload
    }

    .: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 {
           disable success cluster.local
           disable denial cluster.local
        }
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2026-01-31T02:17:17Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "1535"
  uid: d4726017-e1b2-47a3-abca-47b0c9196d14

 Let's pay attention to this part 

hello.test:53 {

      errors
      log
      hosts {
        10.0.0.42 hello.test
      }
      reload
    }


We are listening to port 53 for dns query. 

"errors" - logs error 

"log" - log query logs 

And listen for hello.test query and return 10.0.0.42 ip address (as shown below). The dns lookup must match exactly. 

hosts {
        10.0.0.42 hello.test
      }


if you do

dnslookup hello.test  -> returns OK 

dnslookup hello1.test -> return NXDOMAIN 

dnslookup my.hello.test -> return NXDOMAIN 

Here is an example where I do a dnslookup hello.test


You can see it is returning the configure ip address.

The example above is returning a static ip based on a query.  Let's do a more realistic example for dns server where request for abc.example.com will get redirected to a legit service in your kubenetes cluster. 

Let's start with this configuration

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
       
        # Custom rewrite BEFORE kubernetes plugin
        rewrite stop {
          name regex ^(.*)\.example\.com\.$ {1}.default.svc.cluster.local.
          answer name (.*)\.default\.svc\.cluster\.local\. {1}.example.com.
        }
       
        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
    }
kind: ConfigMap
metadata:
  creationTimestamp: '2026-01-31T03:03:54Z'
  name: coredns
  namespace: kube-system
  resourceVersion: '4243'

We have a httpbin service here and it is deploy with a ip of "10.97.48.2". 


After we have configure our config map and do a restart 

kubectl rollout restart deployment/coredns -n kube-system

we will do this 

nslookup httpbin.example.com

As you can see, it returns 10.97.48.2, which matches the service ip for httpbin service. 


To do conditional fowarding 

A scenario where you forward dns query for corp.example.com to 10.0.0.53 custom dns server

.:53 {
    errors
    health
   
    # ===== EXAMPLE 3 START =====
    forward corp.example.com 10.0.0.53 {
      max_concurrent 50
      policy sequential  # Try first server, then fallback
    }
    # ===== EXAMPLE 3 END =====
   
    forward . /etc/resolv.conf  # Default forwarder for everything else
   
    kubernetes cluster.local in-addr.arpa ip6.arpa {
      pods insecure
      fallthrough in-addr.arpa ip6.arpa
    }
    cache 30
    loop
    reload
    loadbalance
}


  • nslookup api.corp.example.com → goes to 10.0.0.53
  • nslookup google.com → goes to public DNS via /etc/resolv.conf


Multi env resolution 

Here we use different dns server to resolve dns query. 

.:53 {
    errors
    health

    # Dev environment
    rewrite name regex ^(.*)\.dev\.example\.com\.$ {1}.dev.svc.cluster.local.
   
    # Prod environment
    rewrite name regex ^(.*)\.prod\.example\.com\.$ {1}.prod.svc.cluster.local.
   
    # Response rewrites (preserve original domain)
    rewrite stop {
      answer name (.*)\.dev\.svc\.cluster\.local\. {1}.dev.example.com.
      answer name (.*)\.prod\.svc\.cluster\.local\. {1}.prod.example.com.
    }

    kubernetes cluster.local in-addr.arpa ip6.arpa {
      pods insecure
      fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}


nslookup nginx.dev.example.com    # → dev namespace IP

nslookup nginx.prod.example.com   # → prod namespace IP


Multi dns server forwarding failover

In this setup, we will try corp.example.com against 2 internal dns server 10.0.0.53 and 10.0.0.54 before trying the public dns servers.


.:53 {
    errors
    health
   
    forward corp.example.com 10.0.0.53 10.0.0.54 {
      policy sequential   # Try 10.0.0.53 → if timeout, try 10.0.0.54
      max_fails 2         # Mark server dead after 2 failures
      health_check 1s     # Check every second
    }
   
    forward . 8.8.8.8 1.1.1.1 {
      policy random       # Load balance between public DNS
    }
   
    cache 30
    loop
    reload
}


Understanding core dns logs 

In a typical log you would see 

[INFO] 10.244.1.5:53321 - 45678 "A IN api.corp.example.com. udp 42 false 512"

Here is a breakdown of what it means 


Field
Meaning
Example Value
A
Query type (record type)
A = IPv4 address
IN
Query class
IN = Internet (standard)
api.corp.example.com.
Query name (FQDN)
Note trailing dot = fully qualified
udp
Transport protocol
udp vs tcp
42
Message size (bytes)
DNS query payload size
false
DNSSEC OK (DO) bit
❌ Client did NOT request DNSSEC
512
EDNS0 buffer size
Max UDP payload client can receive







Comments

Popular posts from this blog

mongosh install properly

gemini cli getting file not defined error

vllm : Failed to infer device type