NodeLocal DNSCache: DNS Performance at Large Scale
DNS Bottleneck in Kubernetes Clusters
Without NodeLocal DNSCache:
Pod → CoreDNS (kube-dns service) → Upstream DNS (Cloud DNS)At 1000 nodes, 100K Pods:
- Each Pod might resolve service names multiple times (app startup, health checks, periodic lookups)
- CoreDNS replicas (typically 2-4) must handle all cluster DNS requests
- Network overhead: Pod → CoreDNS always requires network traversal (even if on same node)
Scale calculation:
- 100K Pods, each 5 DNS requests per minute = 500K requests/minute
- CoreDNS throughput: ~5,000 qps (queries per second) = 300K requests/minute
- Overload: 200K excess requests/minute
Result:
- CoreDNS CPU saturates
- DNS queries timeout
- Pods fail to resolve service names
- App startup delayed or fails
Why DNS Matters at Scale
DNS lookup latency impacts:
- Pod startup: First DNS query to resolve service → critical path
- Health checks: Periodic HTTP health checks to services (DNS resolution per check)
- Observability: Metrics scraping involves DNS resolution of targets
- Outbound connections: Client connects to service name → DNS lookup first
Even 100ms extra per DNS query × 500K queries/minute = 8+ hour of cumulative delay/day.
NodeLocal DNSCache Solution
Architecture
Deploy CoreDNS as DaemonSet on every node, listening on node IP (not via Service).
Pod → localhost:53 (CoreDNS on same node)
↓ (cache hit?)
Return immediately
↓ (cache miss)
Query CoreDNS service (Cloud DNS)Benefits:
- DNS queries never leave node (no network hop)
- Cache hit rate = 80-90% typical (same services queried repeatedly)
- Reduces load on central CoreDNS by 10x
Deployment
Enable via GKE cluster creation:
gcloud container clusters create mycluster \
--enable-dns-cacheOr enable on existing cluster (beta):
gcloud container clusters update mycluster \
--enable-dns-cacheGKE automatically:
- Deploys CoreDNS DaemonSet (node-local-dns) on every node
- Patches kubelet configuration (--cluster-dns=169.254.20.10)
- Configures localhost DNS stub
Caching Mechanism
NodeLocal CoreDNS caches with TTL:
Service SRV record: _http._tcp.svc.cluster.local → 30s TTL (default)
A record (service IP): nginx-svc.default.svc.cluster.local → 30s TTL
External domain: example.com → 300s TTLCache effectiveness:
- Service queries: 90%+ hit rate (same services accessed repeatedly)
- External queries: 70-80% hit rate (depends on domain diversity)
When NodeLocal DNSCache is Mandatory
Scenario 1: High Pod Density (>80 Pods/node)
At 80+ Pods/node, central CoreDNS can't keep up. NodeLocal reduces load by 80%+.
Scenario 2: Frequent Service Name Resolution
Apps doing many DNS lookups (service discovery, health checks, client retries).
Scenario 3: Network Path Bottleneck
Pod and CoreDNS on different node. Network bandwidth constrained → DNS over network costly.
Scenario 4: Latency-Sensitive Apps
Apps where DNS lookup latency contributes to SLO (edge cases, but real).
Scenario 5: CoreDNS Already Saturated
Monitor CoreDNS metrics: if CPU >60%, latency >100ms, enable NodeLocal.
Operational Considerations
CPU Overhead per Node
NodeLocal CoreDNS DaemonSet:
- Base memory: 20-30MB per node
- CPU: 10-50m under normal load
Cluster-wide: 1000 nodes × 40m = 40 CPU cores just for NodeLocal DNS.
Worth it? Yes, because reduces central CoreDNS need from 4 replicas to 1-2 → net saving.
Cache Invalidation
Cache invalidated when:
- TTL expires: Default 30s for service records
- Explicit invalidation: (rare) etcd watch sees change
- Pod restart: Cache persists (no state in Pod)
Risk: If Service endpoint changes, cache might serve stale IP for up to 30s.
For critical services, consider lower TTL:
# In CoreDNS ConfigMap
cache 10 # cache 10 seconds instead of 30Monitoring NodeLocal DNS
# Check if enabled
kubectl -n kube-system get daemonset node-local-dns
# Check metrics (if Prometheus enabled)
kubectl top pod -n kube-system node-local-dns-xxxxMetrics:
coredns_cache_hits_total: cache hitscoredns_cache_misses_total: cache missescoredns_dns_request_duration_seconds_bucket: query latency
Target hit rate >75%.
Real-World Scenario: DNS Saturation During Surge
Case: Cluster autoscale from 100 to 1000 nodes (scaling event). New workload lands.
Timeline (without NodeLocal):
- New 900 nodes come online
- ~9000 new Pods spawn (10 Pods/node initially)
- All Pods initialize, resolve service names → 9000 concurrent DNS queries
- Central CoreDNS (2 replicas) flooded
- DNS timeout → Pod init fails
- Pods restart → more DNS queries → worse
- Cascading failure
With NodeLocal:
- Same scenario
- Each node has local DNS → 9000 queries handled locally (cache misses rare)
- Local CoreDNS serves from cache (80%+ hit rate)
- Central CoreDNS only sees 20% misses = 1800 queries → manageable
- Pod initialization succeeds
Disabling NodeLocal DNSCache (If Needed)
Reasons to disable (rare):
- Custom DNS resolution needed (security reasons, special firewall rules)
- Debugging DNS issues (need central logging)
Disable:
gcloud container clusters update mycluster \
--no-enable-dns-cache