Service Discovery & CoreDNS
- Naming over IPs: Kubernetes uses Service names for communication. Pod IPs are ephemeral and change on restarts -- Service names provide a stable network identity backed by automatic DNS records.
- CoreDNS Automation: The cluster's built-in DNS server (CoreDNS) watches the Kubernetes API and automatically creates DNS records for every Service and pod. No manual DNS configuration is needed.
- Service FQDN: The full address of a service follows the pattern
<service>.<namespace>.svc.cluster.local. Within the same namespace, you can use just the service name (e.g.,auth-service). - Kubelet Integration: The kubelet configures every container's
/etc/resolv.confto point to CoreDNS, with search domains that enable short-name resolution. - ndots Gotcha: The default
ndots:5setting means any name with fewer than 5 dots is appended with search domains before being queried as-is. This can cause up to 10 extra DNS lookups for external domains and is a common source of latency. - Headless Services: Setting
clusterIP: Nonecreates a headless Service that returns individual pod IPs instead of a virtual IP, essential for stateful workloads like databases.
In Kubernetes, you never hardcode IP addresses. Instead, you use the Service Name. But how does a container know that auth-service actually means 10.96.0.15?
1. Internal DNS Trace
Visualize how a request is resolved inside the cluster.
/etc/resolv.conf points to the CoreDNS Service IP. CoreDNS watches the K8s API and automatically maps service names to their ClusterIPs.2. CoreDNS Architecture
Every Kubernetes cluster runs a DNS server called CoreDNS as a Deployment in the kube-system namespace, exposed via a ClusterIP Service (typically at 10.96.0.10).
How CoreDNS Works
- Watch: CoreDNS uses the
kubernetesplugin to watch the Kubernetes API server for Service and Endpoint changes. - Record creation: When a new Service is created, CoreDNS immediately creates the corresponding DNS records -- no delay, no propagation lag (within the cluster).
- Serve: When a pod makes a DNS query, the request goes to CoreDNS, which resolves it from its in-memory database.
- Forward: For queries that do not match any cluster Service (e.g.,
google.com), CoreDNS forwards the request to upstream DNS servers (configured in the CoreDNS ConfigMap, typically the node's DNS servers).
CoreDNS Configuration
CoreDNS is configured via a ConfigMap in kube-system:
kubectl get configmap coredns -n kube-system -o yaml
A typical CoreDNS Corefile looks like:
.:53 {
errors # Log errors
health { # Health check endpoint on :8080/health
lameduck 5s
}
ready # Readiness endpoint on :8181/ready
kubernetes cluster.local in-addr.arpa ip6.arpa { # Serve cluster DNS
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30 # Cache TTL for cluster records
}
prometheus :9153 # Expose Prometheus metrics
forward . /etc/resolv.conf { # Forward external queries upstream
max_concurrent 1000
}
cache 30 # Cache external DNS for 30 seconds
loop # Detect and break DNS loops
reload # Auto-reload on ConfigMap changes
loadbalance # Round-robin A records
}
3. DNS Record Types
CoreDNS creates several types of DNS records for Kubernetes resources.
A/AAAA Records for Services
Every ClusterIP Service gets an A record (IPv4) or AAAA record (IPv6):
# Format: <service>.<namespace>.svc.cluster.local
auth-service.default.svc.cluster.local -> 10.96.0.15
The record resolves to the Service's ClusterIP, which is a virtual IP that kube-proxy routes to healthy backend pods.
SRV Records for Ports
SRV records are created for named ports in a Service. They contain the port number and the target hostname:
# Format: _<port-name>._<protocol>.<service>.<namespace>.svc.cluster.local
_http._tcp.auth-service.default.svc.cluster.local -> SRV 0 100 8080 auth-service.default.svc.cluster.local
SRV records are useful for service discovery in frameworks that support them, allowing clients to discover both the hostname and port dynamically.
Pod DNS Records
Pods also get DNS records, though they are less commonly used directly:
# Format: <pod-ip-dashed>.<namespace>.pod.cluster.local
10-244-0-5.default.pod.cluster.local -> 10.244.0.5
4. Service FQDN and Search Domains
The FQDN Format
Every Service has a Fully Qualified Domain Name:
<service-name>.<namespace>.svc.<cluster-domain>
The default cluster domain is cluster.local, so a Service named auth-service in the default namespace has the FQDN auth-service.default.svc.cluster.local.
How Short Names Work
You do not need to use the full FQDN. When the kubelet starts a container, it injects /etc/resolv.conf:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
The search domains allow short-name resolution:
| What you type | What gets resolved (from default namespace) |
|---|---|
auth-service | auth-service.default.svc.cluster.local |
auth-service.backend | auth-service.backend.svc.cluster.local |
auth-service.backend.svc | auth-service.backend.svc.cluster.local |
auth-service.backend.svc.cluster.local | Direct FQDN lookup (no search) |
Cross-namespace resolution: If a pod in the frontend namespace needs to reach a service in the backend namespace, use auth-service.backend. The search domain appends .svc.cluster.local automatically.
The ndots Problem
The ndots:5 setting means: "If a name has fewer than 5 dots, try appending each search domain before querying the name as-is."
When a pod queries api.example.com (2 dots, fewer than 5):
- Try
api.example.com.default.svc.cluster.local-- NXDOMAIN - Try
api.example.com.svc.cluster.local-- NXDOMAIN - Try
api.example.com.cluster.local-- NXDOMAIN - Try
api.example.com-- SUCCESS
That is 4 DNS queries instead of 1. For applications that make many external DNS lookups, this causes measurable latency and load on CoreDNS.
Mitigations:
- Trailing dot: Use
api.example.com.(note the trailing dot). This tells the resolver the name is already fully qualified and skips search domain expansion. - Lower ndots: Set
ndots:2in the pod's DNS config (see Pod DNS Policies below). This reduces unnecessary lookups but may break short-name resolution for services in other namespaces. - NodeLocal DNS Cache: Deploy NodeLocal DNSCache as a DaemonSet that caches DNS responses on each node, reducing latency and load on CoreDNS.
5. Headless Services
A normal ClusterIP Service provides a single virtual IP. A headless Service (clusterIP: None) skips the virtual IP and returns the individual pod IPs directly in the DNS response.
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: database
spec:
clusterIP: None # This makes it headless
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
DNS Behavior
A DNS lookup for postgres.database.svc.cluster.local returns an A record for each ready pod:
postgres.database.svc.cluster.local -> 10.244.0.5, 10.244.0.6, 10.244.0.7
When used with a StatefulSet, each pod also gets a predictable DNS name:
postgres-0.postgres.database.svc.cluster.local -> 10.244.0.5
postgres-1.postgres.database.svc.cluster.local -> 10.244.0.6
postgres-2.postgres.database.svc.cluster.local -> 10.244.0.7
This is essential for stateful workloads like databases, where clients need to connect to a specific replica (e.g., the primary) or where the application itself needs peer discovery.
6. Pod DNS Policies
You can control how a pod resolves DNS using the dnsPolicy field:
| Policy | Behavior |
|---|---|
| ClusterFirst (default) | Use CoreDNS for cluster names. Forward external queries upstream via CoreDNS. |
| Default | Inherit the DNS config from the node the pod runs on. Does not use CoreDNS. |
| None | Do not auto-configure DNS. You must provide custom config via dnsConfig. |
| ClusterFirstWithHostNet | Same as ClusterFirst, but for pods using hostNetwork: true. |
Custom DNS Configuration
When you need fine-grained control, use dnsPolicy: None with dnsConfig:
apiVersion: v1
kind: Pod
metadata:
name: custom-dns-pod
spec:
dnsPolicy: "None"
dnsConfig:
nameservers:
- 10.96.0.10 # CoreDNS
- 8.8.8.8 # Google DNS as fallback
searches:
- default.svc.cluster.local
- svc.cluster.local
options:
- name: ndots
value: "2" # Reduce from default 5 to cut external lookups
- name: timeout
value: "3"
- name: attempts
value: "2"
containers:
- name: app
image: myapp:v1
More commonly, you keep dnsPolicy: ClusterFirst and use dnsConfig only to override ndots:
spec:
dnsPolicy: ClusterFirst
dnsConfig:
options:
- name: ndots
value: "2"
7. ExternalName Services
An ExternalName Service maps a Kubernetes service name to an external DNS name. It creates a CNAME record instead of an A record.
apiVersion: v1
kind: Service
metadata:
name: external-db
namespace: production
spec:
type: ExternalName
externalName: mydb.us-east-1.rds.amazonaws.com
Now, any pod that resolves external-db.production.svc.cluster.local gets a CNAME pointing to mydb.us-east-1.rds.amazonaws.com. This provides a layer of indirection -- if the external hostname changes, you update the Service, not every application configuration.
Limitations:
- ExternalName Services do not proxy traffic. There is no ClusterIP and no kube-proxy rules.
- HTTPS connections may fail if the certificate does not match the service name. The TLS handshake uses the original hostname, not the CNAME target.
- ExternalName does not support ports. The client must know the correct port.
8. DNS Debugging
When DNS resolution fails, here is a systematic debugging approach.
Step 1: Start a Debug Pod
kubectl run dns-debug --image=busybox:1.36 --restart=Never --rm -it -- sh
Or use a more complete toolkit:
kubectl run dns-debug --image=nicolaka/netshoot --restart=Never --rm -it -- bash
Step 2: Test Internal Resolution
# Inside the debug pod
nslookup auth-service.default.svc.cluster.local
# Expected: returns the ClusterIP
nslookup kubernetes.default.svc.cluster.local
# Expected: returns the API server ClusterIP (always exists)
Step 3: Test External Resolution
nslookup google.com
# If this fails, CoreDNS cannot reach upstream DNS
Step 4: Check CoreDNS Health
# From your workstation
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
Step 5: Verify Endpoints
# Ensure the Service has endpoints (backing pods)
kubectl get endpoints auth-service
# If empty, the Service selector does not match any running pods
Common Pitfalls
1. ndots:5 causing excessive DNS queries. Every external domain lookup generates up to 4 extra queries. For applications making hundreds of external calls per second, this adds significant latency. Lower ndots or use FQDNs with trailing dots.
2. DNS caching surprises. CoreDNS caches records for 30 seconds by default. If a Service is deleted and recreated, clients may resolve stale IPs for up to 30 seconds. Some language runtimes (Java, notably) cache DNS at the application level indefinitely -- configure your runtime's DNS TTL.
3. Service exists but DNS returns NXDOMAIN. Check that the Service is in the correct namespace and that you are using the right namespace in the FQDN. Also verify CoreDNS pods are running.
4. Headless Service returning stale pod IPs. If pods are terminated but CoreDNS has not yet updated, DNS may return IPs of terminated pods. Ensure readiness probes are configured so unhealthy pods are removed from endpoints.
5. ExternalName and HTTPS. Browsers and HTTP clients validate TLS certificates against the requested hostname. If your code connects to external-db but the certificate is for mydb.us-east-1.rds.amazonaws.com, the TLS handshake may fail depending on how your client handles CNAMEs.
Best Practices
-
Use short service names for same-namespace communication. Just
auth-serviceis sufficient and most readable. Useauth-service.other-namespacefor cross-namespace calls. -
Always use the trailing dot for external FQDNs in configuration files (e.g.,
api.external.com.). This prevents unnecessary search domain expansion. -
Monitor CoreDNS performance. CoreDNS exposes Prometheus metrics on port 9153. Track
coredns_dns_requests_total,coredns_dns_responses_total, andcoredns_dns_request_duration_secondsto detect overload. -
Deploy NodeLocal DNSCache in production clusters. It runs a DNS cache on every node, reducing latency and eliminating CoreDNS as a single point of failure for DNS resolution.
-
Configure readiness probes on all pods behind Services. DNS returns IPs of pods in the Service's endpoints list. Without readiness probes, pods receive traffic (and DNS records) before they are ready to serve.
-
Lower ndots for workloads that primarily call external services. Set
ndots: 2in the pod'sdnsConfigto reduce external lookup latency while still supporting internal short names.
What's Next?
- Ingress (Routing) -- Once DNS resolves your internal Services, learn how Ingress routes external HTTP traffic to those Services.
- Services -- Review the Service resource types (ClusterIP, NodePort, LoadBalancer) that DNS records point to.
- StatefulSets -- Understand how headless Services provide stable network identities for stateful pods.
- Observability -- Monitor CoreDNS metrics and detect DNS resolution failures in your monitoring stack.
- Probes (Health Checks) -- Readiness probes determine which pod IPs appear in DNS for headless Services.