frontière : le nœud de sortie dérive, l'identifiant d'interface est vérifié
Le prochain saut des routes tenants ne s'écrit plus <NOEUD-DE-SORTIE-EVPN> : il dérive. Deux déclarations doivent concorder, et c'est voulu — l'hébergeur nomme le nœud (proxmox_sdn.sortie_primaire, propriété du cluster), l'underlay dit son adresse SUR LE LIEN DE FRONTIÈRE. Nommer un nœud absent du lien rend le devis muet plutôt que faux. route add 10.21.0.0/16 via 10.0.4.41 route add 10.27.0.0/16 via 10.0.4.41 Le devis explique pourquoi cette adresse-là — ni l'underlay ni la gestion, la frontière n'atteint que celle-là — et pourquoi un seul saut : une route statique n'en porte qu'un, et deux nœuds actifs en sortie avec une seule route en entrée donneraient un chemin asymétrique. Identifiant d'interface vérifié plutôt que supposé : l'API des règles donne elle-même sa liste (lan → GESTION, opt1 → TENANTS, wan → WAN). Ni le périphérique vlan040, ni le libellé. L'intrant opnsense_if_transit: opt1 était juste — question ouverte depuis deux jours, tranchée par la mesure. Consigné au CHANGELOG : l'EVPN tourne (six sessions BGP, tunnels sortis de la carte de gestion), une alarme retirée sur des VRF que je croyais inversés, et une brèche réelle à filtrer avant la première VM tenant — un nœud de sortie expose au tenant tout ce que l'hyperviseur touche directement, et déplacer la route par défaut n'y suffira pas puisqu'une route connectée l'emporte. 30 preuves OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e6c259be4d
commit
f8977d2d29
2 changed files with 124 additions and 3 deletions
67
CHANGELOG.md
67
CHANGELOG.md
|
|
@ -1,5 +1,72 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-06 — le chemin nord-sud devient dérivable
|
||||
|
||||
### L'EVPN tourne
|
||||
|
||||
Les commutateurs configurés, le trunk vérifié : `10.0.5.41` joint `.43` et `.47`. Le SDN
|
||||
appliqué a fait basculer les tunnels — ils sortaient de **`192.168.11.41`**, la carte de
|
||||
gestion, et sont maintenant sur `vlan11`. C'est ce que l'objection du 4 août visait, et ce
|
||||
n'est vrai que depuis cette application.
|
||||
|
||||
`bgpd` et `bfdd` étaient à `no` sur les trois nœuds : FRR tournait avec `zebra` seul, donc
|
||||
aucune session EVPN. Activés un nœud à la fois — `vishnu`, `gandalf`, `asgard` — avec
|
||||
vérification du quorum et de la route par défaut après chacun. **Maillage complet : six
|
||||
sessions établies**, 16 préfixes échangés.
|
||||
|
||||
Sauvegarde `/etc/frr/daemons.avant-bgpd` posée sur chaque nœud avant modification.
|
||||
|
||||
### Une alarme retirée
|
||||
|
||||
J'avais annoncé les VRF « inversés » — `vrf_t11` portant les sous-réseaux de Chezlepro. Le
|
||||
noyau tranche : `10.21.16.0/24 is directly connected, t11fron` dans `vrf_t11`. Chaque VRF
|
||||
porte bien ses propres sous-réseaux, par les passerelles anycast de ses VNets. Les
|
||||
`ip route … null0` sont ceux des *autres* zones — et avec deux tenants, « les autres » et
|
||||
« inversés » se ressemblaient exactement.
|
||||
|
||||
### Une brèche réelle, à filtrer avant la première VM
|
||||
|
||||
Une fois les nœuds de sortie actifs, une VM tenant atteint **tous les réseaux directement
|
||||
connectés sur son propre hyperviseur** — gestion Proxmox, stockage iSCSI, Ceph, et le
|
||||
transport VXLAN lui-même. Mesuré avant la suppression de la VM d'essai.
|
||||
|
||||
Ce n'est pas un défaut de configuration : c'est le prix du routage inter-VRF. Une route
|
||||
connectée l'emporte sur la route par défaut, donc **déplacer celle-ci (D-57) n'y suffira
|
||||
pas**. Il faut une règle « supernet tenant → réseaux de l'hyperviseur : DROP », que
|
||||
`make devis-proxmox-fw` sait déjà dériver.
|
||||
|
||||
**Déclencheur : avant la première VM tenant.** À ce stade — plateforme en construction,
|
||||
zéro locataire — c'est un chantier, pas un incident.
|
||||
|
||||
### La frontière : identifiant vérifié, nœud de sortie dérivé
|
||||
|
||||
La clé d'API fonctionne (OPNsense 26.1.2_5). L'API des règles donne elle-même la liste des
|
||||
identifiants qu'elle accepte :
|
||||
|
||||
```
|
||||
lan → « GESTION » opt1 → « TENANTS » wan → « WAN »
|
||||
```
|
||||
|
||||
Ni le périphérique (`vlan040`), ni le libellé. **L'intrant `opnsense_if_transit: opt1`
|
||||
était juste** — la question ouverte depuis deux jours est tranchée par la mesure.
|
||||
|
||||
Le prochain saut des routes tenants ne s'écrit plus `<NOEUD-DE-SORTIE-EVPN>` : il dérive.
|
||||
Deux déclarations doivent concorder, et c'est voulu — l'hébergeur **nomme** le nœud
|
||||
(`proxmox_sdn.sortie_primaire`, propriété du cluster), l'underlay dit **son adresse sur le
|
||||
lien de frontière**. Nommer un nœud absent du lien rend le devis muet plutôt que faux.
|
||||
|
||||
```
|
||||
route add 10.21.0.0/16 via 10.0.4.41
|
||||
route add 10.27.0.0/16 via 10.0.4.41
|
||||
```
|
||||
|
||||
Le devis explique pourquoi cette adresse-là, et pourquoi **un seul** saut : une route
|
||||
statique n'en porte qu'un, et deux nœuds actifs en sortie avec une seule route en entrée
|
||||
donneraient un chemin asymétrique — la réponse reviendrait par une interface où l'état
|
||||
n'a pas été créé.
|
||||
|
||||
30 preuves OK.
|
||||
|
||||
## 2026-08-04 (suite 4) — le numéro est l'adresse
|
||||
|
||||
Les deux commutateurs deviennent **`bifrost-3`** et **`bifrost-4`**, avec leurs adresses
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ Usage :
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import ipaddress
|
||||
import json
|
||||
import sys
|
||||
|
|
@ -123,13 +124,47 @@ def prochain_saut(transit: dict | None) -> str:
|
|||
fois dans l'underlay) ouvrirait la porte a deux valeurs contradictoires — c'est
|
||||
exactement le mode de panne qu'on ferme. Marqueur tant qu'aucun transit n'existe.
|
||||
"""
|
||||
if underlay_mod.routage_tenants(underlay_mod.charger()) == "sdn":
|
||||
underlay = underlay_mod.charger()
|
||||
if underlay_mod.routage_tenants(underlay) == "sdn":
|
||||
# En EVPN, le commutateur ne route plus les tenants : le voisin de la frontiere
|
||||
# est un NOEUD DE SORTIE (hyperviseur), pas le SVI. Emettre le SVI produirait
|
||||
# des routes vers un equipement qui n'a aucun chemin vers le tenant.
|
||||
return NOEUD_SORTIE
|
||||
#
|
||||
# C'est le noeud PRIMAIRE, et son adresse SUR LE LIEN DE TRANSIT — pas son
|
||||
# adresse d'underlay ni de gestion : la frontiere n'atteint que celle-la.
|
||||
# Une seule route statique par tenant, donc un seul saut : deux noeuds actifs
|
||||
# en sortie avec une seule route en entree donneraient un chemin asymetrique.
|
||||
return adresse_noeud_de_sortie(underlay, transit) or NOEUD_SORTIE
|
||||
return str((transit or {}).get("passerelle") or "").strip() or PROCHAIN_SAUT
|
||||
|
||||
|
||||
def adresse_noeud_de_sortie(underlay: dict | None, transit: dict | None) -> str:
|
||||
"""Adresse du noeud de sortie PRIMAIRE sur le lien de frontiere. '' si indeterminee.
|
||||
|
||||
Deux declarations doivent concorder, et c'est voulu : l'hebergeur nomme le noeud
|
||||
(`proxmox_sdn.sortie_primaire`, propriete du cluster), l'underlay dit son adresse
|
||||
sur ce lien. Nommer un noeud absent du lien rend le devis muet plutot que faux.
|
||||
"""
|
||||
primaire = str((_sdn_hebergeur() or {}).get("sortie_primaire") or "").strip()
|
||||
if not primaire or not transit:
|
||||
return ""
|
||||
for h in underlay_mod.hotes(underlay):
|
||||
if (h.get("nom") == primaire and h.get("reseau") == transit.get("nom")
|
||||
and h.get("ip")):
|
||||
return str(h["ip"])
|
||||
return ""
|
||||
|
||||
|
||||
def _sdn_hebergeur() -> dict:
|
||||
"""`proxmox_sdn` du depot de l'HEBERGEUR — meme derivation que devis_sdn (D-35)."""
|
||||
lien = Path(os.environ.get("SETOPS_UNDERLAY") or (RACINE / "underlay.yml"))
|
||||
if not lien.exists():
|
||||
return {}
|
||||
fichier = lien.resolve().parent / "proxmox-hebergeur.yml"
|
||||
if not fichier.is_file():
|
||||
return {}
|
||||
return (yaml.safe_load(fichier.read_text(encoding="utf-8")) or {}).get("proxmox_sdn") or {}
|
||||
|
||||
# Le socle est porte par TOUS les hotes : sa destination n'est pas une poignee
|
||||
# de machines mais le supernet du tenant. Evite un alias de 14 adresses.
|
||||
ROLE_SOCLE = "serveur_debian"
|
||||
|
|
@ -300,6 +335,10 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
for nom, _pfx, n in tenants
|
||||
]
|
||||
|
||||
# Adresse du noeud de sortie primaire sur le lien de frontiere, calculee une fois :
|
||||
# elle sert au prochain saut des routes ET au commentaire qui l'explique.
|
||||
_ip_sortie = adresse_noeud_de_sortie(underlay_mod.charger(), transit)
|
||||
|
||||
return {
|
||||
"role_frontiere": "nord-sud",
|
||||
"instance_active": nom_actif,
|
||||
|
|
@ -315,6 +354,9 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
"tenants_sans_inventaire": tenants_sans_inventaire,
|
||||
"tenants_sans_admin": sorted(set(tenants_sans_admin)),
|
||||
"admin_prives": admin_prives,
|
||||
"noeud_sortie": ({"nom": str((_sdn_hebergeur() or {}).get("sortie_primaire") or ""),
|
||||
"ip": _ip_sortie}
|
||||
if _ip_sortie else None),
|
||||
"transit": {
|
||||
"nom": (transit or {}).get("nom"),
|
||||
"vlan": (transit or {}).get("vlan"),
|
||||
|
|
@ -410,7 +452,19 @@ def rendre(devis: dict) -> str:
|
|||
"# /!\\ LE PROCHAIN SAUT N'EST PAS LE COMMUTATEUR. En SDN EVPN, il ne route plus",
|
||||
"# les tenants : une route pointee vers son SVI arriverait sur un equipement sans",
|
||||
"# chemin vers le tenant. Le voisin de la frontiere est un NOEUD DE SORTIE — un",
|
||||
"# hyperviseur qui porte la sortie du VRF. A figer apres le spike EVPN.",
|
||||
"# hyperviseur qui porte la sortie du VRF.",
|
||||
"# A DECLARER : `proxmox_sdn.sortie_primaire` chez l'hebergeur, et l'adresse de",
|
||||
"# ce noeud sur le lien de transit dans underlay.yml. Les deux doivent concorder.",
|
||||
]
|
||||
elif devis.get("noeud_sortie"):
|
||||
out += [
|
||||
f"# Prochain saut = {devis['noeud_sortie']['nom']}, noeud de sortie PRIMAIRE, a son",
|
||||
f"# adresse SUR LE LIEN DE FRONTIERE ({devis['noeud_sortie']['ip']}) — ni son underlay,",
|
||||
"# ni sa gestion : la frontiere n'atteint que celle-la.",
|
||||
"#",
|
||||
"# Un seul saut, parce qu'une route statique n'en porte qu'un. Deux noeuds actifs",
|
||||
"# en sortie avec une seule route en entree donneraient un chemin asymetrique : la",
|
||||
"# reponse reviendrait par une interface ou l'etat n'a pas ete cree, et serait jetee.",
|
||||
]
|
||||
out += [
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue