CoreDNS is now the default DNS provider for Kubernetes, and migrating from kube-dns to CoreDNS is a common task. Here’s how to do it without disrupting DNS resolution in your cluster.
Let’s see CoreDNS in action. Imagine a simple pod that needs to resolve a service name.
apiVersion: v1
kind: Pod
metadata:
name: dns-test
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "3600"]
stdin: true
tty: true
When this pod starts, it will have a /etc/resolv.conf file that points to its DNS server. With CoreDNS, this typically looks like:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
The 10.96.0.10 is the ClusterIP of the kube-dns (or coredns if migrated) service. A dig command from within this pod for kubernetes.default.svc.cluster.local would be resolved by CoreDNS.
The core problem CoreDNS solves is providing a flexible, plugin-based DNS service for Kubernetes. Unlike kube-dns, which was a monolithic binary, CoreDNS allows you to customize its behavior by enabling or disabling specific plugins. This means you can tailor DNS resolution to your cluster’s specific needs, from simple service discovery to complex forwarding rules and custom record types.
Internally, CoreDNS operates as a chain of plugins. When a DNS query arrives, it’s processed by each plugin in sequence. Each plugin can either handle the query, modify it, or pass it on to the next plugin. This modular design makes CoreDNS highly extensible. Common plugins include:
kubernetes: Handles DNS resolution for Kubernetes services and pods.forward: Forwards queries to upstream DNS servers.cache: Caches DNS responses to reduce load.prometheus: Exposes DNS metrics for monitoring.health: Provides a health check endpoint.
The primary levers you control are within the Corefile, which is the configuration file for CoreDNS. You can edit this ConfigMap to change which plugins are active, how they are configured, and their order. For example, you can specify upstream DNS servers, configure caching TTLs, or enable specific logging formats.
Here’s how to migrate from kube-dns to CoreDNS with zero downtime. The key is to run CoreDNS in parallel with kube-dns and gradually shift traffic.
-
Deploy CoreDNS alongside kube-dns:
-
Create a new ConfigMap for your CoreDNS configuration. A minimal
Corefilemight look like this:.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } -
Deploy the CoreDNS deployment, service, and RBAC using the standard Kubernetes manifests. Ensure the CoreDNS service’s ClusterIP is different from kube-dns’s ClusterIP. For example, if kube-dns uses
10.96.0.10, CoreDNS might get10.96.0.11.
-
-
Update kubelet configurations:
- This is the most critical step. You need to tell kubelets to use CoreDNS in addition to kube-dns. Edit the kubelet configuration file on each node (e.g.,
/var/lib/kubelet/config.yaml). - Find the
clusterDNSfield and add the ClusterIP of your CoreDNS service to the list. For example, ifclusterDNSwas[10.96.0.10], change it to[10.96.0.10, 10.96.0.11]. - Restart the kubelet service on each node (
systemctl restart kubelet). - Why this works: When kubelets are updated, they regenerate the
/etc/resolv.conffile for new pods. By adding CoreDNS’s IP toclusterDNS, pods will now have both IPs in theirnameserverlist. Thendots:5option inresolv.confmeans that short names (likemy-service) will be tried with all the search domains before a full.lookup. The DNS resolver in the kernel tries nameservers in order. As CoreDNS is generally faster and more efficient, it will likely respond first for cluster-internal names.
- This is the most critical step. You need to tell kubelets to use CoreDNS in addition to kube-dns. Edit the kubelet configuration file on each node (e.g.,
-
Monitor and verify:
- Deploy new pods and check their
/etc/resolv.confto confirm both DNS IPs are present. - Perform DNS lookups from these new pods using
digornslookupto verify both internal and external resolution. - Monitor CoreDNS metrics (if Prometheus is enabled) for query volume and latency.
- Deploy new pods and check their
-
Gradually remove kube-dns:
- Once you are confident that CoreDNS is handling traffic correctly, you can start scaling down kube-dns.
- First, scale down the kube-dns deployment replicas to zero.
- Then, remove the kube-dns ClusterIP from the
clusterDNSlist in the kubelet configuration and restart kubelets again. - Finally, delete the kube-dns deployment, service, and related RBAC resources.
The one thing most people don’t realize about the resolv.conf file generated by kubelet is that it’s not just a static file. The ndots option, combined with the order of nameservers, is what allows for seamless cutovers. When multiple nameservers are listed, the resolver attempts them in order for unqualified names (names without dots). If the first nameserver (CoreDNS in our case) quickly responds with a NXDOMAIN for a name that doesn’t exist internally, the resolver moves on to the next nameserver (kube-dns, if still present). This effectively prioritizes CoreDNS for internal cluster lookups, allowing you to shift traffic incrementally without a hard cutover.
The next challenge will be configuring advanced CoreDNS features like custom DNS records or integrating with external DNS providers.