15.6 Custom DNS Entries and ConfigMap for CoreDNS
Right, so you’ve got your cluster humming along, and nginx.default.svc.cluster.local resolves like a charm. But let’s be honest, you don’t want to type that out, and your applications certainly shouldn’t have to. You want to resolve payments.service or magic.internal or database.prod. This is where we stop letting Kubernetes call all the shots and start teaching its DNS system, CoreDNS, some new tricks.
The magic—and the occasional source of frustration—is that CoreDNS is overwhelmingly configured through a single ConfigMap living in the kube-system namespace. It’s a bit like being given the keys to a sports car but being told you can only adjust the steering by editing a single, massive XML file under the hood. Powerful, but handle with care.
The CoreDNS ConfigMap: Your Steering Wheel
First, let’s see what the stock configuration looks like. Run kubectl -n kube-system get configmap coredns -o yaml. You’ll get something wonderfully familiar and slightly intimidating.
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.: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
cache 30
loop
reload
loadbalance
}
This block of text, specifically the Corefile under data, is the entire universe for your cluster’s DNS server. It’s written in the configuration language of Caddy (which CoreDNS is built upon). Each block, denoted by braces {}, defines a zone (here, the root zone .:53) and the plugins that handle requests for that zone.
The key plugin for Kubernetes magic is kubernetes cluster.local .... This is what automatically creates DNS records for every Service and Pod in your cluster. The fallthrough option is a crucial piece of cleverness: if CoreDNS can’t find a record in the cluster.local zone, it will “fall through” to the next plugin, which is often used to forward the query to your upstream nameservers (e.g., your company’s DNS or a public resolver like 8.8.8.8).
Writing Your Own Custom Domains
Now, the fun part. Let’s say you have an external legacy database that will never run in Kubernetes, living at legacy-db.corporate.com. You want all applications in the cluster to resolve my-database.internal to that address. We do this by adding a new hosts plugin block before the kubernetes block.
Why before? Because CoreDNS executes plugins in the order they are defined. We want it to check our custom mappings first; it’s faster and we avoid the potential weirdness of the kubernetes plugin.
Here’s how you modify the ConfigMap to make it happen:
# coredns-custom-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
ready
# Our custom hosts file mapping
hosts {
192.168.100.123 my-database.internal
10.100.10.5 my-other-service.internal
fallthrough
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
The fallthrough directive inside the hosts block is just as important here as it is in the kubernetes plugin. It says, “If you don’t find a match in my custom list, keep going down the plugin chain.” Without it, queries for my-database.internal would work, but queries for nginx.default.svc.cluster.local would mysteriously fail because the request would never make it to the kubernetes plugin.
To apply this: kubectl apply -f coredns-custom-configmap.yaml. CoreDNS pods will automatically reload their configuration within seconds (thanks to the reload plugin), so no need for a pod restart.
Common Pitfalls and the Art of Debugging
This is where the witty friend gets serious for a moment. This system is powerful but brittle.
Order of Plugins Matters: As mentioned, putting your
hostsblock after thekubernetesblock means it will never be reached for anycluster.localquery. Think about the flow.The Dreaded
loop: If you mess up your forwarding and CoreDNS ends up forwarding requests to itself, it will detect the loop and halt. You’ll see aLoop detectederror in the logs. This is a good safety feature, but it means your DNS is now broken. Test your config changes carefully.Forgetting
fallthrough: This is the most common mistake. If you create a custom zone block (e.g., forinternal.), you must includefallthroughif you want non-matching queries in that zone to be resolved by the rest of your Corefile. Otherwise, they just hit a dead end.Debugging with
dig: The best way to see what’s happening is to exec into a pod and usedig. Install it withapt-get update && apt-get install dnsutilsif it’s not there.kubectl run -it --rm debug --image=busybox:1.35 --restart=Never -- /bin/sh # Then inside the container: / # nslookup my-database.internalThis will tell you exactly which server responded and with what answer. If you get an answer from a server that isn’t the CoreDNS service IP, you know your
fallthroughor forwarding is misconfigured.
Ultimately, wielding this ConfigMap is a core admin skill. It feels a bit janky to be editing a massive string in a YAML file, but it gives you an incredible amount of control. You’re not just configuring DNS; you’re directly shaping the network reality of every pod in your cluster. So make your changes, test them relentlessly, and enjoy the power of making magic.internal actually work.