Kubernetes DNS-problemen oplossen: CoreDNS-fouten en resolutieproblemen

Als pods geen DNS-namen kunnen resolven, werkt er niets meer. Service-naar-service-verkeer faalt, externe API-aanroepen lopen vast, en de applicatielogs raken gevuld met connectiefouten. De oorzaak zit ergens in de DNS-keten: de /etc/resolv.conf van de pod, de kube-dns Service, CoreDNS zelf, of de upstream-resolver. Dit artikel loopt elke laag door met concrete diagnostische commando's en oplossingen.

Hoe Kubernetes DNS werkt

Voor je gaat debuggen, helpt het om de keten te begrijpen die een DNS-query doorloopt. Wanneer een container getaddrinfo("my-service.default.svc.cluster.local") aanroept, gebeurt het volgende:

  1. De libc-resolver van de container leest /etc/resolv.conf, die door de kubelet is geplaatst bij het aanmaken van de pod.
  2. De resolver stuurt een UDP-query naar de nameserver die daar staat: het ClusterIP van de kube-dns Service in kube-system (vaak 10.96.0.10 op kubeadm-clusters, maar dit verschilt per distributie).
  3. kube-proxy iptables/IPVS-regels routeren dat verkeer naar een van de CoreDNS-pods.
  4. CoreDNS controleert de query tegen het clusterdomein (cluster.local). Cluster-interne namen worden beantwoord vanuit de in-memory cache van Kubernetes API-objecten. Al het andere wordt doorgestuurd naar upstream-resolvers via de forward-plugin.
  5. Het antwoord gaat terug naar de pod.

Een typische /etc/resolv.conf in een pod ziet er zo uit:

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

De search-lijst en ndots:5 worden ingesteld door de kubelet via --cluster-dns en --cluster-domain (of hun KubeletConfiguration-equivalenten). De Service heet nog steeds kube-dns voor backward compatibility, ook al is CoreDNS sinds Kubernetes 1.13 de standaard DNS-server.

DNS testen vanuit een pod

Begin elk DNS-onderzoek vanuit het cluster, niet vanaf je laptop. Start een debug-pod met DNS-tools:

kubectl run dns-test --rm -it --restart=Never \
  --image=nicolaka/netshoot -- bash

Voor de officiele Kubernetes-testimage:

kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
kubectl exec -it dnsutils -- sh

Loop dan deze stappen door.

Stap 1: controleer welke DNS-server de pod gebruikt.

cat /etc/resolv.conf

De verwachte output toont het kube-dns ClusterIP als nameserver. Als je 127.0.0.53 of een node-IP ziet, heeft de pod een onverwachte dnsPolicy (zie het dnsPolicy-gedeelte hieronder).

Stap 2: test interne naamresolutie.

nslookup kubernetes.default

Verwacht:

Server:    10.96.0.10
Address:   10.96.0.10#53

Name:      kubernetes.default.svc.cluster.local
Address:   10.96.0.1

Stap 3: test externe naamresolutie.

nslookup google.com

Interpreteer de resultaten:

Intern werkt Extern werkt Waarschijnlijke oorzaak
Nee Nee CoreDNS is down of onbereikbaar (network policy, pod-crash, verkeerde dnsPolicy)
Nee Ja CoreDNS kubernetes-plugin probleem of API-server connectiviteit
Ja Nee Upstream-resolverfout (zie upstream-resolverfouten)
Ja Ja DNS werkt; het probleem zit ergens anders

Stap 4: controleer de CoreDNS-pods.

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get endpointslice -l kubernetes.io/service-name=kube-dns -n kube-system

Een lege endpoints-lijst betekent dat geen enkele CoreDNS-pod door z'n readiness probe komt. Bekijk het gedeelte CoreDNS draait niet.

Stap 5: lees de CoreDNS-logs.

kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100

Voor diepere query-level zichtbaarheid kun je tijdelijk log toevoegen aan het .:53-blok in de Corefile en CoreDNS herstarten. Verwijder het weer als je klaar bent; bij hoge QPS genereer je enorme hoeveelheden logs.

CoreDNS-pod draait niet

Controleer de status van de CoreDNS-pods:

kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide

Elke niet-Running status wijst naar een andere oorzaak:

Pod-status Waarschijnlijke oorzaak
Pending Geen node heeft capaciteit, of tolerations matchen niet met taints
CrashLoopBackOff Loop-detectie, CNI niet geinstalleerd, SELinux-conflict, of applicatiefout
OOMKilled (exit 137) Memory-limiet te laag voor de clustergrootte
ContainerCreating Image-pull-fout of CNI nog niet gereed
Running maar 0/1 Ready Readiness probe faalt op poort 8181

DNS forwarding loop (CrashLoopBackOff)

Dit is de meest voorkomende oorzaak van CoreDNS-crashes op nodes met systemd-resolved. De loop-plugin detecteert wanneer een doorgestuurde query terugkomt bij CoreDNS zelf. Als de /etc/resolv.conf van de node naar 127.0.0.53 (de stub-listener van systemd-resolved) wijst, stuurt CoreDNS daar naartoe, systemd-resolved stuurt het terug via het kube-dns ClusterIP, en CoreDNS detecteert de loop en stopt.

De logregel is onmiskenbaar:

[FATAL] plugin/loop: Loop (127.0.0.1:37293 -> :53) detected for zone "."

De loop-detectie is beschermend gedrag. CoreDNS stopt zichzelf om ongecontroleerde geheugengroei te voorkomen. Schakel de loop-plugin niet uit; los het onderliggende resolutieprobleem op.

Fix optie A: wijs de kubelet naar de echte resolv.conf (langs de systemd-resolved stub):

# KubeletConfiguration (of --resolv-conf flag)
resolvConf: /run/systemd/resolve/resolv.conf

Fix optie B: hardcode upstream-IP's in de Corefile in plaats van overerving van de node:

forward . 8.8.8.8 8.8.4.4

Bewerk de ConfigMap:

kubectl -n kube-system edit configmap coredns

En herstart CoreDNS:

kubectl rollout restart deployment coredns -n kube-system

OOMKilled (exit code 137)

CoreDNS-geheugenverbruik schaalt met het aantal Kubernetes-objecten in de cache, niet met het queryverkeer. De standaard 170Mi-limiet in veel distributies is onvoldoende voor grote clusters. De CoreDNS-scalingdocumentatie geeft deze formule:

MB benodigd = (Pods + Services) / 1000 + 54

Met de autopath-plugin ingeschakeld (ruilt CPU voor minder client-side search queries):

MB benodigd = (Pods + Services) / 250 + 56

Een cluster met 5.000 pods en 500 services heeft ongeveer 60 MB nodig zonder autopath, maar clusters met 10.000+ objecten moeten 256 MB of meer rekenen. Zie OOMKilled: Kubernetes out of memory-fouten uitgelegd voor een diepere analyse van exit code 137.

Pas de Deployment aan:

kubectl -n kube-system patch deployment coredns \
  --patch '{"spec":{"template":{"spec":{"containers":[{"name":"coredns","resources":{"limits":{"memory":"512Mi"},"requests":{"memory":"128Mi","cpu":"100m"}}}]}}}}'

CNI niet geinstalleerd

Direct na kubeadm init blijven CoreDNS-pods in Pending of ContainerCreating staan totdat je een CNI-plugin installeert (Calico, Flannel, Cilium, etc.). CoreDNS heeft pod-networking nodig om te werken. Dit is verwacht gedrag, geen bug.

Readiness probe faalt (Running maar niet Ready)

CoreDNS biedt een readiness-endpoint aan op http://localhost:8181/ready. Een 0/1 READY-status betekent meestal dat de kubernetes-plugin de API-server niet kan bereiken. Controleer de gezondheid van de API-server en de RBAC-permissies voor het CoreDNS ServiceAccount.

Schalen voor hoge beschikbaarheid

De CoreDNS-autoscaler gebruikt deze standaardformule voor het aantal replica's:

replicas = max(ceil(cores / 256), ceil(nodes / 16))

Voor een cluster met 50 nodes komt dat neer op minimaal 4 replica's. Spreid ze over nodes met pod anti-affinity, zodat een uitval van een enkele node niet alle DNS platlegt. Wanneer een node in NotReady-status komt, worden CoreDNS-pods op die node standaard pas na 5 minuten verwijderd. In die tussentijd kunnen sommige DNS-queries nog naar de onbereikbare pod gerouteerd worden.

De ndots:5 query-explosie

De ndots-optie in /etc/resolv.conf bepaalt een drempel: als een opgevraagde hostname minder dan ndots punten bevat, probeert de resolver eerst alle search-domeinen voordat de naam als volledig gekwalificeerd wordt behandeld.

Kubernetes stelt ndots in op 5 om een specifieke reden. SRV-records zien er uit als _http._tcp.my-service.default.svc (vier punten). Met ndots:4 zou de resolver dat als absoluut behandelen en de search-domeinuitbreiding overslaan, waardoor SRV-lookups breken.

Het neveneffect: een externe hostname als api.github.com heeft maar 2 punten. Met ndots:5 genereert de resolver tot 5 queries voordat het lukt:

  1. api.github.com.default.svc.cluster.local (NXDOMAIN)
  2. api.github.com.svc.cluster.local (NXDOMAIN)
  3. api.github.com.cluster.local (NXDOMAIN)
  4. api.github.com.<cloud-zone>.internal (NXDOMAIN, bij cloudproviders)
  5. api.github.com. (succes)

Een applicatie die 100 externe API-calls per seconde doet, produceert meer dan 400 verspilde DNS-queries per seconde. Dat voegt 1-5 ms per NXDOMAIN round trip toe en kan CoreDNS onder druk zetten.

Fix: trailing dots voor statische endpoints

Voeg in applicatieconfiguraties een punt toe aan het einde om directe FQDN-resolutie af te dwingen:

api_endpoint: "api.github.com."   # trailing dot = absoluut, slaat search over

Fix: ndots verlagen per pod

Overschrijf ndots in de pod-spec via dnsConfig:

spec:
  dnsPolicy: ClusterFirst
  dnsConfig:
    options:
      - name: ndots
        value: "2"

Met ndots:2 wordt elke hostname met 2+ punten (zoals api.github.com) meteen als absoluut behandeld. Interne korte namen (my-service) gaan nog steeds door de search-lijst. Let op: het verlagen van ndots onder 5 kan SRV-record-lookups breken die afhankelijk zijn van search-domeinuitbreiding. Test met je eigen servicenamen voordat je dit clusterbreed uitrolt.

De vier dnsPolicy-waarden

Het spec.dnsPolicy-veld bepaalt wat er in de /etc/resolv.conf van een pod terechtkomt.

dnsPolicy Gebruikt CoreDNS Ziet clusterservices Gebruikt node-DNS
ClusterFirst (de echte standaard) Ja Ja Nee
Default (niet de standaard) Nee Nee Ja
None Alleen indien geconfigureerd Alleen indien geconfigureerd Alleen indien geconfigureerd
ClusterFirstWithHostNet Ja Ja Nee

ClusterFirst is de impliciete standaard als dnsPolicy niet is opgegeven. De pod gebruikt het kube-dns ClusterIP. Dit is juist voor 99% van de workloads.

Default erft de /etc/resolv.conf van de node. De naamgeving is oprecht verwarrend: "Default" is niet de standaard. Een pod met dit beleid kan my-service.default.svc.cluster.local niet resolven, omdat die nooit met CoreDNS praat.

None biedt helemaal geen DNS-configuratie. Je moet spec.dnsConfig meegeven met minimaal een nameserver. Een veelgemaakte fout: dnsPolicy: None instellen maar dnsConfig vergeten, waardoor de pod een lege /etc/resolv.conf heeft.

ClusterFirstWithHostNet bestaat voor pods met hostNetwork: true. Zonder dit veld degradeert het ClusterFirst-beleid van een host-network pod stilletjes naar Default-gedrag. DaemonSets voor node-level monitoring (Prometheus node-exporter, Datadog agent, CNI-componenten) die cluster-DNS nodig hebben, moeten dit expliciet instellen:

spec:
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

Upstream-resolverfouten

Als interne DNS werkt maar externe lookups falen, zit het probleem in de CoreDNS forward-plugin of de upstream-servers waarnaar die verwijst.

De standaard Corefile-regel:

forward . /etc/resolv.conf

Dit erft nameservers van de node. Veelvoorkomende faalscenario's:

  • De /etc/resolv.conf van de node wijst naar een onbereikbaar IP (verouderde DHCP, uit bedrijf genomen resolver)
  • De upstream is tijdelijk onbeschikbaar (throttling bij cloudprovider-resolver, netwerkstoring)
  • Firewall- of security-group-regels blokkeren uitgaand UDP/TCP-verkeer op poort 53 vanaf de node
  • Split-horizon DNS die SERVFAIL teruggeeft voor bepaalde domeinen

Diagnose

Achterhaal de upstream-IP's vanuit het perspectief van de CoreDNS-pod:

kubectl get configmap coredns -n kube-system -o yaml | grep forward

Test de upstream rechtstreeks:

# vanuit een debug-pod
dig @<upstream-ip> google.com

Bekijk de CoreDNS-logs op upstream-fouten:

kubectl logs -n kube-system -l k8s-app=kube-dns | grep -i "timeout\|servfail\|refused"

Cloudspecifieke limieten

AWS EKS: de VPC-resolver op 169.254.169.253 handhaaft een limiet van 1.024 packets per seconde per ENI. Hoge DNS-volumes (versterkt door ndots:5) raken deze limiet, waardoor SERVFAIL optreedt. Oplossing: deploy NodeLocal DNSCache of verlaag ndots.

Azure AKS: de DNS-server op 168.63.129.16 wordt onbereikbaar als UDR- of firewallregels deze blokkeren. Controleer of UDP/TCP poort 53 niet gefilterd is.

GKE: de metadata-server op 169.254.169.254 fungeert als upstream-resolver. Network policies mogen de toegang tot dit bereik niet blokkeren.

Upstreams hardcoden voor stabiliteit

Vervang de overgeerfde resolvers door expliciete IP's in de Corefile:

forward . 8.8.8.8 8.8.4.4 {
    policy sequential
    health_check 5s
}

Het sequential-beleid probeert upstreams op volgorde, wat uitmaakt wanneer je een primaire bedrijfsresolver combineert met een publieke fallback. Het standaard random-beleid verdeelt verkeer gelijkmatig, en ongeveer 50% van de queries faalt als maar een van de upstreams een bepaald domein kan resolven.

Intermitterende 5-seconden DNS-timeouts (conntrack-race)

Als DNS-resolutie af en toe precies 5 seconden duurt (de /etc/resolv.conf timeout), heb je waarschijnlijk te maken met de Linux conntrack UDP-racecondition.

Oorzaak

glibc stuurt A- en AAAA-queries tegelijk vanaf dezelfde UDP-bronpoort. Beide pakketten raken dezelfde iptables NAT-regel op hetzelfde moment. Twee conntrack-entries racen om dezelfde 5-tuple. Eentje wint; het andere pakket wordt stilletjes gedropt. De applicatie wacht de volledige 5-seconden timeout af voordat die het opnieuw probeert. Dit is gedocumenteerd in meerdere productie-uitvallen.

Symptomen:

  • Willekeurige 5-seconden DNS-vertragingen, niet op commando te reproduceren
  • Erger bij hoge pod-dichtheid of querytempo
  • Node dmesg kan nf_conntrack: table full tonen bij extreme belasting

Fix: single-request-reopen

Voor containers met glibc (Debian, Ubuntu, RHEL-gebaseerde images), voeg dit toe aan de pod-spec:

spec:
  dnsConfig:
    options:
      - name: single-request-reopen

Dit dwingt glibc om A- en AAAA-queries achter elkaar te sturen op aparte sockets, waardoor de bronpoort-botsing verdwijnt. Let op: musl libc (Alpine Linux) ondersteunt deze optie niet. Alpine-gebruikers hebben NodeLocal DNSCache nodig.

Fix: NodeLocal DNSCache

NodeLocal DNSCache is de productiewaardige oplossing. Het draait een node-local-dns DaemonSet op elke node, luisterend op een link-local IP (169.254.20.10 standaard). Pods sturen queries naar deze lokale cache in plaats van via kube-proxy iptables-regels. Omdat de cache lokaal is, wordt DNAT volledig overgeslagen, en verdwijnt de conntrack-racecondition.

Extra voordelen:

  • Lagere latency (lokale cache hit vs. round-trip via kube-proxy)
  • Minder belasting op CoreDNS
  • Per-node DNS-metrics
  • Weerbaarheid tegen CoreDNS-pod-uitval op andere nodes

NodeLocal DNSCache is GA sinds Kubernetes 1.18 en is de aanbevolen aanpak voor clusters met meer dan 100 nodes of hoge DNS-querytempo's.

Network policies die DNS blokkeren

Als een namespace een default-deny NetworkPolicy heeft, wordt al het uitgaande verkeer geblokkeerd, inclusief DNS-queries naar CoreDNS in kube-system.

Het symptoom: DNS faalt direct en volledig (niet intermitterend). Elke pod in de namespace is geraakt.

Controleer op network policies:

kubectl get networkpolicy -n <namespace>

Los het op door een expliciete egress-regel toe te voegen die DNS toestaat:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: my-app
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Zowel UDP als TCP poort 53 moeten worden toegestaan. TCP wordt gebruikt voor antwoorden groter dan 512 bytes, wat gebruikelijk is bij DNSSEC of records met veel entries.

Wanneer escaleren

Als je de diagnostische stappen hierboven hebt doorlopen en DNS nog steeds faalt, verzamel dan deze informatie voordat je hulp inschakelt:

  • Output van kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
  • CoreDNS-logs: kubectl logs -n kube-system -l k8s-app=kube-dns --tail=200
  • De Corefile: kubectl get configmap coredns -n kube-system -o yaml
  • Output van cat /etc/resolv.conf vanuit een getroffen pod
  • Resultaten van nslookup kubernetes.default en nslookup google.com vanuit een debug-pod
  • Network policies in de getroffen namespace: kubectl get networkpolicy -n <namespace> -o yaml
  • DNS-configuratie op node-niveau: cat /etc/resolv.conf op de node die de CoreDNS-pod host
  • Kubernetes-versie (kubectl version) en CoreDNS-versie (kubectl -n kube-system describe deployment coredns | grep Image)
  • Of de fout consistent of intermitterend is, en als het intermitterend is, of het precies 5 seconden duurt

DNS-problemen voorkomen

  • Draai minimaal 3 CoreDNS-replica's met pod anti-affinity over nodes heen. De autoscaler-formule max(ceil(cores/256), ceil(nodes/16)) is een redelijk startpunt.
  • Stem memory-limieten af op je cluster. Gebruik (Pods + Services) / 1000 + 54 MB als basislijn en monitor het werkelijke gebruik via de Prometheus-metrics die CoreDNS aanbiedt op poort 9153.
  • Deploy NodeLocal DNSCache op clusters met meer dan 100 nodes of hoge DNS-querytempo's. Het lost conntrack-races op, verlaagt de CoreDNS-belasting en verbetert latency.
  • Verlaag ndots voor workloads die veel externe API's aanroepen. ndots:2 via dnsConfig elimineert de query-explosie voor de meeste externe hostnames.
  • Neem een DNS-egressregel op in elke default-deny NetworkPolicy. Maak het onderdeel van je namespace-provisioningtemplate.
  • Monitor CoreDNS. De coredns_dns_requests_total, coredns_dns_responses_total en coredns_forward_responses_total metrics (beschikbaar op poort 9153) brengen problemen aan het licht voordat gebruikers ze melden. Zie Prometheus-monitoring op Kubernetes voor hoe je deze verzamelt.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.