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:
parent
70fe6de557
commit
6a80e5b55c
5 changed files with 96 additions and 7 deletions
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -1,5 +1,39 @@
|
||||||
# CHANGELOG — Set-OPS
|
# 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
|
## 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 —
|
Décision : l'**overlay EVPN plafonne à 1450**. Elle a une conséquence qui ne se voit pas —
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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 —
|
- **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.
|
comme `underlay.yml`, et par le même raisonnement.
|
||||||
|
|
|
||||||
|
|
@ -237,11 +237,10 @@ def partie_acces(underlay: dict | None, tenants: list, vlans: str,
|
||||||
fabric = underlay_mod.fabric_du_routeur(underlay)
|
fabric = underlay_mod.fabric_du_routeur(underlay)
|
||||||
mgmt = next((r for r in underlay_mod.reseaux_de_fabric(underlay, fabric)
|
mgmt = next((r for r in underlay_mod.reseaux_de_fabric(underlay, fabric)
|
||||||
if r.get("passerelle")), None)
|
if r.get("passerelle")), None)
|
||||||
# UNIQUEMENT les hotes du reseau de management : les autres equipements declares
|
# SOURCE UNIQUE : `switches_acces()` decide qui est un switch d'acces — reseau de
|
||||||
# (frontieres sur le lien de transit, par exemple) ne sont pas des switches et ne
|
# management ET role `switch`. Une copie locale de ce filtre avait laisse les
|
||||||
# doivent recevoir aucune de ces lignes.
|
# hyperviseurs recevoir une configuration de commutateur.
|
||||||
autres = [h for h in underlay_mod.hotes(underlay)
|
autres = underlay_mod.switches_acces(underlay)
|
||||||
if h.get("nom") != r_nom and mgmt and h.get("reseau") == mgmt["nom"]]
|
|
||||||
if not autres:
|
if not autres:
|
||||||
return []
|
return []
|
||||||
out = [
|
out = [
|
||||||
|
|
|
||||||
|
|
@ -187,11 +187,26 @@ def _ports_a_plat(hote: dict) -> list[str]:
|
||||||
return plat
|
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]:
|
def switches_acces(underlay: dict | None) -> list[dict]:
|
||||||
"""Les switches d'acces : hotes du reseau de management, hors routeur.
|
"""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)
|
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.
|
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)
|
r_nom = routeur(underlay)
|
||||||
if not r_nom:
|
if not r_nom:
|
||||||
|
|
@ -201,7 +216,8 @@ def switches_acces(underlay: dict | None) -> list[dict]:
|
||||||
if not mgmt:
|
if not mgmt:
|
||||||
return []
|
return []
|
||||||
return [h for h in hotes(underlay)
|
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:
|
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")
|
erreurs.append(f"reseau '{nom}': passerelle_sortie '{r['passerelle_sortie']}' invalide")
|
||||||
vus_sous.append((net, nom))
|
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,
|
# 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.
|
# et un rayon ne peut viser qu'un switch d'acces reellement declare.
|
||||||
noms_acces = {h.get("nom") for h in switches_acces(underlay)}
|
noms_acces = {h.get("nom") for h in switches_acces(underlay)}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,9 @@ underlay:
|
||||||
# montante : port vers le routeur, cote SWITCH D'ACCES uniquement
|
# montante : port vers le routeur, cote SWITCH D'ACCES uniquement
|
||||||
# `make underlay` refuse un port declare deux fois sur un meme equipement, un rayon
|
# `make underlay` refuse un port declare deux fois sur un meme equipement, un rayon
|
||||||
# vers un switch inconnu, et une confusion rayons/montante.
|
# 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:
|
hotes:
|
||||||
# Le switch designe `routeur` porte le SVI de management : son adresse de gestion
|
# 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.
|
# EST la passerelle du reseau. `make underlay` refuse les deux valeurs divergentes.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue