La découverte de services Docker échoue : DNS et alias réseau correctement configurés

Cet article vous a aidé ?

Vous déployez une nouvelle version d’un conteneur un vendredi. Rien d’exotique. Même image, même fichier compose, juste un changement de tag.
Puis votre appli commence à enregistrer : « could not resolve db » ou « Name or service not known. » Cinq minutes plus tard, ça « se corrige tout seul ».
Ou pas. Tout le monde convient que c’est le « DNS », qui est le jargon corporate pour « personne ne sait encore. »

La découverte de services Docker est simple si vous restez sur la route goudronnée, et étonnamment étrange si vous mettez un pied dehors.
La bonne nouvelle : la plupart des défaillances sont déterministes. La mauvaise : cette déterminisme est caché derrière des valeurs par défaut comme ndots,
le périmètre des réseaux et le serveur DNS embarqué que vous n’avez jamais demandé.

Un modèle mental qui correspond à la production

Voici le modèle à garder en tête : la découverte de services Docker est un DNS limité au périmètre du réseau.
Les noms ne se résolvent qu’à l’intérieur du même réseau Docker, en utilisant le serveur DNS embarqué de Docker (généralement 127.0.0.11 dans le conteneur),
et le mapping nom→IP est construit à partir des endpoints des conteneurs et des alias réseau. C’est tout. Le reste n’est que conséquences.

Si vous ne retenez qu’une phrase, que ce soit celle-ci : si deux conteneurs ne sont pas sur le même réseau défini par l’utilisateur, ils ne se « voient » pas par nom.
« Mais ils sont sur la même machine ! » n’est pas un modèle réseau ; c’est un appel à l’aide.

Ce à quoi vous pouvez vous attendre que ça marche

  • Sur un réseau bridge défini par l’utilisateur créé par Docker/Compose : les noms de service et les alias réseau se résolvent en adresses IP des conteneurs.
  • Sur le réseau par défaut de Compose : chaque nom de service devient un nom DNS (db, redis, api).
  • En Swarm : les noms de service se résolvent soit en VIP (équilibrage), soit en plusieurs IPs de tâches (mode DNSRR).

Ce que vous devez considérer comme cassé tant que ce n’est pas prouvé

  • La résolution de noms entre des réseaux Docker différents sans connexions explicites.
  • L’utilisation des adresses IP de conteneurs comme identités stables (ce sont des cattle, pas des pets).
  • Se fier aux domaines de recherche et aux noms simples quand ndots et les politiques DNS d’entreprise jouent un rôle.
  • Les fichiers Compose « ça marche sur mon laptop » qui dépendent implicitement des réseaux par défaut et des valeurs amicales.

Une pensée supplémentaire : le DNS Docker n’est pas un serveur DNS général. C’est un registre de noms pour les endpoints de conteneurs avec une couche de transfert.
Traitez-le comme une fonctionnalité de plan de contrôle ayant une dépendance du plan de données. Si vous le gérez comme « juste du DNS », il vous rappellera qui commande.

Idée paraphrasée (attribuée) : Werner Vogels insiste souvent sur l’idée que tout échoue ; concevez pour l’échec plutôt que d’assumer la fiabilité.
C’est aussi la bonne posture pour la découverte de services Docker.

Faits intéressants et un peu d’histoire

Ce ne sont pas des anecdotes de quiz. Elles expliquent pourquoi Docker se comporte ainsi, et pourquoi votre « simple question DNS » se transforme
en une réunion d’incident de deux heures.

  1. Docker s’appuyait à l’origine fortement sur les entrées de /etc/hosts pour la résolution des noms de conteneurs ; la découverte basée sur DNS a mûri plus tard avec l’évolution du réseau.
  2. Le serveur DNS embarqué (127.0.0.11) est par conteneur au sens où il écoute dans chaque namespace réseau du conteneur, soutenu par le moteur Docker.
  3. Les réseaux bridge définis par l’utilisateur ont apporté une vraie découverte de services : le bridge par défaut ne fournissait historiquement pas la résolution automatique comme les réseaux définis par l’utilisateur.
  4. Compose a popularisé « nom de service = nom DNS », ce qui a facilité le développement microservices mais a aussi fait oublier que le DNS est limité par réseau.
  5. Swarm a introduit la découverte par VIP : résoudre un nom de service renvoie souvent une IP virtuelle, pas une IP de tâche, transférant l’équilibrage à la plateforme.
  6. Le comportement du TTL DNS n’est pas une promesse dans les environnements conteneurisés ; les réponses DNS de Docker et les caches côté client peuvent rendre les changements « collants ».
  7. ndots est devenu un fauteur de troubles silencieux à mesure que Kubernetes et les runtimes conteneurs ont augmenté l’usage des domaines de recherche ; Docker hérite du même comportement de résolveur depuis libc.
  8. Les schémas split-horizon DNS d’entreprise entrent en collision avec les conteneurs : ce que votre hôte résout via VPN peut ne pas être atteignable ou résolvable à l’intérieur d’un conteneur sans plomberie supplémentaire.

Comment fonctionne réellement le DNS dans Docker (bridge, compose, swarm)

Le serveur DNS embarqué : pourquoi vous voyez 127.0.0.11

À l’intérieur de la plupart des conteneurs sur des réseaux définis par l’utilisateur, /etc/resolv.conf pointe vers nameserver 127.0.0.11.
Cette adresse n’est pas votre DNS d’entreprise. C’est le stub DNS embarqué de Docker. Ce stub réalise deux tâches :

  • Répond aux requêtes pour les noms de conteneurs et les alias sur le même réseau.
  • Transfère les autres requêtes vers les résolveurs en amont configurés pour le démon Docker (souvent copiés depuis l’hôte).

Cela a de l’importance car lorsque le DNS en amont échoue, vous verrez des échecs même pour des noms internes si la bibliothèque résolveur se confond
(timeouts, retransmissions, expansions de domaines de recherche). Cela importe aussi parce qu’un conteneur peut avoir des serveurs DNS en amont différents de l’hôte.

Réseaux bridge définis par l’utilisateur : le défaut raisonnable

Créez un réseau, attachez-y des conteneurs, et Docker enregistrera leurs noms dans l’espace de noms DNS de ce réseau.
Compose le fait pour vous en créant un réseau scoped par projet. C’est pourquoi db se résout dans Compose sans que vous fassiez quoi que ce soit de malin.

Le bridge par défaut est une commodité legacy. Il s’est amélioré, mais il reste dangereux comparé aux réseaux définis par l’utilisateur.
Si vous tenez à une découverte de services prédictible, arrêtez d’utiliser le bridge par défaut pour des applications multi-conteneurs.

Réseaux overlay (Swarm) : la zone « ça dépend »

En Swarm, la découverte de services est intégrée à l’orchestrateur. Vous obtenez typiquement l’un des deux modes :

  • Mode VIP : le nom de service se résout en une IP virtuelle ; le swarm route vers les tâches. Idéal pour la simplicité ; parfois déroutant pour le débogage.
  • Mode DNSRR : le nom de service renvoie plusieurs enregistrements A (IPs de tâches). Idéal pour l’équilibrage côté client ; facile à mal utiliser.

Les overlays ajoutent des pièces mobiles : trafic de gossip/plan de contrôle, mesh d’ingress, et gestion DNS par nœud. Quand la découverte échoue ici,
« DNS » peut en réalité être un problème du plan de contrôle overlay. Votre approche diagnostique devrait en tenir compte.

Ce que le DNS Docker n’est pas

  • Ce n’est pas un serveur DNS autoritatif complet pour votre domaine d’entreprise.
  • Ce n’est pas un service mesh.
  • Ce n’est pas une promesse que le comportement du résolveur de votre appli est sain.

Blague #1 : DNS signifie « Did Not Sleep », et Docker s’assurera que vous méritez l’acronyme si vous ignorez les réglages du résolveur.

Noms de service, noms de conteneur, hostnames et alias réseau

Les gens confondent ces termes comme s’ils étaient synonymes. Ils ne le sont pas, et la différence est la différence entre « ça marche » et « échoue mystérieusement en prod ».

Nom de service (Compose)

Dans Docker Compose, le nom de service (la clé sous services:) devient un nom DNS sur le réseau Compose.
C’est pourquoi ceci fonctionne :

  • api peut se connecter à db:5432 si les deux sont sur le même réseau Compose.

Compose ajoute aussi un scoping de projet en arrière-plan, mais les noms DNS sont généralement le nom du service, pas le nom complet du conteneur.
Sauf si vous surchargez les choses. Et les gens le font.

Nom du conteneur

Le nom du conteneur est ce que vous voyez dans docker ps. Compose le génère souvent comme project-service-1.
Vous pouvez définir container_name, mais cela tend à créer plus de problèmes qu’autre chose :
cela casse le scaling et encourage des dépendances fragiles sur une identité spécifique.

Hostname

Le hostname d’un conteneur est ce que retourne hostname à l’intérieur du conteneur. Il peut influencer la façon dont certains logiciels s’identifient,
mais ce n’est pas la même chose qu’un enregistrement DNS. Définir hostname: dans Compose ne crée pas automatiquement un nom DNS stable à travers les réseaux.

Alias réseau (l’outil que vous voulez réellement)

Un alias réseau est un nom DNS associé à un endpoint de conteneur sur un réseau spécifique.
Les alias sont scoped par réseau. C’est le but. Vous pouvez donner au même conteneur des alias différents sur des réseaux différents.

Utilisez des alias quand :

  • Vous voulez un nom stable et « bien connu » comme db tout en remplaçant l’implémentation (postgres vs cockroach).
  • Vous avez besoin de plusieurs noms pour le même service pour des migrations (db et postgres-primary).
  • Vous connectez un conteneur à plusieurs réseaux et voulez contrôler les noms visibles sur chacun.

Évitez les alias quand :

  • Vous les utilisez pour masquer une mauvaise conception réseau (« aliasons-le juste pour que ça se résolve »).
  • Vous construisez un pseudo-espace de noms global. Les réseaux Docker sont faits pour être des frontières.

Un mot sur « localhost »

À l’intérieur d’un conteneur, localhost est le conteneur lui-même. Pas l’hôte. Pas l’autre conteneur. Pas vos sentiments.
Si votre appli utilise localhost pour atteindre une dépendance, elle échouera à moins que cette dépendance ne soit dans le même conteneur.
Séparez le processus, et vous devez changer l’adresse.

Blague #2 : « Ça marchait en localhost » est l’équivalent conteneur de « le chèque est dans la boîte ».

Feuille de diagnostic rapide

Quand la découverte de services échoue, vous n’avez pas le temps pour une danse interprétative. Vous avez besoin d’une séquence qui trouve le goulot rapidement.
Cette feuille suppose que vous déboguez depuis l’hôte Docker avec un accès CLI.

Première étape : confirmer que c’est un problème de nom, pas de TCP

  • Si la résolution du nom échoue : vous êtes en territoire DNS.
  • Si la résolution fonctionne mais la connexion échoue : vous êtes en territoire routage/pare-feu/service à l’écoute.

Deuxième : vérifier que les deux conteneurs partagent un réseau défini par l’utilisateur

  • Si ils ne partagent pas de réseau, aucun réglage d’alias ne vous aidera.
  • Si ils le font, inspectez le réseau et les endpoints pour les alias et les IPs.

Troisième : inspecter la configuration du résolveur du conteneur client

  • Vérifiez /etc/resolv.conf pour 127.0.0.11, les domaines de recherche et options ndots.
  • Vérifiez si le conteneur peut atteindre le DNS en amont (si le nom est externe).

Quatrième : reproduire la résolution avec des outils déterministes

  • Utilisez getent hosts pour reproduire le comportement libc.
  • Utilisez dig/nslookup pour voir les réponses DNS brutes (si installés).

Cinquième : vérifier l’état du moteur Docker et des objets réseau

  • Cherchez des anomalies : endpoints obsolètes, réseaux orphelins, redémarrages du démon, ou des « aides » qui remplacent le DNS.

Tâches pratiques : commandes, sorties et décisions

Voici les tâches de terrain que j’exécute réellement. Chacune inclut ce que la sortie signifie et quelle décision prendre ensuite.
Exécutez-les dans l’ordre quand vous êtes sous pression.

Tâche 1 : Confirmer sur quels réseaux est le conteneur client

cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' api | jq
{
  "app_net": {
    "IPAMConfig": null,
    "Links": null,
    "Aliases": [
      "api",
      "3d2c9a8c4c1a"
    ],
    "MacAddress": "02:42:ac:14:00:05",
    "DriverOpts": null,
    "NetworkID": "c5f2e4b8d0f1...",
    "EndpointID": "4c0f2a7c2e0b...",
    "Gateway": "172.20.0.1",
    "IPAddress": "172.20.0.5",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "DNSNames": [
      "api",
      "3d2c9a8c4c1a"
    ]
  }
}

Signification : Le conteneur api est attaché à app_net et possède les noms DNS api plus son ID de conteneur.

Décision : Inspectez si le conteneur cible est aussi sur app_net. Sinon, corrigez l’attachement réseau, pas le DNS.

Tâche 2 : Confirmer que le conteneur cible partage le même réseau

cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' db | jq
{
  "app_net": {
    "IPAMConfig": null,
    "Links": null,
    "Aliases": [
      "db",
      "postgres",
      "a9b1c2d3e4f5"
    ],
    "NetworkID": "c5f2e4b8d0f1...",
    "EndpointID": "d1a6b9aa0f2c...",
    "Gateway": "172.20.0.1",
    "IPAddress": "172.20.0.10",
    "IPPrefixLen": 16
  }
}

Signification : Les deux conteneurs partagent app_net. Le DNS devrait fonctionner pour db et l’alias postgres.

Décision : Passez aux vérifications du résolveur à l’intérieur du conteneur client.

Tâche 3 : Vérifier la config du résolveur à l’intérieur du conteneur client

cr0x@server:~$ docker exec -it api cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
search corp.example

Signification : Le conteneur utilise le DNS embarqué de Docker. ndots:0 signifie que même des noms à label unique comme db sont traités d’abord comme « absolus ».

Décision : Si vous voyez ndots:5 et une longue liste de recherche, attendez-vous à des délais et des requêtes externes étranges ; envisagez d’ajuster ou d’utiliser des FQDN pour les noms externes.

Tâche 4 : Reproduire la résolution en imitant libc

cr0x@server:~$ docker exec -it api getent hosts db
172.20.0.10     db

Signification : La résolution de noms via libc réussit. Si votre application affirme toujours « cannot resolve », suspectez la mise en cache DNS de l’application, une bibliothèque résolveur différente, ou un mauvais hostname.

Décision : Si getent échoue, c’est un vrai problème de chemin DNS/résolveur — continuez avec des outils DNS bruts.

Tâche 5 : Interroger directement le DNS Docker (requête brute)

cr0x@server:~$ docker exec -it api sh -lc 'apk add --no-cache bind-tools >/dev/null 2>&1; dig @127.0.0.11 db +short'
172.20.0.10

Signification : Le DNS Docker renvoie l’enregistrement A attendu.

Décision : Si cela fonctionne mais que l’appli échoue, vous avez affaire à la mise en cache DNS de l’application, des conditions de course au démarrage, ou une connexion au mauvais nom réseau.

Tâche 6 : Vérifier si l’appli échoue à cause d’une préférence IPv6

cr0x@server:~$ docker exec -it api sh -lc 'getent ahosts db | head -n 5'
172.20.0.10      STREAM db
172.20.0.10      DGRAM
172.20.0.10      RAW

Signification : Seulement des réponses IPv4. Si votre appli tente d’abord IPv6 et que votre DNS renvoie des enregistrements AAAA ailleurs, vous pouvez avoir un « hang puis fallback ».

Décision : Si vous voyez des AAAA mais pas de routage IPv6, corrigez la configuration IPv6 ou forcez IPv4 côté client.

Tâche 7 : Confirmer que le réseau a les bons endpoints et alias

cr0x@server:~$ docker network inspect app_net | jq '.[0] | {Name, Driver, Containers}'
{
  "Name": "app_net",
  "Driver": "bridge",
  "Containers": {
    "3d2c9a8c4c1a...": {
      "Name": "api",
      "IPv4Address": "172.20.0.5/16",
      "IPv6Address": ""
    },
    "a9b1c2d3e4f5...": {
      "Name": "db",
      "IPv4Address": "172.20.0.10/16",
      "IPv6Address": ""
    }
  }
}

Signification : Le réseau voit les deux endpoints. Si le conteneur cible n’est pas listé, le DNS ne peut pas le résoudre sur ce réseau.

Décision : Attachez le conteneur au réseau ou corrigez la configuration Compose.

Tâche 8 : Vérifier que Compose a créé le réseau que vous pensez

cr0x@server:~$ docker compose ls
NAME            STATUS
payments        running(6)

cr0x@server:~$ docker network ls | grep payments
c5f2e4b8d0f1   payments_default   bridge    local

Signification : Compose a créé payments_default. Si vos conteneurs sont sur un autre réseau, vous exécutez peut-être plusieurs projets ou mélangez des exécutions manuelles.

Décision : Standardisez : lancez tout via Compose (ou votre orchestrateur), pas en mode semi-manuel.

Tâche 9 : Prouver si un conteneur est accidentellement sur le bridge par défaut

cr0x@server:~$ docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' legacy_worker
bridge 

Signification : Ce conteneur est seulement sur le réseau bridge par défaut. Il ne résoudra pas les noms depuis votre réseau Compose défini par l’utilisateur.

Décision : Déplacez-le vers un réseau défini par l’utilisateur ; cessez d’attendre une découverte de noms entre réseaux.

Tâche 10 : Attacher un conteneur en cours d’exécution au bon réseau (correctif à chaud)

cr0x@server:~$ docker network connect payments_default legacy_worker
cr0x@server:~$ docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' legacy_worker
bridge payments_default 

Signification : Maintenant le conteneur partage le réseau Compose et devrait résoudre les noms de service dessus.

Décision : Traitez cela comme un palliatif. Corrigez le fichier Compose ou le déploiement pour qu’il démarre correctement attaché la prochaine fois.

Tâche 11 : Valider que le nom utilisé est réellement enregistré comme alias

cr0x@server:~$ docker inspect -f '{{json (index .NetworkSettings.Networks "payments_default").Aliases}}' db | jq
[
  "db",
  "postgres",
  "a9b1c2d3e4f5"
]

Signification : L’alias postgres est réel sur ce réseau.

Décision : Si votre appli se connecte à postgresql et que cet alias n’est pas présent, ajoutez l’alias ou mettez à jour la config de l’appli. Ne devinez pas.

Tâche 12 : Détecter l’expansion par domaines de recherche provoquant des résolutions lentes

cr0x@server:~$ docker exec -it api sh -lc 'cat /etc/resolv.conf; echo; time getent hosts db >/dev/null'
nameserver 127.0.0.11
options ndots:5
search corp.example svc.corp.example cloud.corp.example

real    0m2.013s
user    0m0.000s
sys     0m0.003s

Signification : Une recherche de deux secondes pour un nom local est un signe. Avec ndots:5, le résolveur tente d’abord d’ajouter des domaines de recherche. Cela peut provoquer des timeouts avant d’interroger db tel quel.

Décision : Pour les noms Docker internes, préférez des noms à label unique avec ndots:0 quand c’est approprié, ou ajoutez un point final explicite (db.) dans les clients qui le supportent. Autrement, réduisez les domaines de recherche pour cette charge de travail.

Tâche 13 : Confirmer la configuration DNS du démon (côté hôte)

cr0x@server:~$ docker info | sed -n '/DNS:/,/Registry Mirrors:/p'
DNS: 10.10.0.53
  10.10.0.54
Registry Mirrors:

Signification : Le démon Docker transfère les requêtes non-containers vers ces serveurs en amont.

Décision : Si les résolutions externes échouent dans les conteneurs mais fonctionnent sur l’hôte, comparez les résolveurs en amont ; vous devrez peut-être configurer explicitement /etc/docker/daemon.json.

Tâche 14 : Vérifier la connectivité vers le service cible après succès DNS

cr0x@server:~$ docker exec -it api sh -lc 'apk add --no-cache busybox-extras >/dev/null 2>&1; nc -vz db 5432'
db (172.20.0.10:5432) open

Signification : DNS et connectivité TCP sont bonnes. Si l’appli retourne encore une erreur, vous regardez TLS, identifiants, incompatibilité de protocole, ou config applicative.

Décision : Arrêtez de blâmer le DNS. Montez dans la pile.

Tâche 15 : Sanity check en mode Swarm (VIP vs DNSRR)

cr0x@server:~$ docker service inspect payments_api --format '{{json .Endpoint.Spec.Mode}}'
"vip"

Signification : En Swarm, le service se résout en VIP. Vous verrez une IP pour le nom de service, pas des IPs par tâche.

Décision : Si votre client attend plusieurs enregistrements A pour l’équilibrage, passez en DNSRR ou adaptez le client pour se connecter à la VIP.

Trois mini-récits d’entreprise depuis les tranchées DNS

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

Une entreprise de taille moyenne exploitait une API de paiements sur un hôte Docker unique pour une gamme de produits legacy. C’était « temporaire » depuis un an,
ce qui est l’unité de temps standard en architecture d’entreprise.
Ils utilisaient Compose, avec un service api et un service db, plus un conteneur « migration » ponctuel lancé manuellement lors des déploiements.

Un jour, le job de migration a commencé à échouer avec « could not translate host name ‘db’ to address. »
L’ingénieur en astreinte a vérifié la pile Compose : db était up, healthy, les logs semblaient corrects.
Ils ont redémarré la base de données quand même, par tradition. L’échec a persisté.

La mauvaise hypothèse était subtile : ils croyaient que « les conteneurs sur la même machine peuvent se résoudre par nom. »
Le conteneur de migration avait été démarré avec docker run et avait atterri sur le bridge par défaut.
Les services Compose étaient sur project_default. Deux univers séparés. Même hôte, réseaux différents, aucune relation DNS.

La correction était ennuyeuse : exécuter les migrations comme un service Compose attaché au même réseau, ou attacher explicitement le conteneur ponctuel au réseau Compose.
Après cela, l’équipe a écrit un petit wrapper de déploiement qui refusait de lancer des conteneurs ad-hoc sans --network.
C’était légèrement agaçant pour les développeurs, ce qui est le signe que ça a marché.

Mini-récit 2 : L’optimisation qui a mal tourné

Une autre organisation avait une API interne sensible à la latence. Quelqu’un a remarqué des lenteurs occasionnelles d’1–2 secondes au démarrage quand les services tentaient d’atteindre des dépendances.
Un ingénieur bien intentionné a conclu, à juste titre, que les retries DNS et l’expansion des domaines de recherche étaient en cause.
Leur « optimisation » a été de coder en dur les adresses IP des dépendances dans des variables d’environnement.

Pendant une semaine, tout semblait parfait. Le démarrage était plus rapide, les graphiques plus calmes. Puis l’incident est arrivé : un redeploy a mélangé les IPs des conteneurs.
Un service continuait d’essayer de se connecter à l’ancienne IP, et comme l’IP appartenait maintenant à autre chose, le mode d’échec n’était pas « connection refused. »
C’était « connecté au mauvais service », suivi d’erreurs d’authentification ressemblant à une dérive de credentials.

La panne n’était pas catastrophique, mais elle fut moche : pannes partielles, logs confus, et un rollback qui ne restaurait pas la sanity parce que
la configuration « optimisée » vivait dans un template CI partagé.
Le postmortem fut poli et profondément inconfortable.

La vraie correction fut d’aborder le comportement DNS, pas de le contourner : réduire le bruit des domaines de recherche, utiliser des alias réseau, et implémenter des retries avec jitter
côté application pour la readiness des dépendances. Les IPs redevinrent éphémères, comme il se doit.

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

Une grande entreprise exécutait plusieurs projets Compose sur des hôtes partagés pendant une phase de migration.
C’était désordonné, mais l’équipe SRE imposait une règle : chaque projet définit des réseaux explicites avec des noms explicites, et chaque dépendance inter-projet
utilise un réseau « partagé » dédié avec des alias contrôlés.

Ils imposaient aussi une convention de nommage : les connexions service-à-service doivent utiliser un nom DNS qui est soit le nom du service Compose
sur le réseau local, soit un alias réseau sur le réseau partagé. Pas de noms de conteneur. Pas d’IPs. Pas de « ce qui se résout ».
Ça paraissait pédant et ralentissait quelques hacks rapides.

Puis un conteneur fournisseur est arrivé avec l’attente codée pour atteindre license-server.
Sans la convention, les équipes auraient renommé des services, ajouté des extra_hosts aléatoires, ou commencé à éditer des images.
À la place, elles ont attaché le conteneur fournisseur au réseau partagé et donné au service réel un alias réseau license-server.

L’intégration fournisseur a fonctionné du premier coup en staging et production. Personne n’a célébré parce que c’était ennuyeux.
C’est le plus grand compliment qu’on puisse faire au travail d’exploitation.

Erreurs courantes : symptôme → cause racine → correction

1) « Name or service not known » depuis un conteneur, mais ça marche depuis un autre

Symptôme : api peut résoudre db, mais worker ne peut pas.

Cause racine : Les conteneurs sont sur des réseaux différents (souvent un sur bridge par défaut).

Correction : Attachez les deux au même réseau défini par l’utilisateur, ou connectez worker au réseau Compose. Préférez des réseaux explicites dans Compose.

2) « Temporary failure in name resolution » qui disparaît après un redémarrage

Symptôme : Le démarrage initial échoue ; un redémarrage « règle » le problème.

Cause racine : Ordonnancement au démarrage plus retries du résolveur plus dépendance non prête ; parfois aussi DNS lent à cause des domaines de recherche.

Correction : Ajoutez des retries applicatifs avec backoff ; utilisez des healthchecks et des patterns wait-for ; réduisez le bruit des domaines de recherche ou réglez ndots de façon appropriée.

3) Le nom du service se résout en IP, mais les connexions restent bloquées ou échouent

Symptôme : getent hosts db marche ; nc -vz db 5432 échoue.

Cause racine : Service pas à l’écoute, mauvais port, règles de pare-feu, ou le conteneur n’est pas joignable à cause de routing/politiques iptables.

Correction : Vérifiez les sockets à l’écoute (ss -lntp sur la cible), les logs du conteneur, et les règles iptables/nft de l’hôte Docker. Le DNS n’est plus votre problème.

4) Domaines externes résolus sur l’hôte mais pas dans les conteneurs

Symptôme : L’hôte peut résoudre corp-internal ; le conteneur ne peut pas.

Cause racine : Le démon Docker utilise des serveurs DNS en amont différents du résolveur VPN de l’hôte ; ou les routes VPN ne sont pas accessibles depuis les conteneurs.

Correction : Configurez le DNS du démon explicitement ; assurez-vous que le DNS et les routes VPN sont accessibles depuis les conteneurs (parfois nécessite d’exécuter le VPN sur l’hôte de façon que les conteneurs puissent l’utiliser).

5) « Ça marchait jusqu’à ce qu’on scale à 2 replicas »

Symptôme : Après passage à l’échelle, les connexions vont vers le mauvais back-end ou deviennent incohérentes.

Cause racine : Mauvaise utilisation de container_name ou dépendance sur une identité unique ; en Swarm, mauvaise compréhension VIP vs DNSRR.

Correction : Supprimez container_name dans les services scalables ; utilisez les noms de service ; choisissez VIP ou DNSRR intentionnellement.

6) Collision de noms entre projets

Symptôme : db se résout, mais vers la « autre » base de données.

Cause racine : Réseau partagé avec alias qui se chevauchent ou plusieurs stacks attachées au même réseau avec un nommage casual.

Correction : Utilisez des alias spécifiques au projet (payments-db), ou isolez les réseaux et ne partagez que via un réseau dédié et contrôlé.

7) Recherches lentes pour des noms à label unique

Symptôme : Chaque recherche interne coûte ~1–5 secondes.

Cause racine : ndots trop élevé plus longue liste de recherche provoquant plusieurs requêtes échouées avant d’essayer le nom nu.

Correction : Réduisez ndots pour la charge, raccourcissez les domaines de recherche, ou utilisez un point final où c’est supporté.

8) « On a ajouté extra_hosts et maintenant tout est bizarre »

Symptôme : Un service se connecte de façon intermittente à d’anciens endpoints après des redeploys.

Cause racine : extra_hosts fige les noms sur des IPs fixes, contournant les mises à jour du DNS Docker.

Correction : Supprimez extra_hosts pour les services internes ; utilisez des alias réseau et des réseaux corrects. N’utilisez extra_hosts que pour les cas spéciaux que vous êtes prêt à gérer indéfiniment.

Checklists / plan étape par étape

Checklist : concevoir une découverte de services Docker qui ne vous réveillera pas la nuit

  1. Utilisez des réseaux définis par l’utilisateur partout. Dans Compose, définissez-les explicitement ; ne comptez pas sur le bridge par défaut.
  2. Gardez la découverte de services limitée au réseau. Traitez les réseaux comme des frontières de confiance et des domaines de défaillance.
  3. Utilisez des alias réseau pour des noms d’interface stables. Surtout durant les migrations et intégrations fournisseurs.
  4. Ne codez pas en dur les adresses IP des conteneurs. Si vous pensez en avoir besoin, vous avez en réalité besoin d’un nom stable ou d’une autre architecture.
  5. Évitez container_name pour les services scalables. Cela casse le scaling et encourage des dépendances fragiles.
  6. Rendez le démarrage résilient. Le fait que le DNS soit « up » ne signifie pas que la dépendance est prête. Implémentez des retries avec jitter et des timeouts raisonnables.
  7. Contrôlez le comportement du résolveur. Surveillez ndots et les domaines de recherche ; alignez-les sur votre stratégie de nommage.
  8. Séparez les préoccupations de nommage interne et externe. Services internes : noms courts/alias. Dépendances externes : FQDN.
  9. Décidez du mode Swarm consciemment. VIP pour la simplicité ; DNSRR si vos clients savent gérer plusieurs enregistrements A.
  10. Rédigez des runbooks avec des commandes. Si vos étapes de diagnostic vivent uniquement dans la tête de quelqu’un, elles n’existent pas.

Étapes : migrer de « noms aléatoires » vers des alias sensés

  1. Choisissez un nom canonique de dépendance par service (db, cache, queue).
  2. Implémentez des alias réseau sur le réseau applicatif interne pour ces noms.
  3. Modifiez les applications pour n’utiliser que ces noms (pas de noms de conteneur, pas d’IPs).
  4. Déployez service par service ; gardez des alias doubles temporaires pour compatibilité.
  5. Supprimez les alias dépréciés après un cycle complet de déploiement et une fenêtre de rollback.

Étapes : un flux de debug discipliné lors d’un incident

  1. Exécutez getent hosts à l’intérieur du conteneur en échec pour le nom de dépendance.
  2. Si ça échoue, inspectez les réseaux des conteneurs et les réseaux partagés.
  3. Inspectez /etc/resolv.conf pour ndots et les domaines de recherche.
  4. Utilisez dig @127.0.0.11 (si possible) pour valider le comportement du DNS embarqué.
  5. Si le DNS marche, testez la connectivité TCP avec nc -vz.
  6. Si le TCP marche, arrêtez. Ce n’est plus un problème de DNS. Passez au TLS/config/app/auth.

FAQ

1) Pourquoi mes conteneurs utilisent-ils nameserver 127.0.0.11 ?

C’est le stub DNS embarqué de Docker dans le namespace du conteneur. Il résout les noms/alias des conteneurs sur les réseaux Docker et transfère les autres requêtes en amont.

2) Pourquoi la découverte fonctionne en Compose mais pas avec docker run ?

Compose attache les services à un réseau de projet défini par l’utilisateur où la découverte basée DNS est activée. Un simple docker run se retrouve souvent sur le bridge par défaut
sauf si vous précisez --network. Réseau différent = espace de noms DNS différent.

3) Est-ce que container_name est une bonne façon d’obtenir des noms DNS stables ?

Non. Ça rend le scaling douloureux et encourage le couplage. Utilisez les noms de service et les alias réseau ; ils expriment l’intention sans figer l’identité sur un conteneur unique.

4) En quoi les alias réseau diffèrent-ils des hostnames ?

Le hostname est l’identité locale à l’intérieur du conteneur. Un alias réseau est un nom DNS enregistré sur un réseau Docker spécifique pour cet endpoint.
Les alias sont ce que les autres conteneurs peuvent résoudre.

5) Pourquoi les résolutions prennent-elles parfois des secondes ?

Cause courante : ndots combiné avec les domaines de recherche. Un nom à label unique comme db peut être essayé comme db.corp.example,
db.svc.corp.example, etc., avec des timeouts, avant d’essayer db directement.

6) Dois-je utiliser des FQDN pour les services Docker internes ?

Généralement non. Utilisez des noms courts de service et des alias dans le réseau. Utilisez des FQDN pour les services externes, surtout à travers VPNs et DNS d’entreprise.
Mélanger les deux augmente la complexité du résolveur et les modes d’échec.

7) En Swarm, pourquoi mon nom de service se résout à une seule IP malgré plusieurs réplicas ?

Vous êtes probablement en mode VIP. Le nom de service se résout en une IP virtuelle ; Swarm gère l’équilibrage. Si vous voulez plusieurs enregistrements A, utilisez DNSRR
et assurez-vous que votre client sait les gérer.

8) Est-il sûr d’utiliser extra_hosts pour « corriger le DNS » ?

Seulement si vous êtes prêt à gérer ce mapping à long terme. extra_hosts fige les noms sur des IPs et contourne la découverte dynamique.
C’est acceptable pour un endpoint réellement statique ; c’est un piège pour des services internes.

9) Pourquoi mon appli échoue le DNS mais getent hosts fonctionne ?

Votre appli peut utiliser une bibliothèque résolveur différente, mettre en cache agressivement, préférer IPv6, ou avoir son propre client DNS avec des timeouts différents.
Confirmez la pile de résolveur utilisée par le runtime (glibc vs musl vs custom) et testez avec des outils équivalents.

10) Deux réseaux peuvent-ils partager le même alias en toute sécurité ?

Oui, parce que les alias sont scoped par réseau. Cela devient dangereux quand vous attachez un conteneur à plusieurs réseaux puis supposez que le nom se résout de la même façon partout.
Soyez explicite sur le réseau sur lequel se trouve le client.

Conclusion : prochaines étapes à livrer

La découverte de services Docker échoue pour des raisons prévisibles : les conteneurs ne sont pas sur le même réseau, les alias ne sont pas ceux que vous pensez,
ou le résolveur fait exactement ce que vous avez configuré (ou hérité) et vous ne l’avez juste pas lu.
La réparation consiste surtout à choisir un modèle et à l’appliquer.

Prochaines étapes pratiques

  1. Auditez les réseaux : listez les conteneurs qui utilisent encore le bridge par défaut et migrez-les vers des réseaux définis par l’utilisateur.
  2. Standardisez les noms : choisissez des noms de service et ajoutez des alias réseau pour des identités de dépendance stables.
  3. Tuez les configs IP : supprimez les adresses IP des dépendances internes et les hacks extra_hosts sauf si elles sont vraiment statiques.
  4. Instrumentez le démarrage : ajoutez des retries avec jitter et des timeouts bornés ; considérez la réussite DNS comme nécessaire mais pas suffisante.
  5. Rédigez le runbook : copiez la feuille de diagnostic rapide et la section tâches dans vos docs d’astreinte, puis tenez-les à jour.

Si vous faites ces cinq choses, la plupart des « incidents DNS Docker » cessent d’être des incidents. Ils deviennent un diagnostic de cinq minutes et une correction d’une ligne.
Ce que mérite le DNS : une compétence discrète, pas du drame.

← Précédent
Pagination vs défilement infini : modèles d’interface qui n’énervent pas les utilisateurs
Suivant →
MariaDB vs PostgreSQL : Stockage en conteneur — vérité sur les performances Bind Mount vs Volume

Laisser un commentaire