Aligne le GUI et la doc sur le multi-instances
GUI (vue Réseau) : - nouvelle vue FLOTTE (endpoint /api/instances) : liste les instances, marque l'active (star), montre index / plage VLAN / statut federe-local / prod, et affiche une banniere de COLLISION d'index — le pendant visuel de make instances et de la preuve P21. - le devis n'affiche plus que les instances FEDEREES (coherent avec le filtre federe ; le labo local en est ecarte). - retire les deux mentions perimees de « ip-miroir » (concept supprime a la rupture ; la decouverte se fait desormais sur `index`). La bascule d'instance et l'edition de `federe` restent au CLI (chirurgie de symlink / drapeau rarement change) : plan de controle gele. docs/multi-instances.md : - section « Gerer la flotte » (make instances, bascule, les deux reflexes). - section « Comment le moteur decouvre les instances » : convention de dossiers freres (glob ../*/plan/nomenclature.yml), pas de registre. - « Adressage federe » reecrit : il mentait encore (VMID compact 21101/1CSNN, sandbox « sans index », plafond 9). Remplace par le modele derive du seed (tableau des formules, VMID ip-miroir, drapeau federe: false, plafond reel 245). - corrige les mentions residuelles de `supernet` a saisir (c'est `index`). Valide : node --check du GUI, make verifier rc=0 CONFORME 21/21. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b1460a6dde
commit
dbc78f69ec
2 changed files with 125 additions and 39 deletions
|
|
@ -33,19 +33,43 @@ Elle possède **tout ce qui lui est propre** :
|
|||
```
|
||||
|
||||
Isolation **totale** entre instances : plan, inventaire, **voûte**, **identité**
|
||||
(`domaine_interne`), réseau (`supernet`) et **cible Proxmox** distincts. Une instance
|
||||
ne peut pas toucher l'infra d'une autre.
|
||||
(`domaine_interne`), réseau (seed `index` → adressage dérivé distinct) et **cible
|
||||
Proxmox** distincts. Une instance ne peut pas toucher l'infra d'une autre.
|
||||
|
||||
## Choisir l'instance active
|
||||
## Gérer la flotte
|
||||
|
||||
Le moteur lit la variable `SETOPS_INSTANCE` (par défaut le symlink `instance`).
|
||||
Une seule instance est **active** à la fois — celle que pointe le symlink `instance`.
|
||||
Toutes les commandes `make` (instancier, deployer, creer-vm, prouver…) visent l'active.
|
||||
|
||||
**Voir la flotte** — qui existe, laquelle est active (`★`), leur index, plage VLAN,
|
||||
statut fédéré/local et production ; signale toute **collision d'index** :
|
||||
|
||||
```bash
|
||||
make instances
|
||||
```
|
||||
```
|
||||
★ OPS-Chezlepro 13 1131-1136 fédérée prod
|
||||
OPS-Technolibre 2 1021-1026 fédérée
|
||||
OPS-Chezlepro-lab 1 1011-1016 local
|
||||
```
|
||||
|
||||
**Basculer l'active** — le symlink, avec garde-fous (le dossier existe, `instance` est
|
||||
bien un symlink) :
|
||||
|
||||
```bash
|
||||
make instance-courante # quelle instance est montée ?
|
||||
make instance-utiliser NOM=OPS-Chezlepro # bascule sur la production
|
||||
make instance-utiliser NOM=OPS-Chezlepro-lab # bascule sur le bac à sable
|
||||
make instance-utiliser NOM=OPS-Technolibre # bascule (l'inventaire suit le lien)
|
||||
```
|
||||
|
||||
Rien à « recharger » : l'inventaire vit **dans** le dépôt de l'instance, il suit le lien.
|
||||
Le GUI (onglet **Réseau**) montre la même flotte et les collisions ; la bascule reste au
|
||||
CLI (chirurgie de symlink, mal placée dans une interface web).
|
||||
|
||||
**Deux réflexes.** (1) Avant tout déploiement, `make instance-courante` : la seule vraie
|
||||
façon de se tromper est de déployer sur la mauvaise flotte (la colonne `prod` est là pour
|
||||
ça). (2) Un **index unique** par instance fédérée — `make instances` le crie, la preuve
|
||||
**P21** le refuse dans le harnais, mais choisis-le distinct dès la création.
|
||||
|
||||
Sans toucher au symlink (utile en CI ou pour du parallèle) :
|
||||
|
||||
```bash
|
||||
|
|
@ -57,6 +81,29 @@ make deployer SETOPS_INVENTAIRE=../OPS-ClientX/inventories/principal/hosts.yml H
|
|||
L'inventaire est détecté de façon **rétro-compatible** : `principal` > `production` >
|
||||
`lab` (les anciennes instances à double inventaire continuent de marcher).
|
||||
|
||||
## Comment le moteur découvre les instances
|
||||
|
||||
**Par convention de disposition, pas par un registre.** Il n'y a ni base de données ni
|
||||
fichier qui liste les instances. `scripts/instances.py` et `scripts/devis_reseau.py` font
|
||||
simplement :
|
||||
|
||||
```python
|
||||
FRERES = RACINE.parent # le dossier qui CONTIENT Set-OPS-public
|
||||
FRERES.glob("*/plan/nomenclature.yml") # tout dépôt frère ayant un plan
|
||||
```
|
||||
|
||||
Une instance **est** donc un dossier : (1) **frère** du moteur (`../OPS-Chezlepro`,
|
||||
`../OPS-Technolibre`…), (2) portant un **`plan/nomenclature.yml`**, (3) avec un **`index`**.
|
||||
De là : **active** = ce que résout le symlink `instance` ; **fédérée** = `index` présent
|
||||
*et* `federe ≠ false`. Les **modèles** (`Set-OPS-Modeles/integral/…`) sont un cran plus
|
||||
profond — le glob ne les attrape pas, volontairement.
|
||||
|
||||
Conséquence : « inscrire » une instance = la déposer à côté des autres. Rien à éditer,
|
||||
aucune synchronisation, donc aucune dérive « inscrite mais absente ». Le revers — la flotte
|
||||
étant « ce qui est là » — est qu'un simple dossier frère (le bac à sable) y apparaît :
|
||||
d'où le drapeau `federe: false` pour l'écarter du réseau convergé, et `make instances` +
|
||||
P21 pour garder un regard et un filet.
|
||||
|
||||
## `setops_production` — un attribut, pas une catégorie
|
||||
|
||||
Le déploiement réel est permis sur **toute instance** (un bac à sable déploie sur *son*
|
||||
|
|
@ -71,7 +118,8 @@ sable met `false` et affiche « bac à sable ».
|
|||
structure ci-dessus. Le plus simple : copier un modèle de `exemples/modeles/` ou
|
||||
l'instance bac à sable comme point de départ.
|
||||
2. Régler l'identité (`10-intrants.yml` : domaine **distinct**), le réseau
|
||||
(`nomenclature.yml` : `supernet` **distinct**) et la cible Proxmox (`proxmox.yml`).
|
||||
(`nomenclature.yml` : **`index` distinct** — tout l'adressage en dérive, cf. plus bas)
|
||||
et la cible Proxmox (`proxmox.yml`).
|
||||
3. Créer la voûte unique depuis le gabarit (cf. [`config-proxmox.md`](config-proxmox.md)) :
|
||||
`cp exemples/vault.exemple.yml …/group_vars/all/vault.yml` puis `ansible-vault encrypt`.
|
||||
4. `make instance-utiliser NOM=OPS-ClientX` puis `make instancier-appliquer`,
|
||||
|
|
@ -88,29 +136,39 @@ moteur, isolé. C'est la base concrète de la **portabilité des tenants**.
|
|||
> (NetBox, AWX, un coffre type Vault HashiCorp) **à adopter aux seuils**, pas à
|
||||
> réimplémenter dans le moteur. Cf. [`positionnement.md`](positionnement.md).
|
||||
|
||||
## Adressage fédéré (index d'instance)
|
||||
## Adressage fédéré : tout dérive du seed `index`
|
||||
|
||||
Pour que plusieurs écosystèmes **coexistent et puissent s'interconnecter** sans
|
||||
collision, chaque instance de production reçoit un **index** (déclaré en tête de
|
||||
`plan/nomenclature.yml` : `index: N`). Il pilote :
|
||||
Pour que plusieurs écosystèmes **coexistent** sur une même fabric sans collision, chaque
|
||||
instance reçoit un **`index`** (unique champ d'adressage de `plan/nomenclature.yml` :
|
||||
`index: N`). **Rien d'autre n'est écrit à la main** — la nomenclature ne garde que le
|
||||
*modèle* (libellés de zones + placement des fonctions) ; supernet, sous-réseaux,
|
||||
passerelles, VLAN et VMID se **dérivent** (`scripts/inventory_rules` : `supernet_de`,
|
||||
`base3_de`, `passerelle_de`, `vlan_de`). Changer `index` rederive tout le réseau — et la
|
||||
preuve **P20** interdit tout adressage stocké.
|
||||
|
||||
- le **supernet** : `10.(10 + index).0.0/16` (zéro chevauchement) ;
|
||||
- le **préfixe VMID** : `index·catégorie·service·séq` (ex. `21101`) au lieu du `9` fixe.
|
||||
Ce que `index` dérive (modèle à 6 zones) :
|
||||
|
||||
| Instance | index | Supernet | VMID |
|
||||
| --- | --- | --- | --- |
|
||||
| Chezlepro | 1 | `10.11.0.0/16` | `1CSNN` |
|
||||
| Technolibre | 2 | `10.12.0.0/16` | `2CSNN` |
|
||||
| … | N | `10.(10+N).0.0/16` | `N·CSNN` |
|
||||
| Bacs à sable | *(aucun)* | `172.19.x` ad-hoc | `9CSNN` (défaut) |
|
||||
| Élément | Formule | Exemple (index 13) |
|
||||
| --- | --- | --- |
|
||||
| Supernet | `10.(10+index).0.0/16` | `10.23.0.0/16` |
|
||||
| Sous-réseau de zone | `10.(10+index).(15+zone).0/24` | `10.23.16.0/24` (Frontière) |
|
||||
| Passerelle | `…(15+zone).1` | `10.23.16.1` |
|
||||
| VLAN sur le trunk | `1000 + index×10 + zone` | `1131`-`1136` |
|
||||
| VMID (ip-miroir) | `VLAN · octet-hôte · séq` (9 chiffres) | `113102101` |
|
||||
|
||||
Sans `index` déclaré, on retombe sur `9CSNN` (rétro-compatible) : c'est ce qu'utilisent
|
||||
les **bacs à sable**, qui vivent hors fédération (plages jetables `172.19.x`) et n'ont
|
||||
pas vocation à s'interconnecter.
|
||||
Les VLAN de deux index différents ne peuvent **mathématiquement pas** se chevaucher
|
||||
(l'espacement ×10 laisse de la marge). Deux instances de **même** index, elles, ont les
|
||||
mêmes VLAN/VMID : c'est la collision que `make instances` et **P21** attrapent.
|
||||
|
||||
Plafond : un index à 1 chiffre = **9 écosystèmes** prod fédérés (VMID à 5 chiffres) ;
|
||||
élargir l'index pour davantage. Au-delà d'une poignée d'instances co-localisées, confier
|
||||
l'allocation à un **IPAM** (NetBox) plutôt qu'au moteur — cf. `positionnement.md`.
|
||||
**Bacs à sable.** Un bac à sable local (labo) a lui aussi un `index`, mais on le marque
|
||||
`federe: false` : il est alors **exclu du réseau convergé** (devis, `make instances` le
|
||||
montre « local »). Il garde son adressage dérivé et reste déployable sur *son* infra.
|
||||
|
||||
**Plafond théorique : 245 écosystèmes fédérés** (index 1 à 245), borné par l'IPv4
|
||||
`10.(10+index)` (2ᵉ octet 11→255). Le VLAN (≤ 4094) autorise jusqu'à 308, le VMID bien
|
||||
plus — c'est donc l'adressage IP qui plafonne. Au-delà d'une poignée d'instances
|
||||
co-localisées, confier l'allocation à un **IPAM** (NetBox) plutôt qu'au moteur — cf.
|
||||
[`positionnement.md`](positionnement.md).
|
||||
|
||||
## Voir aussi
|
||||
- [`meta-classe.md`](meta-classe.md) — le plan d'une instance instancie sa flotte.
|
||||
|
|
|
|||
|
|
@ -1255,9 +1255,9 @@ HTML = r"""<!doctype html>
|
|||
<div><span class="chip chi-clair">clair</span> non chiffré (local / edge)</div></div>
|
||||
<div class="hint-dep" style="margin-top:10px">Lecture seule. Éditer les <code>meta/flux.yml</code> des rôles, puis <code>make flux</code>.</div></div>`;
|
||||
} else if (vuePrincipale === 'reseau') {
|
||||
d.innerHTML = `<div class="detail"><div class="section-tete">Devis réseau</div>
|
||||
<div class="hint-dep">VLANs + SVIs (passerelles) + ACLs d'isolation inter-tenant, dérivés des nomenclatures <code>ip-miroir</code> (VLAN = 1000 + index×10 + zone, unique sur le trunk).</div>
|
||||
<div class="hint-dep" style="margin-top:10px">Lecture seule, régénéré du plan — <code>make devis-reseau</code>. À adapter à ta plateforme (NX-OS/IOS-XE, HSRP, route par défaut vers OPNsense, port de trunk).</div></div>`;
|
||||
d.innerHTML = `<div class="detail"><div class="section-tete">Réseau & fédération</div>
|
||||
<div class="hint-dep">Chaque instance fédérée dérive son adressage de son seed <code>index</code> (VLAN = 1000 + index×10 + zone, unique sur le trunk). La vue liste la flotte, signale toute collision d'index, et génère le devis des switches.</div>
|
||||
<div class="hint-dep" style="margin-top:10px">Lecture seule, régénéré du plan — <code>make instances</code> / <code>make devis-reseau</code>. Le devis est à adapter à ta plateforme (NX-OS/IOS-XE, HSRP, route par défaut vers OPNsense, port de trunk).</div></div>`;
|
||||
} else {
|
||||
d.innerHTML = `<div class="detail"><div class="section-tete">Reconstruction</div>
|
||||
<div class="hint-dep">L'ordre part du socle et remonte : chaque couche suppose la précédente debout. Le tri intra-couche vient du graphe des dépendances.</div>
|
||||
|
|
@ -1283,21 +1283,42 @@ HTML = r"""<!doctype html>
|
|||
}
|
||||
|
||||
let devisReseauTexte = '';
|
||||
function tableauFlotte(inst, coll) {
|
||||
if (!inst.length) return '<div class="vide" style="padding:14px">Aucune instance découverte (dépôts frères avec plan/nomenclature.yml).</div>';
|
||||
const banniere = Object.keys(coll || {}).length
|
||||
? `<div class="hint-dep" style="color:var(--danger);border:1px solid var(--danger);border-radius:8px;padding:8px 10px;margin-bottom:8px">⚠ COLLISION d'index entre instances fédérées : ${Object.entries(coll).map(([i, n]) => 'index ' + i + ' → ' + n.map(echapper).join(', ')).join(' ; ')} — mêmes VLAN/VMID sur le trunk.</div>`
|
||||
: '';
|
||||
const lignes = inst.map(i => {
|
||||
const vlan = i.vlans ? (i.vlans[0] + '-' + i.vlans[1]) : '—';
|
||||
const fed = i.federe ? '<span class="chip">fédérée</span>' : '<span class="chip" style="opacity:.6">local</span>';
|
||||
const prod = i.production ? '<span class="chip chi-tls-requis">prod</span>' : '';
|
||||
return `<tr>
|
||||
<td class="mono">${i.actif ? '★ ' : ''}${echapper(i.nom)}</td>
|
||||
<td class="mono">${i.index}</td>
|
||||
<td class="mono">${vlan}</td>
|
||||
<td>${fed} ${prod}</td></tr>`;
|
||||
}).join('');
|
||||
return `${banniere}<table class="tbl-lecture"><thead><tr><th>Instance</th><th>Index</th><th>VLAN</th><th>Statut</th></tr></thead><tbody>${lignes}</tbody></table>
|
||||
<div class="hint-dep" style="margin-top:6px">★ = active (symlink <code>instance</code>). Basculer en CLI : <code>make instance-utiliser NOM=<dépôt></code>.</div>`;
|
||||
}
|
||||
async function dessinerReseau() {
|
||||
const cible = document.getElementById('grilles');
|
||||
cible.innerHTML = '<div class="vide" style="padding:14px">Génération du devis…</div>';
|
||||
cible.innerHTML = '<div class="vide" style="padding:14px">Lecture de la fédération…</div>';
|
||||
try {
|
||||
const rep = await fetch('/api/devis-reseau', {headers: {'X-Jeton': JETON}});
|
||||
const data = await rep.json();
|
||||
if (!rep.ok) { cible.innerHTML = '<div class="vide" style="padding:14px">' + echapper(data.erreur || 'Indisponible.') + '</div>'; return; }
|
||||
const [ri, rd] = await Promise.all([
|
||||
fetch('/api/instances', {headers: {'X-Jeton': JETON}}),
|
||||
fetch('/api/devis-reseau', {headers: {'X-Jeton': JETON}}),
|
||||
]);
|
||||
const di = await ri.json(), data = await rd.json();
|
||||
if (!rd.ok) { cible.innerHTML = '<div class="vide" style="padding:14px">' + echapper(data.erreur || 'Indisponible.') + '</div>'; return; }
|
||||
devisReseauTexte = data.config || '';
|
||||
if (!devisReseauTexte) { cible.innerHTML = '<div class="vide" style="padding:14px">Aucune instance fédérée (schéma ip-miroir) trouvée.</div>'; return; }
|
||||
const tenants = (data.tenants || []).map(t => echapper(t.nom) + ' (idx ' + t.index + ')').join(' · ');
|
||||
cible.innerHTML = `<div class="section-grille">
|
||||
<div class="section-tete">Devis config réseau — switches
|
||||
<span class="hint-dep" style="margin-left:auto">${tenants}</span>
|
||||
<button type="button" class="fantome" style="margin-left:10px" onclick="copierDevis()">Copier</button></div>
|
||||
<pre class="devis-reseau">${echapper(devisReseauTexte)}</pre></div>`;
|
||||
const flotte = `<div class="section-grille"><div class="section-tete">Flotte d'instances <span class="compte">${(di.instances || []).length}</span></div>${tableauFlotte(di.instances || [], di.collisions || {})}</div>`;
|
||||
const devis = devisReseauTexte
|
||||
? `<div class="section-grille" style="margin-top:12px"><div class="section-tete">Devis config réseau — switches (instances fédérées)
|
||||
<button type="button" class="fantome" style="margin-left:auto" onclick="copierDevis()">Copier</button></div>
|
||||
<pre class="devis-reseau">${echapper(devisReseauTexte)}</pre></div>`
|
||||
: '<div class="hint-dep" style="margin-top:12px">Aucune instance fédérée : pas de devis convergé à générer.</div>';
|
||||
cible.innerHTML = flotte + devis;
|
||||
} catch (e) {
|
||||
cible.innerHTML = '<div class="vide" style="padding:14px">Erreur : ' + echapper(e.message) + '</div>';
|
||||
}
|
||||
|
|
@ -2495,6 +2516,13 @@ class Gestionnaire(BaseHTTPRequestHandler):
|
|||
"config": devis_reseau.generer(tenants) if tenants else "",
|
||||
"tenants": [{"nom": nom, "index": n["index"]} for nom, _, n in tenants],
|
||||
})
|
||||
elif chemin == "/api/instances":
|
||||
import instances as _inst
|
||||
liste = _inst.decouvrir()
|
||||
self.repondre_json(200, {
|
||||
"instances": liste,
|
||||
"collisions": {str(k): v for k, v in _inst.collisions(liste).items()},
|
||||
})
|
||||
else:
|
||||
self.repondre_json(404, {"erreur": "Introuvable"})
|
||||
except Exception as exc:
|
||||
|
|
|
|||
Loading…
Reference in a new issue