CoreDNS in Kubernetes: architectuur en configuratie

CoreDNS is de DNS-server achter elke service-naar-service-aanroep in een Kubernetes-cluster. Het resolvet cluster-interne namen vanuit een in-memory API-watchcache en forwardt al het andere naar upstream-resolvers. Dit artikel legt uit hoe de Corefile de configuratie aanstuurt, hoe plugins worden uitgevoerd, welke DNS-records Kubernetes-services opleveren, en hoe je CoreDNS tunet voor performance op schaal.

Wat CoreDNS doet in een Kubernetes-cluster

CoreDNS verving kube-dns als standaard cluster-DNS-server vanaf Kubernetes 1.11. Het draait als Deployment in kube-system, meestal met 2 replica's op afzonderlijke nodes voor beschikbaarheid. Een Service met de naam kube-dns (bewaard voor backward compatibility) stelt CoreDNS beschikbaar achter een stabiel ClusterIP, doorgaans 10.96.0.10 afhankelijk van je service-CIDR.

De kubelet vult bij het starten van elke pod de /etc/resolv.conf:

nameserver 10.96.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Alle DNS-queries vanuit pods lopen via CoreDNS. De verantwoordelijkheden: Kubernetes-servicenamen resolven naar cluster-IP's, reverse-DNS serveren voor clusterresources, en niet-clusterqueries forwarden naar upstream-resolvers.

CoreDNS blijft gesynchroniseerd met het cluster door de API-server te watchen op Service-, EndpointSlice-, Namespace- en (optioneel) Pod-objecten. Wanneer een nieuwe Service wordt aangemaakt, ziet CoreDNS die binnen enkele seconden via deze watches en begint meteen DNS-records te serveren, zonder herstart.

Hoe een DNS-query verloopt

Pod (/etc/resolv.conf → nameserver 10.96.0.10)
  └─ iptables DNAT (kube-proxy)
       └─ CoreDNS-pod
            ├── cluster.local? → kubernetes-plugin (API-watchcache)
            └── extern?        → forward-plugin → upstream-resolver

Het ClusterIP van de kube-dns Service wordt door kube-proxy-regels geDNATed naar een van de CoreDNS-pod-IP's. CoreDNS checkt de query tegen zijn zoneconfiguratie. Cluster-interne namen worden beantwoord vanuit de in-memory cache die de kubernetes-plugin opbouwt. Al het andere gaat door naar de forward-plugin.

Opbouw van de Corefile

De Corefile is het configuratiebestand van CoreDNS. In Kubernetes leeft het in de coredns ConfigMap in kube-system:

kubectl get configmap coredns -n kube-system -o yaml

Server blocks en zones

De Corefile werkt met server blocks. Elk blok declareert een of meer DNS-zones, een optionele poort, en plugin-directives tussen accolades:

[SCHEME://]ZONE [:PORT] {
    PLUGIN [ARGUMENTEN]
}

De standaard Kubernetes 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
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

Het .:53-blok behandelt alle zones (. is de DNS-root) op poort 53. Meerdere server blocks zijn toegestaan; bij een binnenkomende query wint het blok met de langst matchende zone.

Twee dingen om te weten: de reload-plugin pollt de SHA512-checksum van de ConfigMap elke ~30 seconden en herlaadt bij wijzigingen (geen pod-herstart nodig, maar propagatie kan tot 2 minuten duren). En {$ENV_VAR}-syntax maakt environment-variable-substitutie mogelijk bij het parsen.

Plugin-keten: uitvoeringsvolgorde

Dit is het belangrijkste concept van de Corefile. De volgorde van pluginnamen in de Corefile bepaalt niet de uitvoeringsvolgorde. CoreDNS heeft een compile-time plugin.cfg die een vaste verwerkingsvolgorde definieert. Je kunt cache voor forward zetten of erna; de uitvoeringsvolgorde is identiek.

Voor de standaard Kubernetes-build is de relevante volgorde:

  1. errors (vangt foutresponses op)
  2. log (querylogging, als ingeschakeld)
  3. rewrite (transformeert queries)
  4. hosts (statische hosttabel-lookups)
  5. kubernetes (autoritatief voor clusterzones)
  6. autopath (zoekpadoptimalisatie, als ingeschakeld)
  7. forward (upstream-proxy)
  8. cache (response-caching)
  9. loop (forwardingloopdetectie)
  10. loadbalance (round-robin van A/AAAA-records)

Elke plugin serveert ofwel een response (en stopt de keten) of roept de volgende plugin aan via fallthrough. Als niets de query serveert, retourneert CoreDNS SERVFAIL.

Belangrijke plugins

Plugin Wat het doet
kubernetes Autoritatief voor cluster.local, in-addr.arpa, ip6.arpa. Watcht de API-server op Services, EndpointSlices, Pods.
forward Proxyt niet-clusterqueries naar upstream-resolvers. Ondersteunt UDP, TCP en DNS-over-TLS. Maximaal 15 upstreams.
cache In-memory responsecache. Standaardcapaciteit: 9984 entries. Aparte TTL's voor succesvolle en NXDOMAIN-responses.
errors Logt foutresponses naar stdout.
health HTTP /health op poort 8080. De lameduck 5s-directive vertraagt shutdown zodat lopende queries kunnen finishen.
ready HTTP /ready op poort 8181. Retourneert 200 alleen als alle plugins ready melden. Gebruikt door de readiness-probe.
prometheus Stelt Prometheus-metrics beschikbaar op poort 9153 (/metrics). Standaard ingeschakeld.
loop Detecteert DNS-forwardingloops (bijv. CoreDNS → systemd-resolved → terug naar CoreDNS) en stopt het proces.
reload Herlaadt de Corefile als de ConfigMap wijzigt.
loadbalance Randomiseert de recordvolgorde in A/AAAA/MX-responses voor round-robin-distributie.

Service-DNS-records

CoreDNS genereert verschillende recordtypen afhankelijk van de Service-configuratie. De Kubernetes DNS-specificatie definieert deze patronen.

Normale services (ClusterIP)

Een query voor my-api.production.svc.cluster.local retourneert een enkel A-record dat naar het ClusterIP van de Service wijst. De client praat met het VIP; kube-proxy regelt de routing naar pods.

Headless services

Een headless Service (clusterIP: None) heeft geen VIP. Dezelfde DNS-naam retourneert meerdere A-records, een per Ready pod-IP. Clients ontvangen alle pod-IP's en moeten zelf selecteren.

Voor StatefulSets krijgt elke pod een individueel record:

postgres-0.postgres.db.svc.cluster.local → pod-0's IP
postgres-1.postgres.db.svc.cluster.local → pod-1's IP
postgres.db.svc.cluster.local            → alle pod-IP's

Het patroon is <pod-naam>.<service-naam>.<namespace>.svc.<cluster-domain>. Deze per-pod-records bestaan alleen wanneer een headless Service matcht met de spec.subdomain van de pod.

SRV-records

Worden aangemaakt voor named ports op zowel normale als headless Services:

_http._tcp.my-api.production.svc.cluster.local → 8080 my-api.production.svc.cluster.local

ExternalName-services

Een Service met type: ExternalName levert een CNAME-record op:

legacy-db.production.svc.cluster.local CNAME rds.us-east-1.amazonaws.com

Geen VIP, geen kube-proxy-regels. De client resolvet het CNAME via normale DNS-recursie.

Aangepaste DNS-configuratie

Stub domains

Een stub domain routeert queries voor een specifiek DNS-suffix naar een toegewijde nameserver in plaats van de upstream. In CoreDNS is dit een extra server block in de Corefile. Bewerk de ConfigMap:

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
        forward . /etc/resolv.conf { max_concurrent 1000 }
        cache 30
        loop
        reload
        loadbalance
    }
    consul.local:53 {
        errors
        cache 30
        forward . 10.150.0.1
    }
    internal.corp:53 {
        errors
        cache 30
        forward . 192.168.1.53
    }

Een beperking: de forward-plugin vereist IP-adressen voor upstream-nameservers. FQDN's zoals ns.corp.example.com werken niet.

Eigen upstream-resolvers

Vervang de standaard forward . /etc/resolv.conf door expliciete IP's om CoreDNS los te koppelen van de node-level resolverconfiguratie:

forward . 8.8.8.8 8.8.4.4 {
    max_fails 3
    health_check 5s
    policy random
}

Voor DNS-over-TLS-forwarding:

forward . tls://9.9.9.9 tls://149.112.112.112 {
    tls_servername dns.quad9.net
    health_check 5s
    force_tcp
}

Statische hostoverrides

De hosts-plugin mapt vaste IP's naar hostnamen, handig voor on-premises databases of legacyservices zonder DNS-records:

hosts {
    10.0.1.100 mydb.internal.example.com
    192.168.5.5 legacy-api.corp.local
    fallthrough
}

De fallthrough-directive stuurt niet-gematchte namen door naar de volgende plugin.

NodeLocal DNSCache

NodeLocal DNSCache is een Kubernetes-add-on (DaemonSet) die een CoreDNS-cache op elke node draait. GA sinds Kubernetes 1.18.

Het probleem dat het oplost

Zonder NodeLocal DNSCache reizen pod-DNS-queries via iptables-DNAT naar een CoreDNS-pod die op een andere node kan zitten. Bij hoge DNS-load veroorzaakt dit drie problemen:

  1. Conntrack-tabeldruk. UDP-entries hebben geen connection-close-events en moeten uitvallen na een timeout (standaard 30 seconden). Bij hoge queryratio's raakt de conntrack-tabel vol en worden pakketten gedropt.
  2. DNAT-races. Gelijktijdige DNS-queries met dezelfde connection-tuple racen in de conntrack-tabel, een bekend Linux-kernelprobleem dat intermitterende 5-seconden timeouts veroorzaakt.
  3. Cross-node latency. Elke cachemiss vereist een netwerk-roundtrip naar een CoreDNS-pod op een andere node.

Voor het diagnosticeren van conntrack-gerelateerde DNS-storingen, zie Kubernetes DNS-problemen oplossen.

Hoe het werkt

Zonder NodeLocal DNSCache:
  Pod → iptables DNAT → kube-dns VIP → CoreDNS-pod (mogelijk andere node)

Met NodeLocal DNSCache:
  Pod → 169.254.20.10 (link-local, node-lokaal) → node-local-dns DaemonSet-pod
          ├── cluster.local cachemiss → TCP → kube-dns VIP → CoreDNS
          └── externe cachemiss      → upstream-nameservers

De init-container van de DaemonSet voegt een link-local IP (169.254.20.10 standaard) toe aan het lo-interface van de node. Dit niet-routeerbare adres zorgt ervoor dat pods de lokale cache bereiken zonder het netwerk over te hoeven. Cachemisses voor cluster.local worden over TCP geforward (niet UDP), wat expliciete conntrack-close-events oplevert en de conntrack-racecondition volledig elimineert.

In IPVS kube-proxy-modus moet de kubelet worden geconfigureerd met --cluster-dns=169.254.20.10 zodat pods geboren worden met de lokale cache als nameserver. In iptables-modus (de standaard) zijn er geen kubelet-wijzigingen nodig; de DaemonSet onderschept verkeer bestemd voor het kube-dns-VIP op nodeniveau.

Geheugenoverwegingen

Als de node-local-dns pod OOMKilled wordt, blijven de aangepaste iptables-regels actief en worden queries gestuurd naar een nu afwezig proces. DNS faalt op die node totdat de DaemonSet-controller de pod herstart. Stel geheugenlimieten royaal in of gebruik VPA in recommender-modus. De standaardcache bevat ongeveer 10.000 entries (~30 MB).

Performancetuning

De ndots:5-overhead

Met ndots:5 genereert een pod die api.github.com opvraagt (2 dots) vier DNS-queries voordat hij resolvet:

  1. api.github.com.app.svc.cluster.local (NXDOMAIN)
  2. api.github.com.svc.cluster.local (NXDOMAIN)
  3. api.github.com.cluster.local (NXDOMAIN)
  4. api.github.com (succes)

In high-QPS-omgevingen is 75% van de DNS-queries gegarandeerd falen. Drie mitigatie-opties:

Verlaag ndots per pod (het best voor workloads met veel externe calls):

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

Trailing dots in applicatieconfiguratie forceren absolute resolutie en slaan de search-domain-expansie helemaal over:

api_endpoint: "api.github.com."

De autopath-plugin (server-side mitigatie) onderschept de eerste search-expanded query, detecteert de namespace van de pod, en retourneert het absolute antwoord in een enkele roundtrip. Het vereist pods verified-modus in de kubernetes-plugin, wat het geheugenverbruik ongeveer 4x verhoogt per Pod/Service-object:

  • Zonder autopath: (Pods + Services) / 1000 + 54 MB
  • Met autopath: (Pods + Services) / 250 + 56 MB

Cachetuning

De standaard cache 30 stelt een maximale TTL van 30 seconden in met 9984 entries. Voor meer controle:

cache {
    success 9984 3600 5     # capaciteit, max TTL, min TTL
    denial  9984 30   5     # NXDOMAIN-responses: kortere TTL
    prefetch 10 1m 10%      # populaire entries verversen voor expiry
    serve_stale 1h immediate  # verlopen entries serveren als upstream down is
}

De prefetch-directive vernieuwt proactief entries die minstens 10 keer zijn opgevraagd in een 1-minutenvenster en waarvan de TTL onder 10% is gezakt. serve_stale biedt veerkracht tijdens upstream-storingen door verlopen cache-entries te serveren gedurende maximaal de opgegeven duur.

Replica's schalen

CoreDNS is CPU-bound. Een CPU-gebaseerde HPA is de aanbevolen schaalaanpak:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coredns
  namespace: kube-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coredns
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Draai altijd minstens 2 replica's met pod anti-affinity om single-node-failures te overleven. De Cluster Proportional Autoscaler (schalen op node-/core-aantallen) is een alternatief, maar CPU-gebaseerde HPA past beter bij CoreDNS omdat de bottleneck CPU is, niet het aantal connecties.

Een waarschuwing over resourcelimieten: vaste CPU-limieten veroorzaken throttling die zich manifesteert als DNS-latencypieken. Sommige operators verwijderen CPU-limieten helemaal en vertrouwen op CPU-requests voor scheduling. Monitor container_cpu_throttled_seconds_total voor de CoreDNS-pods.

Belangrijke metrics om te monitoren

CoreDNS stelt Prometheus-metrics beschikbaar op poort 9153 via de prometheus-plugin.

Wat je wilt zien PromQL
Querysnelheid rate(coredns_dns_requests_total[5m])
NXDOMAIN-ratio (ndots-overheadsignaal) rate(coredns_dns_responses_total{rcode="NXDOMAIN"}[5m])
P99 latency histogram_quantile(0.99, rate(coredns_dns_request_duration_seconds_bucket[5m]))
Cache-hitratio sum(rate(coredns_cache_hits_total[5m])) / (sum(rate(coredns_cache_hits_total[5m])) + sum(rate(coredns_cache_misses_total[5m])))
Upstream health-failures rate(coredns_forward_healthcheck_failures_total[5m])

Een hoge NXDOMAIN-ratio ten opzichte van totale queries is een sterk signaal dat ndots:5-overhead je DNS-verkeer domineert. Zie Kubernetes DNS-problemen oplossen voor stapsgewijze diagnose wanneer deze metrics op een probleem wijzen.

Wat CoreDNS niet is

CoreDNS is geen general-purpose recursive resolver. Het voert standaard geen volledige DNSSEC-validatie uit (hoewel er een dnssec-plugin bestaat). Het fungeert niet als autoritatieve nameserver voor externe zones zonder extra configuratie. En het vervangt de DNS-infrastructuur van je organisatie niet; het zit ervoor als cluster-scoped caching forwarder voor alles buiten cluster.local.

CoreDNS is ook niet kube-dns, ondanks de kube-dns-Servicenaam. kube-dns was een architectuur van drie containers (dnsmasq + kubedns + sidecar) met configuratie verspreid over meerdere flags en ConfigMap-keys. CoreDNS is een enkele binary met een uniforme Corefile-configuratie. De Servicenaam is gewoon een backward-compatibility-artefact.

Verder lezen

  • Kubernetes DNS-problemen oplossen behandelt stapsgewijs het diagnosticeren van DNS-fouten: CrashLoopBackOff, ndots-query-explosie, conntrack-races, dnsPolicy-misconfiguratie en upstream-resolverproblemen.
  • Kubernetes Services uitgelegd behandelt Service-typen, kube-proxy-modi en hoe verkeer van een Service-VIP naar pod-endpoints stroomt.

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.