underlay : un rôle par hôte, et les hyperviseurs au modèle

Redresser les pairs EVPN vers l'underlay suppose que les hyperviseurs
existent dans le modèle. Ils n'y étaient pas.

La reconnaissance a montré la cause : vmbr3 porte 10.27.19.{41,43,47} sur les
trois nœuds — l'adresse des VTEP est prise dans le supernet de Chezlepro. Le
modèle refuse d'exprimer cet état : déclarer 10.27.19.0/24 en underlay ferait
échouer P23. La garde détecte la faute avant qu'on ne la documente.

Ajouté un `role` sur les hôtes (switch par défaut, hyperviseur, frontiere) :
le réseau ne suffit pas à le déduire, et un hyperviseur déclaré recevait une
configuration de commutateur en partie B.

Corrigé une « source unique » qui n'en était pas une : `switches_acces()`
avait été introduite comme LA décision du « qui est un switch d'accès », mais
`partie_acces()` gardait sa copie locale du filtre et ne l'appelait jamais.
Les deux ont divergé au premier hôte non-commutateur. Écrire « source
unique » dans un commentaire ne la crée pas.

Preuves : 24 OK, 0 échec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-03 10:21:30 -04:00
parent 70fe6de557
commit 6a80e5b55c
5 changed files with 96 additions and 7 deletions

View file

@ -1,5 +1,39 @@
# CHANGELOG — Set-OPS
## 2026-08-03 (suite 6) — les hyperviseurs entrent au modèle, à leur adresse cible
Redresser les pairs EVPN vers l'underlay suppose d'abord que les hyperviseurs **existent dans
le modèle**. Ils n'y étaient pas.
### La reconnaissance a montré la cause
`vmbr3` — la nouvelle interface 2,5G — porte `10.27.19.{41,43,47}` sur les trois nœuds :
l'adresse des VTEP est prise **dans le supernet de Chezlepro**. Le transport du cluster dérive
donc de l'index d'un tenant, et une VM de sa zone *Services-infra* partage son sous-réseau avec
les trois VTEP.
Le modèle **refuse d'exprimer cet état** : déclarer `10.27.19.0/24` en underlay ferait échouer
**P23**. La garde écrite deux jours plus tôt détecte la faute avant qu'on ne la documente.
`underlay.yml` déclare donc `asgard`, `gandalf` et `vishnu` à leur adresse **cible**
`10.0.0.{41,43,47}` — dernier octet conservé, comme sur `vmbr0`.
### Ajouté — un `role` sur les hôtes de l'underlay
`switch` (défaut), `hyperviseur`, `frontiere`. Le réseau ne suffit pas à le déduire : un
hyperviseur partage le réseau de management avec les commutateurs, et **recevait une
configuration de commutateur** en partie B du devis dès qu'on le déclarait.
### Corrigé — une « source unique » qui n'en était pas une
`switches_acces()` avait été introduite comme *la* décision du « qui est un switch d'accès »,
utilisée pour les rayons de l'étoile. Mais `partie_acces()` avait **gardé sa copie locale** du
filtre et ne l'appelait jamais. Les deux ont divergé au premier hôte non-commutateur déclaré.
Écrire « source unique » dans un commentaire ne la crée pas.
### Deuxième blocage signalé, non corrigé
`vishnu` : son `vmbr3` n'a **aucun port physique**. Le pont existe, porte une adresse, ne mène
nulle part. Un pair VXLAN pointé sur lui ne fonctionnera jamais — c'est du câblage, pas de la
configuration.
## 2026-08-03 (suite 5) — l'ICMP entre au registre, parce que l'overlay descend à 1450
Décision : l'**overlay EVPN plafonne à 1450**. Elle a une conséquence qui ne se voit pas —

View file

@ -126,7 +126,38 @@ Un tenant de labo, une zone EVPN, deux VNet, une VM dans chacun — et vérifier
Ce n'est qu'après que la question « comment générer cette configuration » se pose.
## 7. Ce qui reste à trancher
## 7. L'état réel du cluster (reconnaissance du 2026-08-03)
Lecture seule par l'API Proxmox. **Le plan de contrôle existe, le plan de données non.**
| | |
|---|---|
| Proxmox | 8.4.19 — `asgard`, `gandalf`, `vishnu` |
| Contrôleur | `EVPN0017`, ASN 65000 |
| Zones | `VRF0011`, `VRF0017` — un VRF par tenant, VNI = index, MTU 1450 |
| VNets | **aucun** |
| Nœuds de sortie | **aucun** |
### Deux blocages à lever avant d'aller plus loin
**Les VTEP sont adressés dans un tenant.** `vmbr3` porte `10.27.19.{41,43,47}` — le
sous-réseau *Services-infra de Chezlepro*. Le transport du cluster dérive donc de l'index d'un
tenant : un changement d'index le casse, une migration l'emporte. Et une VM de cette zone
partage son sous-réseau avec les trois VTEP, ce qui perce l'isolation à l'endroit même que
l'EVPN devait fermer.
Le modèle **refuse d'ailleurs d'exprimer cet état** : déclarer `10.27.19.0/24` comme réseau
d'underlay ferait échouer **P23**, qui interdit tout chevauchement avec un supernet tenant. La
garde détecte la faute avant qu'on ne la documente.
`underlay.yml` déclare donc les trois hyperviseurs à leur adresse **cible**`10.0.0.{41,43,47}`,
dernier octet conservé comme sur `vmbr0`. Le déplacement réel de l'adresse sur les nœuds reste
à faire : c'est une modification du réseau d'un hyperviseur en service.
**`vishnu` n'est pas câblé.** Son `vmbr3` n'a **aucun port physique** : le pont existe, porte
une adresse, et ne mène nulle part. Un pair VXLAN pointé sur lui ne fonctionnera jamais.
## 8. Ce qui reste à trancher
- **Où vit la configuration SDN.** Elle est *par cluster*, donc propriété de l'hébergeur —
comme `underlay.yml`, et par le même raisonnement.

View file

@ -237,11 +237,10 @@ def partie_acces(underlay: dict | None, tenants: list, vlans: str,
fabric = underlay_mod.fabric_du_routeur(underlay)
mgmt = next((r for r in underlay_mod.reseaux_de_fabric(underlay, fabric)
if r.get("passerelle")), None)
# UNIQUEMENT les hotes du reseau de management : les autres equipements declares
# (frontieres sur le lien de transit, par exemple) ne sont pas des switches et ne
# doivent recevoir aucune de ces lignes.
autres = [h for h in underlay_mod.hotes(underlay)
if h.get("nom") != r_nom and mgmt and h.get("reseau") == mgmt["nom"]]
# SOURCE UNIQUE : `switches_acces()` decide qui est un switch d'acces — reseau de
# management ET role `switch`. Une copie locale de ce filtre avait laisse les
# hyperviseurs recevoir une configuration de commutateur.
autres = underlay_mod.switches_acces(underlay)
if not autres:
return []
out = [

View file

@ -187,11 +187,26 @@ def _ports_a_plat(hote: dict) -> list[str]:
return plat
ROLES_HOTE = ("switch", "hyperviseur", "frontiere")
def role_hote(hote: dict) -> str:
"""Nature d'un equipement declare. `switch` par defaut (compatibilite).
Le reseau ne suffit pas a la deduire : un hyperviseur partage le reseau de
management avec les commutateurs, et recevrait sinon leur configuration.
"""
return str((hote or {}).get("role") or "switch")
def switches_acces(underlay: dict | None) -> list[dict]:
"""Les switches d'acces : hotes du reseau de management, hors routeur.
Source unique du « qui est un switch d'acces » — utilisee pour leur devis (partie B)
ET pour les rayons de l'etoile emis cote routeur. Les deux ne peuvent pas diverger.
Filtre sur le ROLE autant que sur le reseau : un hyperviseur declare sur le meme
reseau ne doit recevoir aucune ligne de configuration de commutateur.
"""
r_nom = routeur(underlay)
if not r_nom:
@ -201,7 +216,8 @@ def switches_acces(underlay: dict | None) -> list[dict]:
if not mgmt:
return []
return [h for h in hotes(underlay)
if h.get("nom") != r_nom and h.get("reseau") == mgmt["nom"]]
if h.get("nom") != r_nom and h.get("reseau") == mgmt["nom"]
and role_hote(h) == "switch"]
def reseau_transit(underlay: dict | None) -> dict | None:
@ -315,6 +331,12 @@ def valider(underlay: dict | None,
erreurs.append(f"reseau '{nom}': passerelle_sortie '{r['passerelle_sortie']}' invalide")
vus_sous.append((net, nom))
for h in hotes(underlay):
r = (h or {}).get("role")
if r is not None and str(r) not in ROLES_HOTE:
erreurs.append(f"hote '{h.get('nom','?')}': role '{r}' inconnu "
f"(attendu : {', '.join(ROLES_HOTE)})")
# Ports physiques : un meme port ne peut pas servir deux fois sur un equipement,
# et un rayon ne peut viser qu'un switch d'acces reellement declare.
noms_acces = {h.get("nom") for h in switches_acces(underlay)}

View file

@ -105,6 +105,9 @@ underlay:
# montante : port vers le routeur, cote SWITCH D'ACCES uniquement
# `make underlay` refuse un port declare deux fois sur un meme equipement, un rayon
# vers un switch inconnu, et une confusion rayons/montante.
# `role` : switch (defaut) | hyperviseur | frontiere. Le reseau ne suffit pas a le
# deduire — un hyperviseur partage le reseau de management avec les commutateurs, et
# recevrait sinon une configuration de commutateur dans la partie B du devis.
hotes:
# Le switch designe `routeur` porte le SVI de management : son adresse de gestion
# EST la passerelle du reseau. `make underlay` refuse les deux valeurs divergentes.