CoreDNS can be configured to act as a recursive DNS resolver, forwarding queries for specific domains to different DNS servers, effectively creating custom name resolution policies.
Let’s see this in action. Imagine you have a private network where internal.example.com resolves only through your internal DNS server at 10.0.0.53. You want your CoreDNS instance, running on 10.0.0.10, to handle all other queries but to send internal.example.com traffic to that internal server.
Here’s how you’d configure it in your CoreDNS Corefile:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . 8.8.8.8 8.8.4.4 {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
internal.example.com:53 {
errors
forward . 10.0.0.53
}
In this Corefile:
- The first block (
.:53) is the primary CoreDNS configuration.errorsandhealthare standard plugins for logging and monitoring.readysignals readiness to the orchestrator (like Kubernetes).kuberneteshandles DNS resolution for Kubernetes internal services.fallthroughensures that if a query isn’t found within the Kubernetes cluster, it’s passed to the next plugin.prometheusexposes metrics on port9153.forward . 8.8.8.8 8.8.4.4is the key for general internet resolution. It tells CoreDNS to forward any query it can’t resolve locally (using thekubernetesplugin, for instance) to Google’s public DNS servers (8.8.8.8and8.8.4.4). Themax_concurrent 1000limits how many queries can be in flight simultaneously to these upstream servers.cache 30caches DNS responses for 30 seconds.loopdetects and prevents DNS loops.reloadallows theCorefileto be reloaded without restarting CoreDNS.loadbalancedistributes queries across multiple upstream servers if multiple are listed in aforwarddirective.
- The second block (
internal.example.com:53) is the stub domain configuration.- This block specifically targets queries for
internal.example.com. forward . 10.0.0.53directs all queries within theinternal.example.comzone exclusively to the DNS server at10.0.0.53.
- This block specifically targets queries for
When a client on your network queries myhost.internal.example.com, CoreDNS first checks if there’s a specific zone configuration for internal.example.com. It finds the second block and forwards the query to 10.0.0.53. If the query was for google.com, it wouldn’t match the internal.example.com zone, so CoreDNS would fall through to the first block, hit the forward . 8.8.8.8 8.8.4.4 directive, and send the query to Google’s DNS servers.
This setup allows granular control over DNS resolution. You can define multiple stub domains, each pointing to different internal DNS servers, for various private zones.
The stub plugin is a more modern and often preferred way to achieve this for simpler cases, as it consolidates stub domain configurations within the main block. For example, the internal.example.com block above could be replaced by adding stub to the first block:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . 8.8.8.8 8.8.4.4 {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
stub {
# Forward queries for internal.example.com to 10.0.0.53
10.0.0.53:53
}
}
However, the separate zone configuration approach offers more flexibility when you need to define distinct Corefile directives (like different cache TTLs or specific logging for a particular domain) that go beyond simple forwarding.
Understanding how CoreDNS matches query names to zone blocks in the Corefile is crucial. It processes the blocks from most specific to least specific. A block with a specific domain name like internal.example.com:53 will always be evaluated before a generic block like .:53. This specificity is how the stub domain configuration reliably intercepts and redirects queries.
The next challenge is often handling DNSSEC validation for your stub domains.