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.53nslookup 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