D-80 : un tenant est agnostique de son underlay, a trois cles pres
Formulation de l'exploitant, meilleure que celle du depot. Le commentaire disait « la fabric reste celle de l'hebergeur, quel que soit le tenant actif » — vrai, mais centre sur l'HEBERGEUR. Le cadrage juste est centre sur le TENANT : les deux symlinks composent deux axes INDEPENDANTS (instance = quel tenant, underlay.yml = sur quelle fabric). Tout l'adressage derive du seed index : le plan se deplace d'une fabric a l'autre sans y toucher. Ce qui ne se deplace pas tient en TROIS CLES — proxmox_clone_noeud, _stockage, _pont. Elles vivent cote tenant (c'est lui qui choisit ou se poser) mais nomment des objets de l'hebergeur. Trois, pas trente : c'est ce qui separe « portable » de « theoriquement portable ». P37 confronte ces trois noms aux listes de proxmox-hebergeur.yml, trouve par derivation du symlink underlay.yml. Un nom absent est un ecart STATIQUE (D-75) — sans quoi une faute de frappe ne se decouvre qu'au premier clone, apres quarante minutes. Eprouvee en negatif sur les trois cles. Non verifie et dit comme tel : le gabarit (_vmid_modele) est un objet du cluster, pas une liste declaree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
671fa1fc60
commit
199783f5fd
4 changed files with 102 additions and 1 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -1,5 +1,42 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-13 — D-80 : un tenant est agnostique de son underlay, à trois clés près
|
||||
|
||||
Formulation de l'exploitant, meilleure que celle du dépôt. Le commentaire disait *« la
|
||||
fabric reste celle de l'hébergeur, quel que soit le tenant actif »* — vrai, mais centré
|
||||
sur l'hébergeur. Le cadrage juste est centré sur le **tenant** : les deux symlinks
|
||||
composent **deux axes indépendants**.
|
||||
|
||||
```
|
||||
instance -> quel tenant
|
||||
underlay.yml -> sur quelle fabric
|
||||
```
|
||||
|
||||
Tout l'adressage d'un tenant dérive du seed `index` : son plan se déplace d'une fabric à
|
||||
l'autre **sans y toucher**. Ce qui ne se déplace pas tient en **trois clés** :
|
||||
|
||||
```
|
||||
proxmox_clone_noeud proxmox_clone_stockage proxmox_clone_pont
|
||||
```
|
||||
|
||||
Elles vivent côté tenant — c'est lui qui choisit où se poser — mais elles **nomment des
|
||||
objets de l'hébergeur**. Trois, pas trente : c'est ce qui sépare « portable » de
|
||||
« théoriquement portable ».
|
||||
|
||||
### P37 — le placement est confronté à ce que l'hébergeur offre
|
||||
|
||||
L'écart est entièrement lisible : le tenant déclare trois noms, l'hébergeur déclare ses
|
||||
listes dans `proxmox-hebergeur.yml`, trouvé par dérivation du symlink `underlay.yml`. Un
|
||||
nom absent est un écart **statique** (D-75). Sans cette preuve, une faute de frappe ne se
|
||||
découvre qu'au premier clone — après quarante minutes de déploiement.
|
||||
|
||||
Éprouvée en négatif sur les trois clés : un nœud, un stockage et un pont inexistants sont
|
||||
refusés, avec la liste de ce qui est réellement offert.
|
||||
|
||||
**Non vérifié, et dit comme tel** : le gabarit (`_vmid_modele`) est un objet du cluster,
|
||||
pas une liste déclarée. Seul le cluster peut dire s'il existe.
|
||||
|
||||
|
||||
## 2026-08-13 — Reconstruction **d'un trait** : 43 groupes, 0 échec, 37 minutes
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (36 OK · 0 echec · 0 saute)
|
||||
- **Verdict** : ✅ CONFORME (37 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 39 document(s) declarent leur lecteur (16 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (OPS-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ sont les seules vérifiables.
|
|||
|
||||
| **D-77** | **L'underlay d'un site dérive du même `index` que son tenant**, dans la **bande basse** `10.<index>.0–15.x` ; les réseaux de **stockage** en sortent et sont **identiques partout**, en `192.168.<vlan>.0/24` | tant qu'il n'existait qu'un site, `10.0.x.x` suffisait. Deux sites qui doivent se joindre — reprise mutuelle, sauvegardes croisées, exploitation à distance — **ne peuvent pas** porter les mêmes plages : chaque routeur croit que le réseau est chez lui. Un « numéro de site » a d'abord été proposé : **rejeté**, c'était un SECOND SEED à tenir et à synchroniser, alors que tout dérive déjà d'`index` (D-17). La bande basse est sûre **par la règle, pas par chance** : les zones valent `10.<index>.(15+categorie).0/24`, donc troisième octet ≥ 16 — les octets 0 à 15 ne sont jamais alloués. Les troisièmes et derniers octets de l'underlay actuel sont ainsi **préservés** : seul le deuxième change, et l'invariant `.1` (D-04) survit intact. Le **stockage** en est sorti parce qu'il est jumbo, non routé, et ne quitte jamais son site : il n'a aucun besoin d'être unique, et l'aligner sur le VLAN (`192.168.20.x` ↔ VLAN 20) fait dire son VLAN à l'adresse. Contrepartie assumée : ces réseaux ne pourront jamais traverser un lien inter-sites — sans conséquence, puisque ce qui voyage entre deux sites est l'**état**, par le dépôt de sauvegarde, pas le stockage bloc | `underlay.yml` (`index`), `scripts/underlay.py`, `docs/preparer-un-site-hebergeur.md` | **P23** |
|
||||
|
||||
| **D-80** | **Un tenant est agnostique de son underlay.** Les deux symlinks composent deux axes INDEPENDANTS : `instance` designe le tenant, `underlay.yml` la fabric qui l'accueille. Seule exception : une **liaison de placement** de trois cles | le commentaire disait « la fabric reste celle de l'hebergeur, quel que soit le tenant actif » — vrai, mais centre sur l'hebergeur. Le cadrage juste est centre sur le TENANT : tout son adressage derive du seed `index`, donc son plan se deplace d'une fabric a l'autre **sans y toucher**. Ce qui ne se deplace pas, c'est le **placement** — `proxmox_clone_noeud`, `_stockage`, `_pont` — qui vit cote tenant parce que c'est lui qui choisit ou se poser, mais qui NOMME des objets de l'hebergeur. Trois cles, pas trente : c'est ce qui separe « portable » de « theoriquement portable ». Le gabarit (`_vmid_modele`) s'y ajoute, mais il n'est pas verifiable statiquement — c'est un objet du cluster, pas une liste declaree | `underlay.py`, `inventories/*/group_vars/proxmox.yml`, `proxmox-hebergeur.yml` | **P37** |
|
||||
| **D-79** | Le filtrage que l'hyperviseur applique aux VM tourne encore sur **iptables *legacy***. Le passer à `proxmox-firewall` (nftables natif) est une **dette à rembourser**, pas une option — et c'est le même geste qui rend le **pare-feu de VNet** opérant. Décidée le 2026-08-12, **différée après la reconstruction** | mesuré sur `asgard` : `iptables v1.8.9 (legacy)` — pas `(nf_tables)` ; `nftables v1.0.6` installé mais inutilisé par Proxmox ; `proxmox-firewall 0.7.1` **installé et absent des services en cours** ; seul `pve-firewall 5.1.3` tourne. Le pare-feu de VNet est **entièrement expressif** (`type/action/proto/source/dest/dport`, et `policy_forward` ∈ `ACCEPT,DROP` — donc un vrai default-deny inter-zone) : une règle d'essai a été créée, relue, puis retirée. **Mais il n'est implémenté QUE par le moteur nftables** : posée aujourd'hui, une règle serait acceptée, stockée, visible dans l'interface, et n'appliquerait rien. Trois gains dans le même geste — le moteur rejoint la doctrine (« tout est nftables »), le pare-feu de VNet devient réel, et ses règles **survivent à la reconstruction** là où les groupes par VM doivent être ré-attachés à chaque cycle | `devis_proxmox_fw.py`, `docs/sdn-evpn.md` | P25 *(sur le mécanisme actuel)* |
|
||||
|
||||
> **Ce qui n'a PAS été établi, et qu'il faudra éprouver.** `nft list tables` exige root et le
|
||||
|
|
|
|||
|
|
@ -524,6 +524,66 @@ def preuve_diff_vide_toutes_instances() -> tuple[bool, str]:
|
|||
f"{', '.join(verifiees)} : plan et inventaire applique coincident.")
|
||||
|
||||
|
||||
def preuve_placement_chez_hebergeur() -> tuple[bool, str]:
|
||||
"""Le placement d'un tenant designe des objets que son hebergeur offre REELLEMENT.
|
||||
|
||||
D-80 : un tenant est agnostique de l'underlay — tout son adressage derive du seed.
|
||||
Il porte pourtant une LIAISON DE PLACEMENT vers l'hebergeur qui l'accueille : sur
|
||||
quel noeud cloner, sur quel stockage poser le disque, sur quel pont brancher la
|
||||
carte. Trois valeurs, cote tenant parce que c'est lui qui choisit ou se poser, mais
|
||||
qui NOMMENT des objets de l'hebergeur.
|
||||
|
||||
C'est donc la seule surface qui casse quand un tenant change de fabric — et elle est
|
||||
entierement lisible : le tenant declare trois noms, l'hebergeur declare ses listes
|
||||
(`proxmox-hebergeur.yml`, trouve par derivation du symlink `underlay.yml`). Un nom
|
||||
absent est un ecart STATIQUE. Sans cette preuve, une faute de frappe ne se decouvre
|
||||
qu'au premier clone — apres quarante minutes de deploiement (D-75).
|
||||
|
||||
CE QU'ELLE NE VERIFIE PAS : le GABARIT (`proxmox_clone_vmid_modele`). C'est un objet
|
||||
du cluster, pas une liste declaree par l'hebergeur : seul le cluster peut dire s'il
|
||||
existe. `make hote-afficher` et le clone lui-meme s'en chargent.
|
||||
"""
|
||||
import yaml as _y
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
import underlay as underlay_mod
|
||||
|
||||
c = underlay_mod.chemin()
|
||||
if c is None:
|
||||
return True, "Aucun underlay monte : aucun hebergeur a confronter."
|
||||
heb = c.resolve().parent / "proxmox-hebergeur.yml"
|
||||
if not heb.is_file():
|
||||
return True, f"Aucun {heb.name} chez l'hebergeur monte : rien a confronter."
|
||||
|
||||
offert = _y.safe_load(heb.read_text(encoding="utf-8")) or {}
|
||||
tenant_f = RACINE / INV
|
||||
tenant_f = tenant_f.parent / "group_vars" / "proxmox.yml"
|
||||
if not tenant_f.is_file():
|
||||
return True, "Le tenant monte ne declare aucun placement Proxmox."
|
||||
place = _y.safe_load(tenant_f.read_text(encoding="utf-8")) or {}
|
||||
|
||||
controles = (
|
||||
("proxmox_clone_noeud", "proxmox_noeuds", "noeud"),
|
||||
("proxmox_clone_stockage", "proxmox_stockages", "stockage"),
|
||||
("proxmox_clone_pont", "proxmox_ponts", "pont"),
|
||||
)
|
||||
manques, verifies = [], []
|
||||
for cle_t, cle_h, libelle in controles:
|
||||
voulu = place.get(cle_t)
|
||||
dispo = offert.get(cle_h) or []
|
||||
if voulu is None or not dispo:
|
||||
continue
|
||||
verifies.append(libelle)
|
||||
if str(voulu) not in [str(x) for x in dispo]:
|
||||
manques.append(f"{libelle} « {voulu} » absent de {cle_h} "
|
||||
f"({', '.join(str(x) for x in dispo)})")
|
||||
if manques:
|
||||
return False, (f"le placement du tenant designe {len(manques)} objet(s) que "
|
||||
f"l'hebergeur monte n'offre pas : " + " | ".join(manques)
|
||||
+ " — corriger le placement, ou monter le bon `underlay.yml`.")
|
||||
return True, (f"placement confronte a l'hebergeur monte ({heb.parent.name}) : "
|
||||
f"{', '.join(verifies)} — tous offerts.")
|
||||
|
||||
|
||||
def preuve_etat_sauvegarde() -> tuple[bool, str]:
|
||||
"""Tout hote qui detient de l'etat non regenerable porte `client_backup`.
|
||||
|
||||
|
|
@ -826,6 +886,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_base_par_consommateur},
|
||||
{"id": "P36", "titre": "Tout detenteur d'etat porte une sauvegarde", "refs": [],
|
||||
"func": preuve_etat_sauvegarde},
|
||||
{"id": "P37", "titre": "Le placement du tenant existe chez son hebergeur", "refs": [],
|
||||
"func": preuve_placement_chez_hebergeur},
|
||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||
"cmds": [[sys.executable, "scripts/verifier_ports.py"]]},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue