feat(ingress) : la VIP anycast descend dans le conteneur #25

Merged
thystips merged 3 commits from feat/ingress-vip into main 2026-08-09 01:59:19 +02:00
Owner

Le tier d'ingress est entièrement en conteneur sur les quatre nœuds : Caddy, son bao agent, et maintenant Bird et la VIP. Plus rien n'en reste sur les hôtes.

Ce que le conteneur apporte que le nœud ne pouvait pas

Le contrat de suivi de service est le même — l'adresse existe tant que l'unité tourne — avec un signal de plus : si la machine meurt d'un coup, la session iBGP tombe et le nœud retire la route sans que rien n'ait eu à s'exécuter. L'ExecStopPost d'un nœud ne couvrait pas ce cas.

Vérifié en arrêtant Caddy dans un conteneur : VIP retirée du lo, route basculée vers l'autre nœud, service ininterrompu, retour immédiat au démarrage.

Trois décisions

Route-reflector, pas simple voisin. Une route apprise en iBGP n'est pas réannoncée en iBGP. Sans rr client, le nœud apprendrait la VIP de son conteneur et les trois autres n'en sauraient rien — chacun n'aurait plus qu'un chemin, le sien. Le symptôme serait tardif : tout marche jusqu'au jour où un conteneur tombe. Vérifié : ibgp_fr_rbx1 from fd1d:cc09:1366::1 visible à Bordeaux, cinq chemins vers la VIP.

Le filtre d'import n'est pas optionnel. Une session iBGP est un droit d'écriture dans la table de routage de l'AS. Une adresse, celle que containers.yml déclare, et rien d'autre — et le conteneur filtre aussi à l'export, la sûreté ne devant pas tenir à un seul filtre.

Le router-id ne dépense rien du /27. C'est un identifiant 32 bits, jamais porté ni routé : 0.<index/256>.<index%256>.<node id>. ⚠ Laissé à Bird, il aurait pris l'adresse DHCP du pont NATé — 10.42.0.x sur les quatre nœuds à la fois, et deux locuteurs iBGP qui partagent un router-id n'échouent pas bruyamment : la réflexion est écartée comme déjà-vue.

Les cinq défauts que le premier nœud a révélés

Aucun n'était visible au lint, et trois répondaient « ça marche » alors que non.

Ce qui s'est passé
protocol bgp node Nom refusé par birdc, pas par Bird. bird -p validait, la session s'établissait, seule l'interrogation échouait — un protocole qu'aucune commande ne peut nommer
Contrôle avant handler Le contrôle interrogeait un Bird tournant encore avec la configuration précédente. meta: flush_handlers
preference 200 iBGP vaut 100, Babel 130 : le nœud envoyait le trafic à un autre nœud alors que son conteneur local servait. Rien n'était cassé — c'est le problème
protocol direct sur lo seul Sans le préfixe connecté d'eth0, Bird ne résout pas le next hop v6 des routes v4 : 1347 routes installées en unreachable, session établie, compteur juste, pas un paquet de retour
Route résiduelle Retirer la VIP de lo-dn42 laisse <vip>/128 dev lo-dn42 metric 256. Inoffensive tant que la session tient, trou noir le jour où elle tombe

La bascule

Trois états pour le rôle, un seul discriminant. ingress_on_node: false déclenche le retrait — unités, configuration, binaire, relais, route de traversée. ⚠ Le retrait arrête l'unité avant de la supprimer, parce que c'est son ExecStopPost qui retire la VIP ; l'ordre inverse laisserait Bird annoncer une adresse depuis un nœud muet.

Déroulée nœud par nœud, conteneur d'abord : il prend la VIP avant que le nœud ne la lâche, donc pas de trou — le nœud préfère sa route directe tant qu'elle existe.

Vérifié

Résultat
Les quatre nœuds X-Ingress-Node = leur propre conteneur, en v4 et v6
TLS 302, ssl_verify_result=0 contre la racine DN42, partout
Route v4 172.20.187.251 via inet6 fd1d:…::b:1 dev br-svc — l'ENH jusqu'au dernier saut
Drain Caddy arrêté → route basculée → service ininterrompu → retour immédiat
Caddy sur les nœuds inactive, VIP absente de lo-dn42 sur les quatre
Second passage changed=0 sur les douze hôtes
NetBox 12 objets créés, --check à zéro

À faire à la main

Le sync ne supprime jamais : les huit anciennes assignations de la VIP aux lo-dn42 restent dans NetBox — ids 467 à 474.

⚠ Les journaux de Caddy ne remontent plus : un conteneur a son propre journald, et monitoring_journal_units lit celui de l'hôte. C'est le prochain sujet.

Le tier d'ingress est **entièrement en conteneur** sur les quatre nœuds : Caddy, son `bao agent`, et maintenant Bird et la VIP. Plus rien n'en reste sur les hôtes. ## Ce que le conteneur apporte que le nœud ne pouvait pas Le contrat de suivi de service est le même — l'adresse existe tant que l'unité tourne — avec un signal de plus : **si la machine meurt d'un coup, la session iBGP tombe et le nœud retire la route sans que rien n'ait eu à s'exécuter.** L'`ExecStopPost` d'un nœud ne couvrait pas ce cas. Vérifié en arrêtant Caddy dans un conteneur : VIP retirée du `lo`, route basculée vers l'autre nœud, service ininterrompu, retour immédiat au démarrage. ## Trois décisions **Route-reflector, pas simple voisin.** Une route apprise en iBGP n'est pas réannoncée en iBGP. Sans `rr client`, le nœud apprendrait la VIP de son conteneur et les trois autres n'en sauraient rien — chacun n'aurait plus qu'un chemin, le sien. Le symptôme serait tardif : tout marche jusqu'au jour où un conteneur tombe. Vérifié : `ibgp_fr_rbx1 from fd1d:cc09:1366::1` visible à Bordeaux, cinq chemins vers la VIP. **Le filtre d'import n'est pas optionnel.** Une session iBGP est un droit d'écriture dans la table de routage de l'AS. Une adresse, celle que `containers.yml` déclare, et rien d'autre — et le conteneur filtre aussi à l'export, la sûreté ne devant pas tenir à un seul filtre. **Le router-id ne dépense rien du /27.** C'est un identifiant 32 bits, jamais porté ni routé : `0.<index/256>.<index%256>.<node id>`. ⚠ Laissé à Bird, il aurait pris l'adresse DHCP du pont NATé — `10.42.0.x` sur les quatre nœuds à la fois, et deux locuteurs iBGP qui partagent un router-id n'échouent pas bruyamment : la réflexion est écartée comme déjà-vue. ## Les cinq défauts que le premier nœud a révélés Aucun n'était visible au lint, et **trois répondaient « ça marche » alors que non**. | | Ce qui s'est passé | |---|---| | `protocol bgp node` | Nom refusé par `birdc`, pas par Bird. `bird -p` validait, la session s'établissait, seule l'interrogation échouait — un protocole qu'aucune commande ne peut nommer | | Contrôle avant handler | Le contrôle interrogeait un Bird tournant encore avec la configuration précédente. `meta: flush_handlers` | | `preference 200` | iBGP vaut 100, Babel 130 : le nœud envoyait le trafic à un **autre** nœud alors que son conteneur local servait. Rien n'était cassé — c'est le problème | | `protocol direct` sur `lo` seul | Sans le préfixe connecté d'`eth0`, Bird ne résout pas le next hop v6 des routes v4 : 1347 routes installées en `unreachable`, session établie, compteur juste, pas un paquet de retour | | Route résiduelle | Retirer la VIP de `lo-dn42` laisse `<vip>/128 dev lo-dn42 metric 256`. Inoffensive tant que la session tient, **trou noir** le jour où elle tombe | ## La bascule Trois états pour le rôle, un seul discriminant. `ingress_on_node: false` déclenche le retrait — unités, configuration, binaire, relais, route de traversée. ⚠ Le retrait **arrête l'unité avant de la supprimer**, parce que c'est son `ExecStopPost` qui retire la VIP ; l'ordre inverse laisserait Bird annoncer une adresse depuis un nœud muet. Déroulée nœud par nœud, **conteneur d'abord** : il prend la VIP avant que le nœud ne la lâche, donc pas de trou — le nœud préfère sa route directe tant qu'elle existe. ## Vérifié | | Résultat | |---|---| | Les quatre nœuds | `X-Ingress-Node` = leur propre conteneur, en v4 et v6 | | TLS | `302`, `ssl_verify_result=0` contre la racine DN42, partout | | Route v4 | `172.20.187.251 via inet6 fd1d:…::b:1 dev br-svc` — l'ENH jusqu'au dernier saut | | Drain | Caddy arrêté → route basculée → service ininterrompu → retour immédiat | | Caddy sur les nœuds | `inactive`, VIP absente de `lo-dn42` sur les quatre | | Second passage | `changed=0` sur les douze hôtes | | NetBox | 12 objets créés, `--check` à zéro | ## À faire à la main Le sync ne supprime jamais : les huit anciennes assignations de la VIP aux `lo-dn42` restent dans NetBox — **ids 467 à 474**. ⚠ Les journaux de Caddy ne remontent plus : un conteneur a son propre `journald`, et `monitoring_journal_units` lit celui de l'hôte. C'est le prochain sujet.
Bird dans le conteneur d'ingress, en client de route-reflector de son nœud, et
la VIP posée sur son `lo` par l'unité Caddy. Le contrat est celui d'avant —
l'adresse existe tant que le service tourne — avec un signal de plus : si la
machine meurt d'un coup, la session tombe et le nœud retire la route SANS que
rien n'ait eu à s'exécuter. L'`ExecStopPost` d'un nœud ne couvrait pas ce cas.

POURQUOI RÉFLECTEUR ET NON SIMPLE VOISIN. Une route apprise en iBGP n'est pas
réannoncée en iBGP. Sans `rr client`, le nœud apprendrait la VIP de son conteneur
et les trois autres n'en sauraient rien : chacun n'aurait plus qu'un chemin, le
sien, et l'anycast perdrait ce pour quoi il existe. Le symptôme serait tardif —
tout marche jusqu'au jour où un conteneur tombe.

LE FILTRE D'IMPORT N'EST PAS OPTIONNEL. Une session iBGP est un droit d'écriture
dans la table de routage de l'AS : une adresse, celle que containers.yml déclare,
et rien d'autre. Le conteneur filtre aussi à l'export — la sûreté ne doit pas
tenir à un seul filtre.

LE ROUTER-ID NE DÉPENSE RIEN DU /27. C'est un identifiant 32 bits, jamais porté
ni routé : `0.<index/256>.<index%256>.<node id>`, dérivé, documenté dans as.yml.
⚠ Laissé à Bird, il aurait pris l'adresse DHCP du pont NATé — `10.42.0.x` sur les
quatre nœuds à la fois. Deux locuteurs iBGP qui partagent un router-id
n'échouent pas bruyamment : la réflexion est écartée comme déjà-vue et une route
n'arrive jamais.

La VIP v4 sur une machine v6 seule tient par `extended next hop`, que cet AS
impose déjà partout. Mesuré à la main avant d'écrire quoi que ce soit, routes
temporaires posées puis retirées : ping 0,039 ms et TCP « pong from container »,
retour compris.

Bird vient de DEBIAN et non du dépôt CZ.NIC : celui-ci existe pour la métrique
RTT de Babel, que le conteneur ne fait pas tourner. Trixie paquete une Bird 3,
donc la même syntaxe. Un conteneur qui se recrée sans dépôt tiers est la
propriété pour laquelle le modèle « détruire et refaire » existe.

Pare-feu et matrice dans le même commit : ligne 10a′ (BGP depuis le conteneur,
épinglé à son adresse) et la VIP en `forward`, sur les ports d'anycast.yml. ⚠ La
règle porte sur l'adresse ANYCAST, jamais sur celle du conteneur, qui reste
fermée : le monde atteint un service, pas la machine qui le sert.

LE RÔLE A MAINTENANT TROIS ÉTATS, et `ingress_on_node: false` en déclenche le
troisième : sur un nœud, il RETIRE le proxy — unités, configuration, binaire,
relais, route de traversée. ⚠ Le retrait arrête l'unité AVANT de la supprimer,
parce que c'est son `ExecStopPost` qui retire la VIP de `lo-dn42` ; l'ordre
inverse laisserait Bird annoncer une adresse depuis un nœud muet, ce qui est le
seul vrai risque de cette bascule — un trou noir anycast invisible du dehors.

La bascule se déroule nœud par nœud, conteneur d'abord : il PREND la VIP avant
que le nœud ne la lâche, donc pas de trou. Procédure exacte dans le README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9
Tous trouvés en déroulant fr-rbx1, aucun n'était visible au lint. Ils sont ici
avec ce qui les a fait apparaître, parce que trois d'entre eux répondaient
« ça marche » alors que non.

1. `protocol bgp node` — nom REFUSÉ PAR `birdc`, pas par Bird. `bird -p` validait,
   la session s'établissait, et seule l'interrogation échouait :
   « syntax error, unexpected CF_SYM_UNDEFINED ». Un protocole qu'aucune commande
   ne peut nommer : ni `show protocols`, ni `show route protocol`, ni `disable`.
   La validation hors ligne ne pouvait pas l'attraper.

2. Le contrôle de session tournait AVANT le handler qui recharge Bird, donc il
   interrogeait le démon avec la configuration précédente. Au passage qui
   renommait le protocole, `birdc` répondait CF_SYM_UNDEFINED sur un nom
   parfaitement valide — il n'existait pas encore. `meta: flush_handlers`.

3. `preference 200` sur les deux canaux du côté nœud. Un canal iBGP vaut 100,
   Babel 130 : le nœud dont le conteneur LOCAL servait la VIP envoyait le trafic
   à un AUTRE nœud par le tunnel de maille — `via fe80::3 on wgm-fr-rbx2` alors
   que le service tournait sur la machine. Rien n'était cassé, et c'est le
   problème : ça répondait. L'anycast perdait juste ce qu'on lui demande.

4. `protocol direct` ne couvrait que `lo`. Bird n'avait donc pas le préfixe
   connecté d'eth0, ne pouvait pas RÉSOUDRE le next hop v6 des routes v4, et
   installait les 1347 routes en `unreachable`. Session établie, routes reçues,
   compteur juste, et pas un paquet qui revenait.

5. Retirer la VIP de `lo-dn42` laisse derrière une route
   `<vip>/128 dev lo-dn42 proto kernel metric 256`. Inoffensive tant que la
   session vers le conteneur tient — Bird gagne en metric 32 — et trou noir le
   jour où elle tombe : le trafic serait livré localement, sur une interface où
   plus rien n'écoute. Donc au pire moment, et sans que rien ne le dise.

Vérifié sur fr-rbx1 : route v4 `via inet6 <conteneur>` dans la table 42,
`X-Ingress-Node: fr-rbx1` en v4 et en v6, session stable sur six relevés en deux
minutes, et la route réfléchie jusqu'à Bordeaux (`ibgp_fr_rbx1 from
fd1d:cc09:1366::1`) — cinq chemins vers la VIP depuis fr-bod1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9
Le sync annonçait « 0 to update » et se trompait : il déclarait toujours
l'adresse anycast `ingress` sur les `lo-dn42` des quatre nœuds, alors qu'elle
vit maintenant sur le `lo` de leurs conteneurs. Un IPAM en retard répond avec
assurance, et celui-là aurait répondu faux à la question la plus courante —
« où est cette adresse ».

Un service anycast descendu en conteneur cesse donc d'être déclaré sur le nœud,
et le conteneur gagne son interface `lo` avec la VIP dessus. La bascule est
dérivée de `containers.yml` : c'est la même déclaration `anycast:` qui ouvre la
session iBGP, arme le filtre d'import et place l'adresse ici.

⚠ Le sync NE SUPPRIME JAMAIS. Les huit anciennes assignations restent donc dans
NetBox et se retirent à la main, objet par objet — ids 467 à 474.

⚠ `description` est limité à 200 caractères côté NetBox, et le dépassement est
un 400 à l'appel, pas une erreur de rendu. Rencontré sur l'interface `lo`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9
thystips deleted branch feat/ingress-vip 2026-08-09 01:59:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ThysTips_dn42/infra-dn42!25
No description provided.