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.

Want structured learning?

Take the full Dns course →