fix(network) : un /64 de conteneurs par nœud, pour que la réponse revienne au bon #38
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/per-node-container-prefix"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Le défaut
Les deux nœuds d'un site partageaient un seul
/64de services et l'annonçaient tousles deux. Un conteneur qui interrogeait une destination DN42 externe voyait donc sa réponse
revenir par le pair que le lointain avait choisi — une fois sur deux le nœud voisin,
qui n'a aucune entrée de conntrack pour un flux parti d'à côté et la jetait sur
drop "DN42 does not reach containers".La règle de sortie portait l'hypothèse fausse dans son propre commentaire : « le retour
revient en
established, accepté tout en haut de la chaîne ». Vrai seulement si le retourrevient sur le même nœud. Une hypothèse d'état écrite au singulier là où il y a deux
machines.
En production : deux résolveurs anycast sur quatre ne résolvaient plus le moindre nom
DN42 externe, tout en annonçant leur VIP. Par destination et intermittent, donc invisible.
Le correctif
Un
/64de conteneurs par nœud, que le nœud est seul à annoncer — une seconde adressesur
br-svcsuffit à rendre le préfixe connecté. Le voisin ne délivre donc plus localement :il forwarde vers le propriétaire, qui a l'entrée de conntrack.
⚠ La règle de transit n'affaiblit pas la politique : le nœud de destination applique la
sienne —
establisheds'il a l'entrée,dropsinon. La décision est simplement prise unefois, sur la machine seule à pouvoir en juger.
⚠ Le
/64du segment ne bouge pas :ghosty vit à Bordeaux, avec une session BGP versles deux
bod.La mesure, avant / après
kdig +retry=0 +timeout=3 @<stub-addr> dn42. NSdepuis chaque conteneur résolveur, sur lestrois serveurs de délégation v6 d'Unbound :
ac53::12189::1Le signe qui compte n'est pas le total, c'est que les quatre donnent désormais le même
résultat. Avant, deux jumeaux d'un même site — même configuration, même segment —
divergeaient : c'était toute la signature du défaut. (
fd00:913e:130::400échouait déjàpartout avant : serveur hors service côté DN42, pas notre chemin.)
Trois défauts trouvés en déroulant, corrigés ici
cloud-initn'écrit l'adresse d'un conteneur qu'au premier boot. La branche ne pouvaitdonc renuméroter aucun conteneur existant — l'adresse était un état en écriture unique,
jamais confronté à sa source de vérité. Le rôle
containerspose désormais10-dn42-eth{0,1}.networket désactive la gestion réseau de cloud-init : deux écrivainssur une interface, c'est un redémarrage qui défait un déploiement.
Bird refuse une connexion entrante d'une adresse qui n'est pas celle de son voisin —
« Unexpected connect from unknown address » — session coincée en
Activedes deux côtésavec TCP/179 ouvert et joignable.
Déployé et vérifié
Establishedlg.thystips.dn42répond 302 depuis les 4Raft Applied Indexdn42_dns_recursion_successà 1 partout0 to create, 0 to update⚠ Le raft ne s'est pas remis seul :
retry_joinne sert qu'au join initial et ne corrigepas la configuration interne du raft, donc plus aucun membre ne se joignait et aucun leader ne
pouvait être élu. Dénoué par la récupération officielle
peers.json. Un membre migré afficheSealed false/standby— donc l'air d'aller bien — alors que le raft ne l'a jamaisréenregistré : le seul contrôle qui voit la panne est l'index identique sur les quatre.
⚠ Les deux conteneurs
lg(PR #37) n'étaient pas dans l'inventaire de cette branche etrestent sur le
/64du segment. Sans régression — le segment reste dansct_sources— maisils gardent le défaut jusqu'au rebase de la #37, qui leur donnera leur adresse tout seul.
⚠ La v4 depuis les conteneurs restait hétérogène après ce correctif — pour une raison
différente, instruite et corrigée plus bas.
Un SECOND retour asymétrique, de cause différente (14/08/2026)
Trouvé en instruisant la v4 restée hétérogène après le correctif ci-dessus.
Un conteneur n'a pas d'adresse v4 propre dans DN42 : sa seule v4 est la VIP anycast,
sur sa loopback. Quand Unbound récurse en v4, il sort donc avec une source anycast, et le
lointain répond à l'instance la plus proche de lui :
⚠ Ce n'est pas le défaut du
/64: celui-ci tient à l'anycast lui-même et serait restéentier. Une adresse anycast comme source est fausse par construction. En v6 la question ne
se pose pas — le conteneur a une unicast pour sortir et la VIP pour servir ; en v4 il n'a que
la seconde.
Le correctif : un SNAT vers la loopback v4 du nœud, qui est unicast. C'est le premier NAT
que ce dépôt écrit lui-même — choisi contre une v4 unicast par conteneur parce que le
/27dusite est une ressource rare (32 adresses, 7 prises).
Il ne voit pas le servant : la chaîne
natn'est consultée que pour le premier paquet d'unflux, jamais pour la réponse à une requête entrante. Et il exclut
br-svc, pour que le raft etles sondes gardent l'identité de qui parle.
Mesuré après déploiement : les quatre résolveurs atteignent les quatre serveurs testés
en v4 comme en v6 (16/16), et les deux VIP v4 répondent toujours depuis les quatre nœuds
avec le bon NSID.
`cloud-init.network-config` est lu au PREMIER BOOT et jamais ensuite. L'adresse d'un conteneur n'existait donc que là, et changer `topology/` ne renumérotait rien sur un conteneur déjà en marche — un état en écriture unique, qui dérivait en silence de sa source de vérité parce que personne ne l'avait jamais confronté. Le passage au /64 par nœud le confronte : les douze conteneurs doivent bouger. Ansible prend donc la patte. cloud-init amorce, `10-dn42-eth{0,1}.network` maintient, et la gestion réseau de cloud-init est désactivée derrière — deux écrivains sur une interface, c'est un redémarrage qui défait un déploiement. `networkctl reconfigure` et pas seulement `reload` : c'est lui qui RETIRE ce qui n'est plus déclaré. La vérification cherche l'ancienne adresse autant que la nouvelle, un conteneur répondant sur deux préfixes gardant exactement le défaut qu'on corrige. Le render-check validait tout template du rôle en YAML, ce qui était vrai tant qu'ils étaient tous des documents cloud-init. Il valide maintenant ceux qui en sont. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9Un conteneur n'a pas d'adresse v4 propre dans DN42 : sa seule v4 est la VIP anycast, sur sa loopback. Quand Unbound récurse vers un serveur de délégation en v4, il sort donc avec une source anycast — et le lointain répond à l'instance la plus proche de LUI. fr-rbx2 wgp-yuyuko Out 172.20.187.253 > 172.20.129.1 NS? dn42. fr-bod1 wgp-routedbits In 172.20.129.1 > 172.20.187.253 NS ... Question partie de Roubaix, réponse arrivée à Bordeaux, livrée au conteneur qui n'avait rien demandé. ⚠ Ce n'est PAS le défaut du /64 corrigé la veille, dont la cause était un préfixe annoncé depuis deux endroits. Celui-ci tient à l'anycast lui-même et serait resté entier. En v6 la question ne se pose pas — le conteneur a une unicast pour sortir et la VIP pour servir ; en v4 il n'a que la seconde. Le premier NAT que ce dépôt écrit lui-même, choisi contre une v4 unicast par conteneur parce que le /27 du site est rare. Il ne voit pas le servant : la chaîne nat n'est consultée que pour le premier paquet d'un flux, jamais pour la réponse à une requête entrante. Et il exclut `br-svc`, pour que le raft garde l'identité de qui parle. Mesuré après : les quatre résolveurs atteignent les quatre serveurs testés en v4 comme en v6, et les deux VIP v4 répondent toujours depuis les quatre nœuds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9