Ubuntu 24.04 : systemd-resolved casse le DNS de Docker — corriger sans tout désactiver

Cet article vous a aidé ?

Vous déployez un conteneur, il démarre correctement, le healthcheck est vert… puis il ne parvient plus à résoudre un nom d’hôte.
apt update reste bloqué. Les logs de votre application indiquent « Temporary failure in name resolution. »
L’hôte lui-même résout les noms parfaitement, ce qui rend la situation d’autant plus frustrante.

Sur Ubuntu 24.04, cela aboutit souvent au même piège : systemd-resolved fait son travail,
Docker fait le sien, et le chevauchement crée une situation DNS qui ressemble à de la sorcellerie. Ce n’est pas le cas.
C’est de la plomberie. Et la correction ne demande pas de désactiver la moitié de votre pile réseau par antipathie.

Ce qui casse réellement : résolveurs stub, namespaces et DNS de Docker

Nommont les éléments en mouvement, car l’échec ne paraît mystérieux que si vous traitez le DNS comme une boîte unique.
Sur Ubuntu 24.04, l’hôte utilise couramment systemd-resolved pour fournir un « stub resolver » local
lié à 127.0.0.53. Le /etc/resolv.conf de l’hôte est souvent un lien symbolique vers un fichier
qui pointe vers ce stub.

Docker, quant à lui, injecte typiquement un résolveur dans les conteneurs en copiant ou en générant
/etc/resolv.conf à l’intérieur de l’espace de noms réseau du conteneur. Si Docker voit des serveurs de noms dans le
resolv.conf de l’hôte, il tentera de les utiliser. Voici le piège : le 127.0.0.53 d’un conteneur n’est pas l’hôte.
C’est le conteneur lui‑même. Donc quand un conteneur tente d’interroger 127.0.0.53, il se pose essentiellement
la question DNS à lui-même, et personne ne répond.

Docker dispose d’un serveur DNS embarqué (généralement accessible à 127.0.0.11 à l’intérieur des conteneurs)
pour la découverte de services sur les réseaux définis par l’utilisateur. Mais ce DNS embarqué a toujours besoin de serveurs en amont
pour transférer les requêtes vers l’extérieur. Si l’amont est réglé vers le stub de l’hôte (127.0.0.53), le transfert échoue.

Il existe d’autres variantes :

  • Docker récupère nameserver 127.0.0.53 depuis l’hôte et l’écrit dans les conteneurs. Échec immédiat.
  • Docker utilise son DNS embarqué (127.0.0.11) mais l’amont est cassé, donc les noms internes fonctionnent, les noms externes échouent.
  • Le split DNS (domaines d’entreprise routés vers des résolveurs spécifiques) fonctionne sur l’hôte via systemd-resolved,
    mais les conteneurs contournent cette logique et interrogent le DNS public pour des zones internes, recevant NXDOMAIN ou des timeouts.

L’objectif ici n’est pas de « gagner » en désactivant systemd-resolved. Il apporte une valeur légitime :
DNS par lien, état DNSSEC, mise en cache, et bonne intégration avec NetworkManager et netplan. L’objectif est de faire en sorte que
Docker utilise un resolv.conf qui contient des serveurs en amont atteignables, et de le faire de manière prévisible.

Une citation pour cadrer l’esprit (idée paraphrasée) : John Allspaw a promu l’idée que la fiabilité vient de la compréhension des systèmes, pas du blâme des personnes.
Les pannes DNS sont presque toujours des interactions entre systèmes, pas de l’incompétence d’un opérateur.

Blague #1 : le DNS est la seule partie de la pile où « ça marchait hier » est considéré comme un format de configuration.

Faits intéressants et contexte historique (pour que ça paraisse moins aléatoire)

  1. Le stub de systemd-resolved à 127.0.0.53 existe pour éviter les collisions avec des résolveurs locaux comme dnsmasq,
    et pour garder un point d’accès loopback cohérent même lorsque les upstream changent.
  2. Le DNS embarqué de Docker (127.0.0.11) a été introduit pour supporter la découverte conteneur-à-conteneur sur
    des réseaux définis par l’utilisateur, pas comme un « client DNS d’entreprise » généraliste.
  3. La bibliothèque résolveur de glibc lit /etc/resolv.conf et a des règles comme « 3 nameservers max »
    qui peuvent silencieusement ignorer des entrées, ce qui est amusant quand les clients VPN ajoutent cinq résolveurs.
  4. Ubuntu s’est intégré encore plus dans systemd ces dernières versions ; que /etc/resolv.conf
    soit un lien symbolique est maintenant normal, pas exotique.
  5. Le split DNS était autrefois rare hors des entreprises. Maintenant même des utilisateurs domestiques y sont confrontés avec des VPN maillés, des outils dev,
    et du « DNS magique » pour des domaines internes.
  6. Les namespaces réseau signifient que le loopback est par namespace. 127.0.0.1 dans un conteneur n’est pas l’hôte.
    Si vous ne retenez qu’une chose, retenez celle-ci.
  7. Le resolv.conf dans les conteneurs n’est pas sacré. Il est généré au démarrage du conteneur et peut changer selon
    les paramètres Docker, le pilote réseau et le runtime.
  8. systemd-resolved peut exposer un resolv.conf « réel » listant les serveurs en amont via
    /run/systemd/resolve/resolv.conf, qui est souvent la source la plus propre pour Docker à consommer.

Mode opératoire de diagnostic rapide

Quand la production sonne l’alerte et que vous avez besoin d’un signe rapide, ne commencez pas par éditer des fichiers de configuration.
Commencez par identifier où la résolution échoue : à l’intérieur du conteneur, au DNS embarqué de Docker, ou au niveau du résolveur de l’hôte.

Première étape : confirmer ce qui échoue et où

  • Dans le conteneur : pouvez-vous résoudre quoi que ce soit ? Des noms publics ? Des noms internes ?
  • Dans le conteneur : que contient réellement /etc/resolv.conf ?
  • Hôte : est-ce que systemd-resolved est sain et quels serveurs en amont connaît-il ?

Deuxième étape : classer la panne

  • nameserver 127.0.0.53 dans le conteneur → presque certainement une mauvaise propagation du resolv.conf.
  • nameserver 127.0.0.11 dans le conteneur, mais timeouts → l’amont du DNS embarqué de Docker est incorrect/inaccessible.
  • Le public fonctionne, les domaines d’entreprise échouent → mismatch du split DNS entre l’hôte et les conteneurs.
  • Seuls certains réseaux échouent → route VPN, pare-feu, ou MTU/fragmentation affectant le trafic DNS.

Troisième étape : corriger au niveau le plus étroit

  • Préférez indiquer à Docker un resolv.conf réel ou des serveurs DNS explicites.
  • Utilisez des paramètres DNS par réseau ou par Compose quand c’est approprié.
  • Évitez de désactiver systemd-resolved sauf si vous le remplacez par un résolveur mieux défini et que vous acceptez la portée du changement.

Tâches pratiques : commandes, sorties attendues et décision à prendre

Voici les vérifications que j’exécute réellement sur des hôtes Ubuntu quand des conteneurs commencent à avoir des problèmes DNS.
Chaque tâche inclut (1) une commande, (2) ce que la sortie signifie, et (3) la décision à prendre.
Exécutez-les dans l’ordre jusqu’à obtenir un diagnostic fiable.

Tâche 1 : confirmer le symptôme DNS Docker depuis l’intérieur d’un conteneur minimal

cr0x@server:~$ docker run --rm alpine:3.20 sh -lc 'cat /etc/resolv.conf; nslookup -type=a example.com 2>&1 | sed -n "1,12p"'
nameserver 127.0.0.53
options edns0 trust-ad
nslookup: can't resolve '(null)': Name does not resolve

Signification : le conteneur tente de parler à 127.0.0.53 (lui‑même), pas au résolveur de l’hôte.
La requête échoue immédiatement.
Décision : cessez d’accuser l’amont DNS. Corrigez la façon dont Docker peuple le resolv.conf des conteneurs (ou le DNS du daemon Docker).

Tâche 2 : vérifier si le resolv.conf de l’hôte est le stub de systemd

cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Oct 10 09:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

Signification : l’hôte pointe vers le fichier du stub resolver.
Décision : si Docker hérite de ceci, il fournira aux conteneurs un serveur de noms sans issue. Vous voulez que Docker utilise le resolv.conf « réel ».

Tâche 3 : inspecter le resolv.conf « réel » qui liste les serveurs en amont

cr0x@server:~$ sed -n '1,20p' /run/systemd/resolve/resolv.conf
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
nameserver 10.20.0.10
nameserver 10.20.0.11
search corp.example

Signification : ce sont des résolveurs en amont atteignables depuis le point de vue réseau de l’hôte.
Décision : la meilleure pratique est généralement : faire en sorte que Docker lise ce fichier au lieu du fichier stub.

Tâche 4 : vérifier que systemd-resolved tourne et n’est pas dégradé

cr0x@server:~$ systemctl status systemd-resolved --no-pager -l
● systemd-resolved.service - Network Name Resolution
     Loaded: loaded (/usr/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
     Active: active (running) since Thu 2025-12-28 10:20:18 UTC; 3h 12min ago
       Docs: man:systemd-resolved.service(8)
   Main PID: 812 (systemd-resolve)
     Status: "Processing requests..."
      Tasks: 1 (limit: 38291)
     Memory: 6.1M
        CPU: 1.228s

Signification : resolved est sain.
Décision : ne « corrigez » pas cela en le désactivant. Votre problème est l’intégration avec Docker, pas la santé du résolveur.

Tâche 5 : voir quels serveurs DNS resolved pense utiliser par lien

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

Link 2 (ens160)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.20.0.10
       DNS Servers: 10.20.0.10 10.20.0.11
        DNS Domain: corp.example

Signification : l’hôte utilise le DNS de ens160 et a un domaine de recherche.
Décision : si les conteneurs doivent résoudre corp.example, vous devez faire en sorte que Docker utilise ces serveurs en amont ou vous perdrez le comportement split DNS.

Tâche 6 : vérifier la vue de Docker sur le resolv.conf de l’hôte qu’il utilisera

cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 127.0.0.53

Signification : le daemon Docker est configuré (explicitement ou implicitement) pour utiliser le stub resolver.
Décision : overridez le DNS de Docker pour utiliser des upstream réels ou pointez Docker vers le resolv.conf non-stub.

Tâche 7 : confirmer quel resolv.conf un conteneur en cours d’exécution a réellement obtenu

cr0x@server:~$ cid=$(docker run -d alpine:3.20 sleep 300); docker exec "$cid" cat /etc/resolv.conf; docker rm -f "$cid" >/dev/null
nameserver 127.0.0.53
options edns0 trust-ad

Signification : ce n’est pas le DNS embarqué de Docker ; il hérite directement du stub.
Décision : corrigez l’entrée que Docker utilise (resolv.conf de l’hôte ou configuration DNS du daemon).

Tâche 8 : vérifier si le DNS embarqué de Docker est présent (scénario 127.0.0.11)

cr0x@server:~$ docker network create testnet >/dev/null
cr0x@server:~$ cid=$(docker run -d --network testnet alpine:3.20 sleep 300)
cr0x@server:~$ docker exec "$cid" cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
cr0x@server:~$ docker rm -f "$cid" >/dev/null; docker network rm testnet >/dev/null

Signification : sur les réseaux définis par l’utilisateur, Docker utilise son DNS embarqué.
Décision : si les résolutions échouent encore ici, l’amont utilisé par le DNS embarqué de Docker est le problème.

Tâche 9 : inspecter les règles iptables/nft qui permettent le transfert DNS des conteneurs

cr0x@server:~$ sudo nft list ruleset | sed -n '1,80p'
table inet filter {
  chain input {
    type filter hook input priority filter; policy accept;
  }
  chain forward {
    type filter hook forward priority filter; policy accept;
  }
}
table ip nat {
  chain POSTROUTING {
    type nat hook postrouting priority srcnat; policy accept;
    oifname "ens160" ip saddr 172.17.0.0/16 masquerade
  }
}

Signification : une masquerade NAT existe, la politique forward est accept (dans cet extrait).
Décision : si vous voyez des politiques forward restrictives ou une masquerade manquante, les requêtes DNS peuvent ne jamais quitter l’hôte. Corrigez les règles de pare-feu avant de toucher à la configuration DNS.

Tâche 10 : surveiller les logs de systemd-resolved pendant une tentative DNS d’un conteneur

cr0x@server:~$ sudo journalctl -u systemd-resolved -n 30 --no-pager
Dec 28 13:21:04 server systemd-resolved[812]: Using degraded feature set UDP instead of UDP+EDNS0 for DNS server 10.20.0.10.
Dec 28 13:21:10 server systemd-resolved[812]: DNS server 10.20.0.11 does not support DNSSEC, disabling DNSSEC validation for this server.

Signification : resolved négocie des capacités ; ces messages ne sont pas fatals en eux‑mêmes.
Décision : s’il y a des timeouts, des vagues de SERVFAIL, ou un changement fréquent de serveur, vous avez aussi des problèmes en amont. Ne vous arrêtez pas au problème du stub Docker.

Tâche 11 : valider que l’hôte peut résoudre ce que les conteneurs ne peuvent pas (vérification split DNS)

cr0x@server:~$ resolvectl query registry.corp.example
registry.corp.example: 10.30.40.50                      -- link: ens160

-- Information acquired via protocol DNS in 12.7ms.
-- Data is authenticated: no

Signification : l’hôte résout un nom d’entreprise via le DNS configuré sur son lien.
Décision : si les conteneurs ne peuvent pas résoudre ceci, vous devez fournir ces résolveurs d’entreprise à Docker, pas le DNS public.

Tâche 12 : valider la résolution du conteneur avec des serveurs DNS explicites (preuve rapide)

cr0x@server:~$ docker run --rm --dns 10.20.0.10 --dns 10.20.0.11 alpine:3.20 sh -lc 'nslookup example.com | sed -n "1,8p"'
Server:		10.20.0.10
Address:	10.20.0.10:53

Non-authoritative answer:
Name:	example.com
Address: 93.184.216.34

Signification : avec des serveurs en amont explicites, le conteneur résout correctement.
Décision : votre correctif permanent est de configurer le DNS du daemon Docker (ou le DNS de Compose/réseau) pour utiliser des serveurs atteignables ou le resolv.conf réel.

Tâche 13 : vérifier la présence du fichier de configuration du daemon Docker et ses paramètres effectifs

cr0x@server:~$ sudo test -f /etc/docker/daemon.json && sudo cat /etc/docker/daemon.json || echo "no /etc/docker/daemon.json"
no /etc/docker/daemon.json

Signification : aucune configuration explicite du daemon n’est définie.
Décision : vous pouvez ajouter en toute sécurité un daemon.json minimal pour contrôler le DNS plutôt que de jouer aux jeux de liens symboliques.

Tâche 14 : confirmer quel resolv.conf Docker utilise lorsque le fichier de l’hôte change

cr0x@server:~$ sudo readlink -f /etc/resolv.conf
/run/systemd/resolve/stub-resolv.conf

Signification : Docker sélectionnera couramment ceci au démarrage du daemon, pas nécessairement de manière dynamique par conteneur.
Décision : si vous changez le lien symbolique resolv.conf de l’hôte, redémarrez Docker pour vous assurer qu’il le relit (prévoir une fenêtre de maintenance si nécessaire).

Blague #2 : rien ne dit « haute disponibilité » comme livrer une correction DNS qui nécessite un redémarrage du daemon à 14h un mardi.

Schémas de correction qui fonctionnent (sans désactiver systemd-resolved)

Il existe trois approches raisonnables. Choisissez-en une selon le niveau « entreprise » de votre DNS, et selon l’importance que vous accordez à conserver le réseau hôte standard.
Je vous dirai ce que je ferais en production pour chaque cas.

Schéma A (recommandé) : dire à Docker d’utiliser des serveurs DNS en amont réels

C’est la plus propre dans le sens « rendre les pannes ennuyeuses ». Vous empêchez Docker d’hériter du stub resolver et vous lui fournissez
les résolveurs vers lesquels il doit transférer.

Créez ou modifiez /etc/docker/daemon.json :

cr0x@server:~$ sudo install -d -m 0755 /etc/docker
cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "dns": ["10.20.0.10", "10.20.0.11"],
  "dns-search": ["corp.example"]
}
EOF
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 10.20.0.10
 DNS: 10.20.0.11

Ce que cela fait : le DNS embarqué de Docker et/ou le resolv.conf des conteneurs pointe désormais vers des résolveurs atteignables depuis les conteneurs (via le NAT de l’hôte).
Pourquoi c’est bien : déterministe, pas de jeux de liens symboliques, résilient aux mises à jour d’Ubuntu.
Inconvénient : si les upstream changent (DHCP, ordinateurs portables en déplacement), vous devrez mettre à jour cette configuration ou l’automatiser.

Schéma B : pointer le /etc/resolv.conf de l’hôte vers le fichier non-stub de systemd-resolved

C’est courant et fonctionne bien sur des serveurs ayant un réseau stable. L’astuce : vous ne désactivez pas resolved ; vous faites simplement
en sorte que /etc/resolv.conf liste des serveurs en amont réels au lieu de 127.0.0.53.
Docker hérite alors de résolveurs utilisables.

cr0x@server:~$ sudo mv /etc/resolv.conf /etc/resolv.conf.bak
cr0x@server:~$ sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 32 Dec 28 14:01 /etc/resolv.conf -> /run/systemd/resolve/resolv.conf
cr0x@server:~$ sudo systemctl restart docker

Ce que cela fait : le résolveur glibc de l’hôte interroge maintenant directement les serveurs en amont.
Pourquoi c’est bien : Docker hérite de bons serveurs de noms sans configuration additionnelle du daemon.
Inconvénient : certaines configurations reposent sur le mode stub pour des comportements spécifiques ; aussi, les ordinateurs portables/VPN peuvent beaucoup faire tourner le DNS.
Si cet hôte se déplace souvent, je préfère configurer Docker explicitement.

Schéma C : overrides DNS par Compose / par conteneur (chirurgical, parfois nécessaire)

C’est ce que vous faites quand une stack a besoin du DNS d’entreprise et une autre doit utiliser des résolveurs publics, ou quand un conteneur tiers
fait des choses étranges avec le DNS. C’est aussi ce que l’on fait quand on ne possède pas les paramètres du daemon hôte (bonjour, runners partagés).

Exemple avec Docker Compose :

cr0x@server:~$ cat compose.yaml
services:
  app:
    image: alpine:3.20
    command: ["sh", "-lc", "cat /etc/resolv.conf; nslookup example.com | sed -n '1,8p'; sleep 3600"]
    dns:
      - 10.20.0.10
      - 10.20.0.11
    dns_search:
      - corp.example

Ce que cela fait : remplace le resolv.conf à l’intérieur du conteneur.
Pourquoi c’est bien : rayon d’impact limité ; acceptable pour le contrôle des changements.
Inconvénient : prolifération de la configuration. Si vous avez 40 projets Compose, vous en oublierez un.

À éviter : « désactiver systemd-resolved » par réflexe

Vous pouvez tout à fait supprimer systemd-resolved et utiliser un resolv.conf statique ou un autre résolveur local.
Parfois c’est la bonne décision, surtout si vous exécutez déjà dnsmasq/unbound et que vous voulez une pile plus simple.
Mais comme réponse par défaut, c’est trop radical.

Désactiver resolved casse souvent :

  • Le comportement split DNS du VPN intégré à NetworkManager
  • La configuration DNS par interface sur des hôtes multi‑homés
  • Les systèmes qui s’attendent au stub de resolved (divers outils desktop et dev)

Si vous voulez de la fiabilité, ne supprimez pas des composants avant de pouvoir expliquer exactement ce qu’ils faisaient pour vous.

Un détail subtil en production : comportement de redémarrage et cycle de vie des conteneurs

Docker écrit généralement le /etc/resolv.conf d’un conteneur au démarrage. Si vous corrigez le DNS et ne redémarrez pas les conteneurs,
certains garderont l’ancienne config cassée. Le plan de déploiement importe donc :

  • Corriger le DNS du daemon Docker.
  • Redémarrer Docker si nécessaire.
  • Redeployer / redémarrer les conteneurs pour rafraîchir leur resolv.conf.

Trois micro-récits du monde d’entreprise (comment ça échoue en réalité)

Micro-récit 1 : l’incident causé par une mauvaise hypothèse

Une équipe avec laquelle j’ai travaillé exécutait des agents de build sur des hôtes Ubuntu. Rien de particulier : builds Docker, quelques tests d’intégration,
puis push d’artefacts vers un registre interne. Une mise à jour système routinière est passée : Ubuntu 24.04, mises à jour du noyau,
un reboot, et tout semblait correct. L’hôte résolvait tout. Les agents CI ? La moitié d’entre eux ont commencé à échouer
sur « could not resolve host » en plein pipeline.

L’hypothèse erronée était douloureusement simple : « si l’hôte résout le DNS, les conteneurs aussi. »
Cette croyance tenait depuis des années sur des configurations plus anciennes parce que le /etc/resolv.conf de l’hôte listait des upstream réels.
La mise à jour l’a remplacé par le stub systemd, et Docker a copieusement mis l’adresse du stub dans les conteneurs.
Les conteneurs se sont alors interrogés eux‑mêmes pour obtenir des réponses DNS. Ils n’étaient pas aussi sages qu’ils en avaient l’air.

La réponse initiale a été le théâtre corporatif classique d’escalade : tickets ouverts auprès de l’équipe réseau, demande si le DNS est en panne,
relances des builds, blâme de « l’infra flaky ». Pendant ce temps, l’indice était sous les yeux : nameserver 127.0.0.53
à l’intérieur du conteneur. La correction a été de définir le DNS du daemon Docker vers les résolveurs d’entreprise et de redémarrer le service Docker
pendant une fenêtre de maintenance.

La leçon durable n’était pas « systemd-resolved est mauvais ». C’était « les namespaces changent la signification de localhost. »
À chaque revue d’incident depuis, l’équipe a ajouté une ligne : toujours vérifier le /etc/resolv.conf du conteneur
avant de diagnostiquer l’amont DNS.

Micro-récit 2 : l’optimisation qui s’est retournée contre eux

Une autre organisation avait décidé de « réduire la latence » en hardcodant des serveurs DNS publics dans Docker sur toutes les machines de dev.
Le raisonnement semblait sensé : moins de pièces mobiles, pas de dépendance au DNS d’entreprise, résolution plus rapide que via le VPN.
Ils ont poussé un /etc/docker/daemon.json standard avec deux résolveurs publics.

Ça a fonctionné une semaine. Puis les outils internes ont commencé à échouer dans les conteneurs : miroirs de paquets, hôtes Git internes,
découverte de services sous un domaine privé. Sur l’hôte, tout fonctionnait toujours parce que systemd-resolved
appliquait le split DNS : les domaines d’entreprise allaient vers les résolveurs d’entreprise, le reste vers le public.
Les conteneurs ont contourné cette logique et ont interrogé le DNS public pour des zones privées, recevant NXDOMAIN rapidement et avec assurance.

L’« optimisation » n’a pas seulement échoué ; elle a échoué vite, de la manière la plus trompeuse. Les développeurs voyaient « l’hôte marche, le conteneur ne marche pas »,
ont supposé que le réseau Docker était cassé et ont commencé à ajouter des bricolages : --add-host supplémentaires, paramètres DNS ad hoc par projet,
et caches qui masquaient le problème jusqu’au prochain changement réseau.

Le rollback a été d’arrêter d’imposer le DNS public globalement, et de configurer Docker pour utiliser les mêmes résolveurs en amont que l’hôte,
ou d’utiliser un DNS par projet seulement quand une exception légitime existait. Le gain profond a été une politique : le DNS fait partie de la parité d’environnement.
Si votre environnement dev conteneurisé n’obéit pas aux mêmes règles de routage DNS que la production, vous déboguerez des fantômes.

Micro-récit 3 : la pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe SRE avait une habitude peu glamour : chaque hôte disposait d’un petit script de « sanity réseau » exécuté par cron et aussi disponible comme snippet de runbook.
Il vérifiait trois choses : la résolution DNS de l’hôte via resolvectl, la résolution DNS du conteneur via un conteneur minimal connu,
et si le /etc/resolv.conf de l’hôte était en mode stub ou upstream.

Quand Ubuntu 24.04 est arrivé, les alertes ont commencé à sonner avant que les clients ne remarquent quoi que ce soit : « mismatch DNS conteneur détecté. »
Rien n’était tombé parce que la plupart des services utilisaient des conteneurs déjà démarrés avec des résultats mis en cache, mais l’équipe savait
que le prochain déploiement échouerait.

Ils ont utilisé la sortie du script pour classifier le problème comme « stub propagé dans les conteneurs » et ont appliqué une correction standard :
définir le DNS du daemon Docker vers des résolveurs en amont tirés de /run/systemd/resolve/resolv.conf, puis redémarrer Docker
pendant une fenêtre contrôlée, puis redémarrer d’abord les services sans état.

La pratique n’était pas glamour. Elle n’impliquait pas de service mesh ni d’IA. C’était une validation basique aux frontières
entre hôte et conteneur. Et elle a transformé ce qui aurait pu être un incident multi‑équipes en un changement planifié.

Erreurs courantes : symptômes → cause racine → correction

1) Les conteneurs affichent « Temporary failure in name resolution » immédiatement

Symptôme : toute recherche DNS échoue instantanément dans les conteneurs ; l’hôte fonctionne.

Cause racine : le /etc/resolv.conf du conteneur contient nameserver 127.0.0.53.

Correction : configurez le DNS du daemon Docker (/etc/docker/daemon.json) ou pointez le resolv.conf de l’hôte vers
/run/systemd/resolve/resolv.conf, puis redémarrez Docker et les conteneurs affectés.

2) Les noms de services internes se résolvent, les noms externes expirent

Symptôme : ping other-container fonctionne sur un réseau défini par l’utilisateur, mais apt update échoue.

Cause racine : le DNS embarqué de Docker (127.0.0.11) fonctionne pour la découverte interne, mais le transfert en amont casse
à cause de l’héritage du stub ou d’un DNS en amont inaccessible.

Correction : définissez le DNS du daemon Docker vers des résolveurs en amont réels ; confirmez que le NAT/pare‑feu permet la sortie UDP/TCP 53.

3) Ça marche hors VPN, ça échoue sur VPN (ou l’inverse)

Symptôme : les domaines d’entreprise échouent uniquement quand le VPN est connecté.

Cause racine : l’hôte utilise le split DNS via resolved ; les conteneurs pointent vers des résolveurs publics ou anciens.

Correction : fournissez à Docker les mêmes résolveurs d’entreprise (et domaines de recherche) que l’hôte, ou ajoutez un DNS par Compose pour les stacks qui en ont besoin.

4) Seuls certains conteneurs échouent après un changement DNS

Symptôme : les anciens conteneurs continuent de fonctionner ; les nouveaux déploiements échouent.

Cause racine : les conteneurs conservent le resolv.conf avec lequel ils ont été créés ; les nouveaux obtiennent la nouvelle configuration cassée.

Correction : après avoir corrigé le DNS de Docker, redémarrez les conteneurs (priorisez les stateless), ou recréez les stacks Compose.

5) Le DNS fonctionne pour de petites réponses mais échoue pour des réponses plus grandes

Symptôme : certaines requêtes réussissent ; d’autres bloquent ou retournent SERVFAIL ; souvent pire sur VPN.

Cause racine : problèmes de MTU/fragmentation provoquant la perte de fragments UDP, ou problèmes de taille EDNS0 sur un chemin.

Correction : réduisez la MTU sur le bridge Docker ou l’interface VPN, ou assurez-vous que le fallback TCP fonctionne ; vérifiez les logs de resolved pour « degraded feature set. »

6) Vous avez « corrigé » en mettant 8.8.8.8, maintenant les choses internes sont cassées

Symptôme : les domaines publics se résolvent ; les domaines internes ne se résolvent pas.

Cause racine : vous avez contourné le DNS d’entreprise et les zones à visibilité différente.

Correction : utilisez les résolveurs d’entreprise comme upstream pour Docker, pas des résolveurs publics, à moins d’être certain que vos workloads n’ont jamais besoin du DNS interne.

7) Le DNS de l’hôte casse après avoir changé le lien symbolique /etc/resolv.conf

Symptôme : l’hôte cesse soudainement de résoudre ou se comporte de façon incohérente après des modifications du lien symbolique.

Cause racine : des outils s’attendaient au mode stub ; ou vous avez écrasé resolv.conf d’une manière qui entre en conflit avec NetworkManager/netplan.

Correction : préférez la configuration DNS du daemon Docker (schéma A). Si vous changez le lien symbolique, gardez une sauvegarde et vérifiez avec resolvectl.

Listes de contrôle / plan étape par étape

Plan A (recommandé) : serveurs stables, vous contrôlez le daemon Docker

  1. Confirmer la panne : lancez un conteneur one-shot et vérifiez /etc/resolv.conf.
    Si vous voyez 127.0.0.53, poursuivez.
  2. Identifier les résolveurs en amont depuis /run/systemd/resolve/resolv.conf ou resolvectl status.
  3. Configurer le DNS du daemon Docker dans /etc/docker/daemon.json avec ces serveurs en amont.
  4. Redémarrer Docker dans une fenêtre contrôlée.
  5. Relancer les conteneurs (redémarrer/recréer) pour rafraîchir le resolv.conf des conteneurs.
  6. Valider avec des noms publics et internes (si pertinent).
  7. Documenter la décision dans un runbook : « Nous fixons le DNS Docker aux résolveurs en amont ; ne pas utiliser 127.0.0.53. »

Plan B : vous devez préserver précisément le comportement split DNS

  1. Utilisez les résolveurs d’entreprise que resolved rapporte par lien, et incluez les domaines de recherche si nécessaire.
  2. Pour les stacks qui doivent résoudre plusieurs zones différemment, privilégiez des overrides DNS par Compose plutôt qu’un réglage global.
  3. Validez avec resolvectl query sur l’hôte et nslookup dans les conteneurs pour les mêmes noms.

Plan C : mitigation à court terme en attendant la gestion des changements

  1. Lancez les conteneurs critiques avec --dns explicitement pour restaurer la fonctionnalité.
  2. Documentez les services touchés et pourquoi, car sinon cela deviendra une « configuration mystère » en trois mois.
  3. Planifiez le correctif au niveau du daemon pour éviter des configurations permanentes ad hoc.

FAQ

1) Pourquoi 127.0.0.53 fonctionne sur l’hôte mais pas dans les conteneurs ?

Parce que le loopback est spécifique au namespace réseau. Le 127.0.0.53 du conteneur n’est pas le résolveur de l’hôte ; il est à l’intérieur du conteneur.
À moins d’exécuter resolved dans ce conteneur (ne le faites pas), rien n’écoute là‑dessus.

2) Docker n’est-il pas censé utiliser 127.0.0.11 pour le DNS ?

Souvent oui, surtout sur les réseaux définis par l’utilisateur. Mais le DNS embarqué de Docker transmet toujours aux résolveurs en amont.
Si Docker a appris que l’amont était 127.0.0.53 depuis l’hôte, le transfert échouera quand même.

3) Puis-je simplement changer /etc/resolv.conf vers le fichier non-stub ?

Vous pouvez, et cela fonctionne souvent bien sur des serveurs. Sur des ordinateurs portables ou des environnements fortement VPN, cela peut se comporter différemment du mode stub.
Si vous voulez la voie la moins surprenante, configurez explicitement le DNS du daemon Docker.

4) Dois‑je redémarrer Docker après avoir changé les paramètres DNS ?

Oui pour les changements au niveau du daemon. Redémarrez ou recréez aussi les conteneurs afin que leur /etc/resolv.conf soit régénéré avec les nouveaux paramètres.
Sinon vous corrigerez l’hôte et garderez des conteneurs cassés en fonctionnement.

5) Et si mes serveurs DNS sont fournis par DHCP et changent fréquemment ?

Alors hardcoder le DNS dans /etc/docker/daemon.json est fragile à moins de l’automatiser.
Pour des hôtes itinérants, pensez à une petite automation qui met à jour le DNS de Docker depuis /run/systemd/resolve/resolv.conf et redémarre Docker pendant des fenêtres sûres.

6) Pourquoi ne pas définir le DNS de Docker sur un résolveur public et en rester là ?

Parce que le split-horizon DNS existe. Les zones internes n’existent pas publiquement, et certaines organisations servent intentionnellement des réponses différentes
en interne et en externe. Le DNS public peut « fonctionner » jusqu’à ce que vous touchiez quelque chose d’entreprise, puis ça échoue de manière déroutante.

7) Est-ce que cela affecte Kubernetes/containerd aussi ?

Oui, dans l’esprit. Les mécaniques exactes diffèrent (CNI, options resolvConf du kubelet), mais le problème sous-jacent reste :
les conteneurs ont besoin de résolveurs atteignables depuis leur namespace. Les résolveurs stub sur loopback sont un piège récurrent.

8) Je vois des timeouts, pas des échecs immédiats. Est‑ce toujours le problème du stub ?

Parfois. Les échecs immédiats « can’t resolve » pointent souvent vers 127.0.0.53 à l’intérieur du conteneur.
Les timeouts peuvent aussi indiquer des problèmes de pare‑feu/NAT, de routage VPN, de MTU, ou de santé des serveurs en amont. Utilisez le playbook de diagnostic rapide pour classifier le cas.

9) Puis‑je exécuter un cache DNS sur l’hôte et pointer Docker dessus ?

Oui, mais faites‑le délibérément. Si vous exécutez unbound/dnsmasq lié à une adresse non loopback atteignable depuis les réseaux conteneurs,
les conteneurs peuvent l’utiliser. Lier uniquement sur 127.0.0.1 recrée le problème de namespace sous une autre forme.

Conclusion : prochaines étapes que vous pouvez vraiment déployer

La panne récurrente Ubuntu 24.04 + DNS Docker n’est pas une histoire « Docker est cassé ». C’est un décalage prévisible :
systemd-resolved annonce un stub loopback, et Docker (ou vos conteneurs) ne peuvent pas atteindre ce loopback depuis un namespace différent.
Une fois que vous l’avez vu, vous ne pouvez plus l’ignorer.

Étapes concrètes suivantes :

  1. Exécutez le conteneur one-shot et regardez /etc/resolv.conf. Ne devinez pas.
  2. Si vous voyez 127.0.0.53, choisissez un schéma de correction : DNS du daemon (préféré) ou lien resolv.conf vers le fichier non-stub.
  3. Redémarrez Docker dans une fenêtre contrôlée, puis redémarrez/recréez les conteneurs pour récupérer la nouvelle configuration du résolveur.
  4. Validez les domaines publics et internes (si vous avez un split DNS, considérez que vous en avez un).
  5. Notez la décision dans votre runbook pour que la prochaine mise à jour n’enseigne pas la même leçon de la manière dure.

La meilleure correction DNS est celle qui rend à nouveau le DNS ennuyeux. Gardez votre adrénaline pour ce qui la mérite.

← Précédent
Postfix « Relay access denied » : corriger le relais sans créer un relais ouvert
Suivant →
Ubuntu 24.04 PostgreSQL autovacuum « lenteur mystérieuse » : comment le régler en toute sécurité

Laisser un commentaire