Kubernetes cluster DNS isn’t just a lookup service; it’s a distributed, self-healing directory of your ephemeral services.
Let’s watch it in action. Imagine you have a frontend deployment and a backend deployment, and the frontend needs to talk to the backend on port 8080.
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: nginx:latest # Replace with your actual frontend image
ports:
- containerPort: 80
env:
- name: BACKEND_SERVICE_HOST
value: "backend.default.svc.cluster.local" # This is the magic
- name: BACKEND_SERVICE_PORT
value: "8080"
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: your-backend-image:v1 # Replace with your actual backend image
ports:
- containerPort: 8080
When the frontend pod starts, its application code can simply try to connect to backend.default.svc.cluster.local:8080. It doesn’t need to know the IP addresses of the backend pods. The Kubernetes cluster DNS, powered by CoreDNS, handles resolving backend.default.svc.cluster.local to one of the healthy backend service IPs.
The magic happens because Kubernetes, by default, injects environment variables into pods. For the frontend pod, Kubernetes will set BACKEND_SERVICE_HOST to backend.default.svc.cluster.local and BACKEND_SERVICE_PORT to 8080 if a service named backend exists in the default namespace. Your application can then use these variables.
Let’s break down how CoreDNS makes this possible. CoreDNS acts as the authoritative DNS server for your cluster’s internal domain (usually .cluster.local). When a pod makes a DNS query for backend.default.svc.cluster.local, the query first goes to the cluster DNS service (which runs CoreDNS pods).
Here’s a simplified look at the CoreDNS configuration:
# coredns-configmap.yaml
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
hosts {
# Static entries if needed
fallthrough
}
cache 30
loop
reload
loadbalance
}
The kubernetes plugin is the key. It intercepts queries for the cluster.local domain. When it sees a query for backend.default.svc.cluster.local, it consults the Kubernetes API server to find services and their associated endpoints (the IPs of the pods backing the service). It then generates an A record for the service name, pointing to the cluster IP of the backend service. If you query for a pod directly (e.g., pod-name.namespace.pod.cluster.local), it can even resolve to the pod’s IP.
The cache plugin helps by storing recent DNS responses, reducing the load on the Kubernetes API server and speeding up lookups. reload allows CoreDNS to pick up changes to its configuration without restarting. loadbalance is used for SRV records, distributing load across multiple instances of a service.
The fallthrough directive is crucial. If CoreDNS can’t find an answer for a query within Kubernetes (e.g., it’s a query for an external domain like google.com), it passes the query to upstream DNS servers configured on the nodes.
You can test this directly from a pod. Exec into a pod and run nslookup:
kubectl exec -it <your-frontend-pod-name> -- nslookup backend.default.svc.cluster.local
You should see output like:
Server: 10.43.0.10
Address: 10.43.0.10#53
Name: backend.default.svc.cluster.local
Address: 10.43.0.123 # This is the ClusterIP of the backend Service
The IP 10.43.0.123 is the virtual ClusterIP assigned to the backend Kubernetes Service. When traffic hits this ClusterIP, kube-proxy (or its IPVS equivalent) intercepts it and, based on the backend Service definition, forwards it to one of the healthy backend pods.
One critical aspect is how CoreDNS handles service discovery for headless services. For a headless service (where spec.clusterIP: None), kubernetes.default.svc.cluster.local will resolve to multiple A records, one for each pod backing that service, allowing direct pod-to-pod communication via DNS. This bypasses the kube-proxy load balancing and goes straight to a pod IP.
The next hurdle is understanding how NetworkPolicies can restrict this DNS-driven communication, effectively creating firewalls at the pod level based on DNS names.