DNS : votre DNS « fonctionne » mais les applications échouent — les couches de cache oubliées

Cet article vous a aidé ?

Voici une chute familière : dig renvoie la bonne IP, vos tableaux de bord DNS sont au vert, et pourtant votre application se comporte comme si elle était coupée du monde. Les requêtes expirent. Les handshakes TLS échouent. Les retries s’accumulent jusqu’à ce que votre load balancer s’inquiète. Tout le monde pointe le DNS puisqu’il est le coupable le plus simple à nommer.

Parfois le DNS est effectivement en cause. Le plus souvent, le DNS n’est que témoin — et la scène du crime réelle est une couche de cache que vous avez oubliée, située entre votre appli et la réponse autoritaire, qui sert fièrement la vérité d’hier.

Le modèle mental : « DNS fonctionne » n’existe pas

Quand quelqu’un dit « DNS fonctionne », il veut généralement dire : depuis mon laptop, maintenant, avec mon outil préféré, je peux résoudre un nom. C’est un bon moment. Ce n’est pas une clôture d’incident.

En production, le DNS est une chaîne. Votre application appelle une API de résolveur (souvent via libc), qui peut consulter des caches locaux, un résolveur stub, un cache node-local, un service DNS de cluster, un résolveur récursif en amont, et enfin les serveurs autoritaires. À chaque étape, quelqu’un peut décider de mettre en cache. À chaque étape, les timeouts et les retries peuvent se multiplier. À chaque étape, la configuration peut diverger entre environnements.

Le résultat est l’échec classique en écran partagé :

  • Les humains : « Regardez, dig marche. »
  • Les applis : « Je ne peux pas me connecter, et je vais maintenant garder une IP obsolète en mémoire jusqu’à la mort thermique de l’univers. »

Ce désaccord survient parce que le DNS n’est pas un seul cache. C’est plusieurs caches, chacun avec ses propres règles de TTL, politique d’éviction et sémantique d’échec. Vous ne déboguez pas le DNS en argumentant sur des enregistrements A. Vous le déboguez en trouvant la couche de cache qui ment.

Les couches de cache auxquelles vous faites réellement face

1) Caches au niveau applicatif (le cache « mon code est innocent »)

Beaucoup de runtimes et de bibliothèques mettent en cache les résultats DNS, parfois de manière agressive, parfois indéfiniment, et parfois d’une façon qui ne respecte pas le TTL que vous avez soigneusement défini.

  • JVM : la mise en cache DNS est famosément « utile ». Selon les propriétés de sécurité et la présence d’un SecurityManager (de moins en moins courant), elle peut mettre en cache les réponses positives longtemps, et les réponses négatives aussi. Si vous exécutez Java et ignorez vos paramètres effectifs de TTL DNS, vous jouez à quitte ou double.
  • Go : le chemin du résolveur dépend des flags de build et de l’environnement. Dans beaucoup de configurations Linux, Go peut utiliser le résolveur système via cgo (héritant du comportement de libc) ou un résolveur en pur Go avec sa propre approche de mise en cache et de retries. Les conteneurs ajoutent du piment.
  • Node.js / Python / Ruby : le comportement varie selon la bibliothèque. Le runtime peut ne pas beaucoup mettre en cache, mais les clients HTTP de haut niveau, les pools de connexions ou les SDK de discovery service le font souvent.
  • Clients HTTP et pools : Même si le DNS se rafraîchit correctement, un pool peut continuer à se connecter à une ancienne IP jusqu’à ce que le socket se rompe. « DNS corrigé » ne signifie pas que le trafic a basculé.

Les problèmes de cache applicatif sont brutaux car ils sont invisibles pour vos outils DNS habituels. Une JVM tenant une IP en mémoire continuera d’échouer alors que dig réussit. Ce n’est pas un DNS capricieux. C’est votre processus qui fait de la résistance.

2) libc et NSS (le cache « ça dépend de /etc/nsswitch.conf »)

Sur Linux, la résolution de noms passe par NSS (Name Service Switch). Cela signifie que votre appli peut consulter files (fichier hosts), puis dns, puis mDNS, LDAP, ou n’importe quoi que quelqu’un a configuré il y a trois ans lors d’un « quick fix ». L’ordre importe. Le comportement en cas d’échec importe. Les timeouts importent.

glibc lui‑même ne maintient pas un gros cache DNS persistant comme certains le supposent. Mais il peut quand même vous piquer via :

  • /etc/hosts qui écrase (obsolète, oublié ou intégré dans des images).
  • Modules NSS qui mettent en cache (ex. SSSD, nscd) ou introduisent des délais.
  • Options de resolv.conf comme timeout, attempts, et rotate qui changent fortement les modes d’échec.

3) Démons de cache locaux : nscd, dnsmasq, systemd-resolved

Si vous avez systemd-resolved, il exécute probablement un stub resolver sur 127.0.0.53 et met en cache les réponses. Si vous avez dnsmasq sur des laptops ou des nœuds, il met en cache. Si vous avez encore nscd (il n’a pas disparu partout), il peut mettre en cache les recherches d’hôtes.

Ces caches sont généralement bien intentionnés : réduire la latence, réduire le taux de requêtes en amont, survivre à des pannes transitoires de résolveur. Mais ils peuvent devenir l’endroit où les mauvaises réponses persistent.

4) Caches node-local (particulièrement en Kubernetes)

Les clusters Kubernetes exécutent souvent CoreDNS comme service. Beaucoup d’équipes ajoutent NodeLocal DNSCache pour réduire la charge et améliorer la latence tail. C’est bien — jusqu’à ce que ce ne le soit plus. Maintenant vous avez :

  • Pods → cache node-local (iptables vers une IP locale)
  • Cache node-local → service CoreDNS
  • CoreDNS → résolveurs récursifs en amont

Plus de couches, plus d’occasions d’entrées obsolètes, de configurations incohérentes et de télémetries confuses. Si le cache d’un nœud est bloqué, une tranche de pods échouera tandis que d’autres sembleront normales. C’est le type d’incident qui génère de longs fils Slack et des nerfs à vif.

5) Résolveurs récursifs (le cache « pour lequel on paye »)

Vos résolveurs récursifs en amont (DNS d’entreprise, résolveurs des fournisseurs cloud ou services managés) mettent en cache agressivement. Ils appliquent aussi des plafonds de TTL dans certains environnements : TTL minimums/maximums, comportements de prefetch, et service d’entrées périmées lors de pannes en amont.

Certains résolveurs renverront des réponses expirées mais mises en cache (« serve stale ») pour préserver la disponibilité. C’est excellent quand les serveurs autoritaires sont down. C’est terrible quand vous essayez d’évacuer le trafic d’une IP morte.

6) DNS autoritatif (l’endroit où vous éditez tout en pensant que ça change)

Le DNS autoritatif est l’endroit où résident vos paramètres TTL. C’est aussi là où vous pensez avoir le contrôle. Mais votre autorité ne compte que si chaque couche de cache la respecte. Beaucoup ne le feront pas. Beaucoup la « respecteront » d’une manière techniquement permise mais opérationnellement gênante.

7) Caches non-DNS qui ressemblent à des pannes DNS

Ce n’est pas toute panne « DNS » qui est liée au DNS. Certains problèmes sont causés par :

  • Pooling de connexion : les clients continuent d’utiliser des sockets existants vers d’anciens backends.
  • Happy Eyeballs / préférence IPv6 : votre appli tente AAAA d’abord, échoue lentement, puis retombe sur A.
  • Réutilisation de session TLS et mismatch SNI : vous atteignez la mauvaise IP et obtenez une erreur de certificat ; cela ressemble à un « DNS incorrect », mais c’est routage plus TLS.
  • Délais de propagation des états de santé du load balancer : le DNS pointe vers un LB qui envoie encore le trafic vers des cibles cassées.

Blague #1 : Le DNS est comme un moulin de rumeurs — au moment où il atteint tout le monde, il est techniquement exact et opérationnellement inutile.

Faits et contexte historique qui expliquent le désordre actuel

  1. Le DNS a été conçu au début des années 1980 pour remplacer la distribution de HOSTS.TXT ; la mise en cache était une fonctionnalité dès le départ, pas une réflexion après coup.
  2. Le concept de TTL visait à contrôler la durée de vie des caches, mais beaucoup de résolveurs récursifs appliquent des politiques : planchers/plafonds de TTL qui outrepassent votre intention.
  3. Le caching négatif a été standardisé plus tard : NXDOMAIN et les réponses « no data » peuvent être mises en cache, ce qui signifie que « ça n’existait pas » peut persister après que vous l’ayez créé.
  4. Les premières recommandations opérationnelles supposaient des enregistrements relativement stables. Les microservices modernes et l’autoscaling produisent plus de churn DNS que le système ne l’avait socialement anticipé.
  5. Certains résolveurs implémentent des comportements « serve stale » pour améliorer la disponibilité lors de pannes en amont, au prix de coupures plus lentes.
  6. systemd-resolved (milieu des années 2010) a normalisé les stub resolvers en localhost sur de nombreuses distributions, changeant les habitudes de debug et surprenant ceux qui s’attendent à ce que /etc/resolv.conf liste de vrais upstreams.
  7. Kubernetes a rendu le DNS critique pour le plane de contrôle de la découverte de services ; CoreDNS est devenu l’un des pods les plus importants du cluster, que cela vous plaise ou non.
  8. Les CDN et les traffic managers globaux ont fait du DNS une partie du routage. C’est puissant, mais cela signifie que les réponses DNS sont parfois dépendantes de la localisation et pilotées par des politiques.
  9. Le split-horizon DNS est courant en entreprise (réponses internes et externes différentes), ce qui signifie que « ça marche sur mon laptop » peut refléter une vue différente de celle qu’obtiennent vos workloads.

Trois mini-récits d’entreprise (anonymisés, plausibles, techniquement exacts)

Mini-récit #1 : L’incident causé par une mauvaise hypothèse

L’équipe gérait une API multi-région derrière un nom DNS qui renvoyait différents enregistrements A par région. Pendant une migration, ils ont abaissé les TTL à l’avance et programmé une bascule. Le plan officiel était : mettre à jour les enregistrements, attendre une minute, vérifier la nouvelle distribution du trafic.

Le jour de la bascule arriva. dig depuis une bastion montrait immédiatement les nouvelles cibles. Le monitoring, cependant, affichait un flux tenace de trafic vers l’ancienne région. La latence a grimpé. Certaines requêtes ont expiré. L’on-call a pensé que le changement n’avait pas été propagé et a commencé à « réparer le DNS » à plusieurs reprises, sans rien accomplir sauf plus d’anxiété.

Le véritable coupable n’était ni le DNS autoritatif ni les résolveurs récursifs. C’était une couche applicative : un service Java qui effectuait une recherche DNS une fois au démarrage pour une dépendance en amont et mettait le résultat en cache dans le processus. Ce n’était pas malveillant. C’était « une optimisation », écrite il y a des années quand les upstreams ne bougeaient jamais. Le service avait été stable si longtemps que personne ne se souvenait de son existence.

Ils ont redéployé pour forcer une nouvelle résolution. Le trafic a bougé instantanément. Le postmortem fut gênant parce que l’équipe DNS avait tout fait correctement, et l’équipe applicative avait fait quelque chose de compréhensible. La correction pérenne fut de supprimer le cache in-process, d’honorer le TTL, et d’ajouter une vérification au déploiement pour prouver que le runtime rafraîchit le DNS sans redémarrage.

Mini-récit #2 : L’optimisation qui s’est retournée contre eux

Un groupe plateforme a introduit NodeLocal DNSCache pour réduire la charge sur CoreDNS et corriger la latence DNS intermittente sous trafic en rafales. Le déploiement semblait concluant. La consommation CPU de CoreDNS est passée à la baisse. La latence P99 de résolution s’est améliorée. L’équipe a déclaré victoire.

Deux mois plus tard, un sous‑ensemble de nœuds commença à montrer des échecs localisés et mystérieux : des pods sur certains nœuds ne pouvaient pas résoudre de nouveaux noms de service pendant plusieurs minutes. Les ingénieurs voyaient des NXDOMAIN dans les logs applicatifs. Pendant ce temps, des pods planifiés sur d’autres nœuds résolvaient correctement. Cette asymétrie fit suspecter Kubernetes lui‑même, ou le « networking », ou la phase de la lune.

Le cache node-local faisait du caching négatif de NXDOMAIN pour un nom de service apparu juste après un déploiement. Une course dans la séquence de déploiement provoquait des requêtes précoces avant que le Service existe, générant NXDOMAIN. Une fois mis en cache, ces nœuds continuaient à croire que le service n’existait pas. La vérité autoritaire avait changé, mais le cache négatif ne s’était pas mis à jour.

Ils ont corrigé le problème en resserrant l’ordre de déploiement (Service avant workloads), en réduisant le TTL du cache négatif dans la configuration DNS, et en ajoutant une alerte sur le taux de NXDOMAIN par nœud. NodeLocal DNSCache est resté — c’était toujours une bonne idée — mais l’équipe a appris qu’améliorer le P99 peut aussi augmenter la vitesse à laquelle vous mettez en cache la mauvaise chose.

Mini-récit #3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une plateforme de paiements travaillait avec une pratique DNS conservatrice : chaque dépendance critique avait une entrée de runbook listant l’intégralité du chemin de résolution. App → libc → stub node → DNS de cluster → résolveur en amont → autoritatif. Pour chaque étape, ils documentaient quels logs existaient, quelles métriques surveiller, et comment la contourner.

C’était du travail ennuyeux. Personne n’a été promu pour « avoir documenté la chaîne de résolveur ». Mais cela signifiait que lors d’une panne où un résolveur récursif en amont commença à timeouter intermittently, ils n’ont pas perdu des heures à se disputer via les laptops de chacun.

Ils ont immédiatement reproduit la panne depuis un pod affecté, puis depuis le nœud, puis directement contre l’IP du résolveur en amont. Cela a isolé le problème : le résolveur en amont était le goulot, pas CoreDNS, pas l’appli. Ils ont reconfiguré temporairement les nœuds pour utiliser un pool de résolveurs secondaire et ont restauré le service.

Plus tard, ils ont utilisé leurs tableaux de bord existants pour montrer la latence de requête et les taux de timeout par résolveur. Ils ont escaladé vers le propriétaire du résolveur récursif avec des preuves plutôt qu’avec des sentiments. L’incident fut court. Le postmortem fut calme. La pratique ennuyeuse a payé.

Playbook de diagnostic rapide : quoi vérifier en premier/deuxième/troisième

Voici le workflow qui vous fait gagner des heures. L’objectif est d’identifier quelle couche de cache ment, et si l’échec vient de la résolution, du routage, ou de la réutilisation de connexion.

Premier : reproduire depuis le même namespace réseau que l’appli qui échoue

  • Si c’est Kubernetes : exec dans le pod (ou un pod de debug sur le même nœud).
  • Si c’est une VM : exécutez depuis l’hôte, pas depuis votre laptop.
  • Si c’est un conteneur : exécutez depuis l’intérieur du conteneur.

Si vous ne pouvez pas reproduire depuis le même endroit, vous déboguez des impressions.

Deuxième : confirmer quel résolveur le workload utilise réellement

  • Vérifiez /etc/resolv.conf à l’intérieur du workload.
  • Vérifiez s’il pointe vers 127.0.0.53 (stub systemd-resolved), une IP node-local, ou le service DNS du cluster.
  • Vérifiez les search domains et ndots. Ils peuvent transformer une recherche en cinq.

Troisième : isoler si c’est une « mauvaise réponse » ou une « réponse lente »

  • Mauvaise réponse : l’IP renvoyée est obsolète, pointe vers une cible morte, ou diffère selon le client.
  • Réponse lente : timeouts, retries longs, SERVFAIL occasionnels. Les applis traitent souvent cela comme une panne de dépendance.

Quatrième : bypasser intentionnellement des couches

  • Interrogez le résolveur configuré.
  • Interrogez directement le résolveur récursif en amont.
  • Interrogez l’autoritatif (ou au moins un résolveur récursif connu bon) directement.

Chaque contournement est un test qui élimine une couche de cache.

Cinquième : vérifier les paramètres de cache DNS de l’appli/runtime

  • TTL et TTL négatif de la JVM.
  • Sidecars proxy (Envoy) : fréquence de rafraîchissement DNS et comportement de circuit.
  • Clients de découverte de service (Consul, SDKs basés sur etcd, personnalisés) qui mettent en cache des endpoints.

Sixième : vérifier que les connexions ont réellement bougé

  • Si le DNS a été mis à jour mais le trafic n’a pas bougé, c’est généralement du pooling de connexions ou du comportement du LB.
  • Vérifiez les sockets existants, les keep-alives, et la logique de retry.

Idée paraphrasée (attribuée) : Werner Vogels a insisté sur le fait que tout échoue, donc les systèmes doivent être conçus pour s’attendre et gérer les pannes.

Tâches pratiques : commandes, ce que signifie la sortie, et la décision à prendre

Voici les actions concrètes que je veux que les ingénieurs exécutent réellement. Chaque tâche inclut une commande, une sortie représentative, ce que cela signifie, et la décision à prendre.

Task 1: Voir à quoi ressemble resolv.conf à l’intérieur de l’environnement défaillant

cr0x@server:~$ cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
search svc.cluster.local cluster.local

Signification : Vous utilisez un stub resolver local (127.0.0.53), et les search domains seront ajoutés. Le comportement de résolution dépend de systemd-resolved, pas du « nameserver » que vous pensiez.

Décision : Ne perdez pas de temps à interroger des upstreams aléatoires pour l’instant. Interrogez d’abord systemd-resolved et confirmez vers où il relaie.

Task 2: Vérifier l’ordre NSS et si des sources non-DNS peuvent écraser

cr0x@server:~$ grep -E '^\s*hosts:' /etc/nsswitch.conf
hosts:          files mdns4_minimal [NOTFOUND=return] dns myhostname

Signification : /etc/hosts est consulté en premier. mDNS peut court-circuiter. DNS n’est pas le premier arrêt.

Décision : Si le symptôme est « marche sur une machine mais pas une autre », comparez /etc/hosts et les configs NSS. Votre « problème DNS » peut être une surcharge locale.

Task 3: Confirmer le statut systemd-resolved, les serveurs upstream, et les stats du cache

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.0.0.2
       DNS Servers: 10.0.0.2 10.0.0.3

Signification : Le stub relaie vers 10.0.0.2 et 10.0.0.3. Si ceux‑ci sont lents ou empoisonnés, tout ce qui est au-dessus en souffre.

Décision : Testez directement ces IP de résolveur en amont ensuite. Si les requêtes directes sont lentes, escaladez auprès des propriétaires du résolveur.

Task 4: Vider le cache systemd-resolved (pour un test contrôlé)

cr0x@server:~$ sudo resolvectl flush-caches

Signification : Vous avez supprimé les entrées locales mises en cache. Si le comportement change immédiatement, vous avez trouvé une couche qui ment.

Décision : Si le flush corrige, concentrez-vous sur pourquoi des entrées obsolètes étaient servies (politique TTL, serve-stale, caching négatif, ou incohérence en amont).

Task 5: Interroger via le résolveur configuré et mesurer

cr0x@server:~$ dig +tries=1 +time=2 api.internal.example A

;; ANSWER SECTION:
api.internal.example. 30 IN A 10.40.12.19

;; Query time: 3 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)

Signification : Le stub a répondu rapidement avec TTL 30. C’est bien, mais ce n’est toujours qu’une couche.

Décision : Si l’appli échoue encore, suspectez le caching applicatif ou la réutilisation de connexions. Comparez aussi avec une interrogation directe de l’amont.

Task 6: Interroger directement le résolveur récursif en amont (bypass du stub)

cr0x@server:~$ dig @10.0.0.2 api.internal.example A +tries=1 +time=2

;; ANSWER SECTION:
api.internal.example. 30 IN A 10.40.12.19

;; Query time: 210 msec
;; SERVER: 10.0.0.2#53(10.0.0.2)

Signification : L’amont est plus lent que le stub. Cela peut être normal (cache local), ou un signal d’alerte (amont surchargé).

Décision : Si les requêtes amont sont constamment lentes ou timeoutent, réparez la capacité, le chemin réseau, ou la santé du résolveur. Ne réglez pas d’abord les retries applicatifs ; cela masque le vrai problème.

Task 7: Vérifier le caching négatif en interrogeant un nom que vous venez de créer

cr0x@server:~$ dig newservice.svc.cluster.local A +tries=1 +time=2

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1122
;; Query time: 4 msec

Signification : NXDOMAIN peut être mis en cache. Si le service existe maintenant mais que certains clients voient encore NXDOMAIN, vous faites face au caching négatif.

Décision : Validez l’ordre de création dans les déploiements et ajustez le TTL négatif lorsque vous en avez le contrôle (CoreDNS, caches node-local). Si vous ne pouvez pas régler, adaptez les rollouts pour éviter les requêtes avant que les enregistrements existent.

Task 8: Observer l’expansion des search domains et le comportement ndots (piège courant Kubernetes)

cr0x@server:~$ cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5 timeout:1 attempts:2

Signification : Avec ndots:5, un nom comme api.internal.example (deux points) peut être traité comme « relatif » et essayé avec les search domains d’abord, générant plusieurs requêtes et des délais.

Décision : Pour les noms externes, utilisez le FQDN avec un point final (api.internal.example.) dans les configs quand c’est supporté, ou ajustez ndots prudemment (en comprenant les valeurs par défaut du cluster).

Task 9: Utiliser getent pour voir ce que l’application voit via libc/NSS

cr0x@server:~$ getent hosts api.internal.example
10.40.12.19     api.internal.example

Signification : getent utilise NSS, ce qui correspond mieux au chemin de résolution de nombreuses applications que dig.

Décision : Si dig marche mais getent échoue ou renvoie des résultats différents, concentrez-vous sur la configuration NSS, le fichier hosts, et les démons de cache locaux — pas le DNS autoritatif.

Task 10: Vérifier si /etc/hosts écrase votre nom

cr0x@server:~$ grep -n "api.internal.example" /etc/hosts
12:10.20.1.99 api.internal.example

Signification : Quelqu’un l’a codé en dur. Cela écrasera le DNS si NSS vérifie files en premier (commun).

Décision : Supprimez l’écrasement, reconstruisez les images sans lui, et ajoutez des contrôles CI pour éviter le pinning du fichier hosts pour des noms de services en production.

Task 11: En Kubernetes, interroger CoreDNS directement et comparer les réponses

cr0x@server:~$ kubectl -n kube-system get svc kube-dns -o wide
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE   SELECTOR
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP   2y    k8s-app=kube-dns

Signification : L’IP du service CoreDNS est 10.96.0.10.

Décision : Interrogez-la directement depuis un pod pour bypasser les caches node-local ou les stubs lorsque vous isolez le responsable.

Task 12: Inspecter la config CoreDNS pour le caching et les stub domains

cr0x@server:~$ kubectl -n kube-system get configmap coredns -o yaml | sed -n '1,120p'
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        forward . 10.0.0.2 10.0.0.3
        cache 30
        loop
        reload
        loadbalance
    }

Signification : CoreDNS relaie vers des résolveurs en amont et met en cache pendant 30 secondes. Ce n’est pas « mauvais », mais cela explique le délai de propagation et le comportement obsolète lors des bascules.

Décision : Si vous avez besoin d’une bascule plus rapide pour certains enregistrements, considérez des TTL plus bas de bout en bout et vérifiez les politiques des résolveurs. Ne mettez pas bêtement le cache à 0 ; vous vous exposerez à une avalanche de requêtes.

Task 13: Chercher des erreurs et timeouts dans CoreDNS

cr0x@server:~$ kubectl -n kube-system logs -l k8s-app=kube-dns --tail=20
[ERROR] plugin/errors: 2 api.internal.example. A: read udp 10.244.1.10:45712->10.0.0.2:53: i/o timeout
[ERROR] plugin/errors: 2 api.internal.example. AAAA: read udp 10.244.1.10:46891->10.0.0.2:53: i/o timeout

Signification : CoreDNS timeoute vers le résolveur amont 10.0.0.2. Les applis verront des échecs de résolution intermittents ou des délais.

Décision : Réparez la santé/capacité du résolveur amont ou le routage. Comme mitigation, ajoutez/retirez des upstreams et réduisez timeout/attempts pour échouer vite, mais ne prétendez pas que c’est « juste Kubernetes ».

Task 14: Confirmer sur quelle IP votre appli se connecte réellement (DNS vs pooling de connexions)

cr0x@server:~$ sudo ss -tnp | grep -E ':(443|8443)\s' | head
ESTAB 0 0 10.244.3.21:48712 10.40.12.10:443 users:(("java",pid=2314,fd=214))

Signification : Le processus est connecté à 10.40.12.10, qui peut être une IP ancienne même si le DNS renvoie maintenant 10.40.12.19.

Décision : Si le DNS est correct mais que les sockets pointent vers d’anciennes cibles, votre problème est la réutilisation des connexions. Réduisez la durée de keep-alive, implémentez une ré-résolution périodique, ou faites tourner les pods/clients lors des bascules.

Task 15: Mesurer rapidement la latence de résolution et le taux d’échec

cr0x@server:~$ for i in {1..10}; do dig +tries=1 +time=1 api.internal.example A | awk '/Query time/ {print $4}'; done
3
4
1002
2
1001
3
4
1001
2
3

Signification : Certaines requêtes prennent ~1s (timeout) puis réussissent via des chemins de retry, ce qui empoisonne la latence tail.

Décision : Traitez cela comme une panne de dépendance : réparez le saut résolveur lent, réduisez les attempts, et évitez de multiplier les retries à plusieurs couches.

Blague #2 : Le caching DNS est le seul endroit où être « finalement consistant » signifie « finalement, vous recevez une alerte à 3h du matin ».

Erreurs fréquentes : symptômes → cause racine → correction

1) Symptom : dig marche, l’appli cible encore une vieille IP

Cause racine : Pooling de connexions ou cache DNS in-process (JVM, SDK, cache personnalisé). Le DNS a changé, mais l’appli n’a pas re-résolu ou ne s’est pas reconnectée.

Correction : Assurez-vous que le runtime respecte le TTL et re-résout périodiquement ; limitez la durée des keep-alives ; lors des bascules, recyclez proprement les clients. Validez avec ss et les paramètres runtime.

2) Symptom : Certains pods échouent, d’autres vont bien (même déploiement)

Cause racine : Cache DNS node-local bloqué, chemin réseau node-spécifique vers le résolveur, ou problèmes de stub resolver par nœud.

Correction : Comparez /etc/resolv.conf et la couche de cache par nœud ; videz les caches ; redémarrez les pods DNS node-local ; réparez la reachability en amont depuis le nœud.

3) Symptom : Le nom de service nouvellement créé renvoie NXDOMAIN pendant des minutes

Cause racine : Caching négatif dans des caches node-local ou résolveurs récursifs ; ordre de déploiement où des requêtes arrivent avant que l’enregistrement n’existe.

Correction : Créez d’abord les objets DNS avant que les clients ne les interrogent ; réduisez le TTL du caching négatif quand c’est possible ; ajoutez des gates de readiness dans les rollouts qui vérifient la résolution.

4) Symptom : Première requête lente, puis les suivantes rapides

Cause racine : Timeouts/attempts du résolveur trop élevés, expansion des search domains, ou tentatives AAAA qui timeoutent avant que A réussisse.

Correction : Ajustez les timeouts et attempts du résolveur ; réparez la reachabilité IPv6 ou ajustez la stratégie des familles d’adresses ; réduisez les expansions de search non nécessaires en utilisant des FQDN absolus.

5) Symptom : Marche sur le laptop, échoue en production

Cause racine : Split-horizon DNS, chaîne de résolveur différente, VPN/résolveurs d’entreprise, ou zones internes non visibles depuis la production.

Correction : Testez depuis le même namespace réseau et les mêmes résolveurs que le workload ; documentez quelle vue (interne/externe) chaque environnement utilise ; n’utilisez pas le DNS du laptop comme preuve.

6) Symptom : Pics aléatoires de SERVFAIL

Cause racine : Problèmes de résolveur récursif en amont, échecs de validation DNSSEC sur certaines voies, perte de paquets (fragmentation UDP), ou CoreDNS surchargé.

Correction : Vérifiez les logs CoreDNS et la latence des requêtes en amont ; testez fallback TCP ; réduisez la taille EDNS si besoin ; scalez la capacité des résolveurs et ajoutez de la diversité.

7) Symptom : Le taux de requêtes DNS explose après un petit changement de config

Cause racine : Désactivation du caching, TTL trop bas globalement, ou création de nombreux noms uniques (par ex. hostnames par requête).

Correction : Rétablissez le caching avec des TTLs raisonnables ; évitez les hostnames par requête ; utilisez des patterns de service discovery qui n’impliquent pas de transformer le DNS en boucle chaude.

8) Symptom : Le canary réussit ; le trafic en masse échoue

Cause racine : Stack runtime différent : le canary utilise un client, une configuration sidecar, ou des flags JVM différents ; le bulk utilise pooling de connexions ou résolution obsolète.

Correction : Assurez-vous que le canary et le bulk ont la même chaîne de résolveur et les mêmes paramètres runtime ; comparez les flags de process et les configs sidecar ; exécutez getent et inspectez les sockets dans les deux.

Checklists / plan étape par étape

Checklist A : Pendant un incident (15–30 minutes)

  1. Reproduire depuis le workload (pod/container/host). Capturez l’horodatage et le nom du nœud.
  2. Enregistrer les preuves de la chaîne de résolveur : /etc/resolv.conf, resolvectl status (si pertinent), et /etc/nsswitch.conf.
  3. Exécuter à la fois dig et getent pour le nom en échec. Si elles diffèrent, priorisez le debug NSS/cache local.
  4. Mesurer la distribution des temps de requête (boucle dig 10–20 fois). Cherchez des pics intermittents à 1s/2s.
  5. Bypasser des couches : interroger le résolveur configuré, puis l’upstream directement.
  6. Vérifier le caching négatif : confirmer si NXDOMAIN est impliqué ; identifier si l’enregistrement/service a été créé récemment.
  7. Confirmer les connexions réelles : utilisez ss pour voir où les processus sont connectés. Le DNS peut être correct alors que les sockets sont figés.
  8. Miter prudemment : redémarrez la couche de cache qui ment (stub/node-local/CoreDNS) seulement si vous comprenez le rayon d’impact ; sinon redirigez vers des résolveurs sains.

Checklist B : Après l’incident (pour que ça ne se reproduise pas)

  1. Documenter la chaîne de résolveur pour chaque environnement. Inclure qui possède chaque étape.
  2. Définir explicitement la politique de cache runtime pour la JVM et les sidecars. Ne comptez pas sur les valeurs par défaut.
  3. Standardiser les outils : getent pour « ce que voit l’appli », dig pour « ce que dit le DNS ». Enseignez la différence.
  4. Aligner les TTLs avec la réalité opérationnelle : si vous avez besoin de bascules en 30 secondes, assurez-vous que les caches n’imposent pas un TTL minimum de 5 minutes.
  5. Ajouter de l’observabilité : latence de requête, taux SERVFAIL/NXDOMAIN, santé des caches par nœud en Kubernetes.
  6. Pratiquer les bascules : effectuez un changement d’enregistrement planifié en non-prod et vérifiez que les clients migrent réellement sans redémarrages.

Checklist C : Avant d’ajouter une nouvelle couche de cache (faire preuve d’honnêteté)

  1. Définir pourquoi vous en avez besoin : latence tail, charge du résolveur, résilience. Choisissez un objectif principal.
  2. Décider qui en est propriétaire et comment c’est monitoré. « C’est juste du DNS » n’est pas un modèle de propriété.
  3. Décider explicitement de la politique de caching négatif.
  4. Planifier les contournements et contrôles d’urgence : comment pointer rapidement les clients vers un autre résolveur.
  5. Tester les modes de défaillance : timeout en amont, SERVFAIL, NXDOMAIN, perte de paquets, et changements d’autorité.

FAQ

1) Pourquoi dig marche mais l’appli échoue à résoudre ?

dig interroge le DNS directement et bypass une partie du chemin du résolveur système. Votre appli utilise probablement NSS/libc (ou le comportement de résolveur du runtime) plus des caches. Utilisez getent hosts name pour mieux correspondre au comportement de l’appli.

2) Si je mets le TTL à 30 secondes, pourquoi les clients utilisent-ils encore d’anciennes réponses pendant des minutes ?

Parce que certaines couches de cache appliquent des planchers/plafonds de TTL, des politiques serve-stale, ou l’appli met en cache indépendamment. De plus, même avec un rafraîchissement DNS correct, les connexions TCP existantes ne migreront pas magiquement vers de nouvelles IP.

3) Vider le cache DNS est‑il une vraie correction ?

C’est un diagnostic et parfois une mesure d’urgence. Si vider le cache corrige, vous avez trouvé une couche qui sert des données périmées/incorrectes. La vraie correction consiste à aligner la politique TTL, la santé des résolveurs, et le comportement de cache runtime.

4) Pourquoi seuls certains pods Kubernetes échouent le DNS ?

Souvent parce que la résolution dépend du nœud (cache DNS node-local, réseau node-spécifique, redirection iptables, ou reachability du résolveur par nœud). Confirmez le nœud et testez depuis un autre pod planifié sur le même nœud.

5) Qu’est‑ce que le caching négatif et pourquoi s’en soucier ?

Le caching négatif signifie mettre en cache les réponses « n’existe pas » (NXDOMAIN/no data). Si votre déploiement interroge un nom avant qu’il existe, les caches peuvent se souvenir du NXDOMAIN et continuer à échouer après la création du nom.

6) glibc met‑il en cache les résultats DNS ?

glibc suit principalement NSS et la configuration du résolveur ; le caching persistant est généralement fait par des démons externes (systemd-resolved, nscd, dnsmasq) ou des couches supérieures (appli/runtime). N’assumez pas un « cache glibc » sans preuve.

7) Comment savoir si c’est DNS ou pooling de connexions ?

Si les réponses DNS sont correctes mais que le process se connecte encore à une ancienne IP, c’est du pooling/réutilisation. Vérifiez les connexions actives avec ss -tnp et comparez avec les réponses DNS actuelles.

8) Devons‑nous désactiver le caching DNS pour éviter l’obsolescence ?

Non, pas en politique générale. Désactiver le caching transfère la charge en amont, augmente la latence, et peut créer des pannes en cascade lors de pépins de résolveur. Au lieu de cela, ajustez TTL, caching négatif, et assurez‑vous que les clients se comportent correctement.

9) Pourquoi voyons‑nous des délais pour résoudre des noms courts à l’intérieur des clusters ?

Les search domains et ndots peuvent provoquer plusieurs requêtes par lookup. Un nom peut être essayé comme name.namespace.svc.cluster.local et autres variantes avant le FQDN prévu, et les timeouts se cumulent.

10) Quelle est la métrique la plus utile pour la fiabilité DNS ?

La latence des requêtes du résolveur et les taux de timeout/SERVFAIL à chaque saut (node-local, CoreDNS, résolveur amont). « Taux de requête » seul est trompeur ; vous voulez savoir quand la résolution devient lente ou instable.

Conclusion : actions pratiques à engager

Si votre DNS « fonctionne » mais que les applis échouent, arrêtez de traiter le DNS comme un composant unique. Traitez‑le comme une chaîne de caches et de politiques. Votre travail est de trouver le saut qui ment ou qui est lent, puis de le rendre ennuyeux à nouveau.

  1. Standardisez votre base de debug : collectez toujours /etc/resolv.conf, /etc/nsswitch.conf, et un résultat getent depuis l’environnement du workload.
  2. Instrumentez la chaîne de résolveur : latence et taux d’échec de CoreDNS/node-local cache/résolveur amont. Si vous ne le voyez pas, vous ne pouvez pas l’exploiter.
  3. Rendez explicite le caching runtime : JVM, proxies, et SDKs. Les valeurs par défaut ne sont pas une stratégie.
  4. Planifiez les bascules en tenant compte des connexions : les changements DNS ne drainent pas les sockets existants. Prévoyez du temps ou forcez la reconnexion en sécurité.
  5. Écrivez le runbook que vous auriez aimé avoir : chemin de résolveur, commandes de contournement, et propriété. C’est ennuyeux. C’est aussi ce qui vous évitera le spectacle d’improvisation à 3h du matin.
← Précédent
Passthrough GPU Proxmox : écran noir — 10 causes et solutions
Suivant →
GPUs OEM : comment éviter d’acheter une carte « réduite en secret »

Laisser un commentaire