Le driver de log qui empêchait le conteneur de démarrer
Le studio où tournait ce prototype avait un défaut d’alimentation que rien, de mon côté, ne pouvait corriger: des micro-coupures à répétition, une salle serveur partiellement ondulée et un hôte qui tombait et redémarrait sans arrêt propre du guest. Un matin de coupures en rafale, la moitié de la stack n’est pas revenue. L’histoire mérite d’être racontée parce que le coupable évident, l’électricité, n’était que le déclencheur et que la vraie faute se cachait dans une ligne de configuration que tout le monde aurait validée.
Le symptôme
Au redémarrage, la stack réseau de base repart normalement, le reverse proxy aussi, mais la stack de provisioning reste entièrement à 0/1 et l’observabilité à moitié.
Un coup d’oeil au périmètre et le contraste saute aux yeux: tout ce qui dépend du DNS interne est par terre, tout ce qui héberge le DNS tient debout.
[OK] net_pdns-auth: 1/1
[OK] net_pdns-recursor: 1/1
[WARN] foreman_webapp: 0/1
[WARN] obs_grafana: 0/1
[OK] obs_victorialogs: 1/1
L’erreur réelle, non tronquée, désigne le driver de log, pas l’application:
starting container failed: failed to initialize logging driver:
dial udp: lookup victorialogs on 172.27.65.202:53: server misbehaving
Le daemon Docker, en initialisant le driver syslog du conteneur, essaie de résoudre victorialogs.devops.lab, n’y arrive pas et comme il ne peut pas initialiser le driver, il échoue la création de la tâche.
Le conteneur ne démarre pas, non pas à cause de l’application, mais parce que son journal n’a pas de destination résolvable.
Le mécanisme
L’arrêt sale avait laissé l’overlay réseau du Swarm dans un état incohérent.
Le recursor PowerDNS n’atteignait plus son autorité, pdns-auth, qui tournait pourtant et servait sur son port: c’est le lien vers elle qui était rompu et dig le confirmait d’un SERVFAIL assorti d’un EDE 22 (No Reachable Authority).
Résultat, *.devops.lab ne résolvait plus et les serveurs de repli de l’hôte étaient les DNS d’entreprise, qui ignorent ce domaine interne: la résolution échouait complètement.
Le détail qui transforme ce raté transitoire en panne, c’est le couplage.
La stack réseau, elle, survivait et pour une bonne raison: son propre driver de log pointait sur une IP, pas sur un nom, parce qu’elle héberge le DNS et se déploie en premier, donc elle ne peut pas résoudre un nom pour journaliser, ce serait une dépendance circulaire.
Les autres stacks, déployées ensuite, utilisaient volontairement le nom de service victorialogs, l’écriture portable et pensée pour le multinode.
Ce contraste, que je n’avais pas prémédité, a été l’expérience naturelle qui a confirmé la cause: le seul driver qui ne faisait aucun lookup était le seul à survivre.
Au passage, j’ai écarté les fausses pistes dans l’ordre.
Pas de corruption de volume, le down/up réutilise les mêmes volumes et repart.
Pas de recovery MySQL en cause, le XA crash recovery bouclait en quelques millisecondes et la base était prête aussitôt.
La corruption n’était pas globale, seul l’overlay réseau était touché.
Ce qui ne répare pas et ce qui répare
La leçon la plus contre-intuitive de l’incident tient dans ces trois commandes qui ne changent rien:
docker service update --force net_pdns-recursor # dig: toujours SERVFAIL
docker service update --force net_pdns-auth # dig: toujours SERVFAIL
sudo systemctl restart docker # dig: toujours SERVFAIL
Recréer les tâches une par une ou redémarrer le daemon, ne suffit pas: l’état fautif n’est pas porté par le sandbox runtime que le restart reconstruit, il est porté par le service-discovery de l’overlay, persisté dans le raft du Swarm et il survit à tout ça. Ce qui le purge, c’est de détacher tous les services de l’overlay et de le redéployer:
make down STACK=net && make up STACK=net
Quelques secondes de patience, le recursor n’écoute pas immédiatement, puis *.devops.lab résout de nouveau.
La vraie faute
Le correctif de déblocage était évident une fois la cause comprise: aligner les autres stacks sur ce que faisait déjà la stack réseau, pointer leur driver de log sur une IP plutôt que sur un nom.
Avec une IP et de l’UDP, l’init du driver ne fait aucun lookup et ne demande aucune réponse, les conteneurs démarrent même avec le DNS interne par terre et les logs de la fenêtre de panne sont perdus, ce que le mode non bloquant acceptait déjà.
Validé en conditions réelles: les stacks sont remontées à pleine santé alors que *.devops.lab renvoyait toujours SERVFAIL.
Mais figer une IP à la place d’un nom de service, c’est renoncer à la portabilité multinode, donc c’est un déblocage de prototype, pas une cible d’architecture. Et surtout, ce n’était pas la vraie faute. La vraie faute, celle qui transforme un overlay qui encaisse mal un arrêt sale en panne de service, c’est l’architecture DNS interne. Faute d’accès en écriture au DNS du groupe, le projet avait inventé un domaine de lab et l’auto-hébergeait dans le Swarm qu’il desservait, l’hôte se pointant sur lui-même comme resolver. Choix pragmatique et légitime dans un prototype, mais qui ne laisse aucun chemin de secours: les noms internes n’existent que dans ce PowerDNS, donc la moindre perturbation de ce dernier coupe toute résolution et le couplage du driver de log la transforme en échec de démarrage.
La faute n’était pas où on croyait
L’incident n’est pas la coupure électrique, qui n’est que le déclencheur, récurrent et bien réel; la fragilité qui en fait une panne vient de l’architecture. Un reboot propre ne reproduit jamais le problème, seul l’arrêt sale le déclenche, donc l’onduleur traite le déclencheur et laisse la dette intacte. Un vrai DNS, une zone ou une délégation servie par une infrastructure indépendante du Swarm, aurait fourni le chemin de secours qui manquait et la perturbation de l’instance interne n’aurait cascadé en rien. Et un nom de service dans un driver de log reste la bonne écriture, portable et multinode; le figer en IP débloque le mononœud mais couple la configuration à un hôte, ce qui veut dire qu’un correctif de déblocage n’est jamais une décision d’architecture, juste une rustine qu’on s’autorise en sachant ce qu’elle coûte.
La suite durable, celle que j’ai mise au backlog, découple la collecte de logs du cycle de vie des conteneurs: un collecteur latéral en service global qui lit les logs localement sur chaque nœud et les pousse par l’overlay, sans resolver hôte ni IP figée, ce qui supprime d’un coup le couplage au DNS et la limite mononœud.