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

vllm : Failed to infer device type

android studio kotlin source is null error

gemini cli getting file not defined error