conception : un tronc commun, deux classes (SITE et LOCATAIRE)
docs/conception-contextes.md, arretee avec l exploitant : le releve des devinettes de contexte et des echanges site <-> locataire, le contrat en deux fiches, le poste selecteur, le chemin en neuf etapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0d7578b367
commit
42e23885cd
2 changed files with 287 additions and 0 deletions
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -1,5 +1,33 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-04 (78) — Conception : un tronc commun, deux classes (SITE et LOCATAIRE)
|
||||
|
||||
**Demande de l'exploitant** : en finir avec les « classes à tout faire ». Deux grandes classes,
|
||||
SITE et LOCATAIRE, qui héritent du tronc commun Set-OPS et surchargent ce qui dépend de leur
|
||||
contexte ; des consoles cohérentes avec leur contexte.
|
||||
|
||||
**Page arrêtée avec lui** : `docs/conception-contextes.md`. Rien n'est encore construit.
|
||||
|
||||
**Le relevé qui la fonde** :
|
||||
- 33 scripts devinent leur contexte, à partir de cinq indices (liens `instance/` et
|
||||
`underlay.yml`, variables `SETOPS_*`, dossiers frères, `SITE-*`). Trois défauts passés en
|
||||
viennent, dont le `make ci` mélangé du jour.
|
||||
- Le site informe le locataire par **trois canaux**, sans fiche : des copies à la main (douze
|
||||
valeurs, un certificat, une valeur hors contrat), une **lecture directe du site par
|
||||
l'instancier** (mesurée : sans le site monté, l'inventaire de Technolibre perd son serveur de
|
||||
temps, son SDN et sa délégation DNS), et le génome.
|
||||
- Le site **fouille** six fichiers internes du locataire et **recalcule** ses flux avec sa
|
||||
propre version du moteur.
|
||||
|
||||
**Décisions** : `Ecosysteme` → `Site`, `Locataire` ; un **contrat** en deux fiches générées
|
||||
(la fiche du site pour chaque locataire, **déposée par le site** dans le dépôt du locataire ;
|
||||
la face réseau du locataire, flux déjà résolus compris) ; le poste devient un **sélecteur** ;
|
||||
le contexte actif se nomme dans un fichier `contexte` ; `SITE-Modele` à côté d'`OPS-Modele` ;
|
||||
un `parente.yml` pour le site. On commence par le moteur.
|
||||
|
||||
**Ce que ça donne en plus** : la portabilité. Un locataire qui change de site reçoit la fiche
|
||||
du nouveau, et son dépôt ne contient plus rien d'interne à l'ancien.
|
||||
|
||||
## 2026-10-04 (77) — Release `v2026.10.04` : les deux locataires reconstruits sur `f42d30b`
|
||||
|
||||
**Pourquoi une seconde fois le même jour.** (72) prouvait `5340220`. Ensuite, (73) et (74) ont
|
||||
|
|
|
|||
259
docs/conception-contextes.md
Normal file
259
docs/conception-contextes.md
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# Contextes : un tronc commun, deux classes (SITE et LOCATAIRE)
|
||||
|
||||
> **Statut : arrêtée avec l'exploitant le 2026-10-04.** Rien n'est encore construit ; les
|
||||
> décisions sont au §7, le chemin au §6.
|
||||
|
||||
## 1. Le problème, mesuré
|
||||
|
||||
Le moteur ne sait pas dans quel contexte il tourne : **chaque script le devine**. Relevé du
|
||||
2026-10-04 : **33 scripts** font leur propre déduction, à partir de cinq indices différents.
|
||||
|
||||
| Indice | Ce qu'on en déduit | Scripts |
|
||||
|---|---|---|
|
||||
| lien `instance/` ou `SETOPS_INSTANCE` | « un locataire est monté » | 23 |
|
||||
| lien `underlay.yml` ou `SETOPS_UNDERLAY` | « un site est monté » ; son plan est à côté | 8 |
|
||||
| `SETOPS_INVENTAIRE` | quel inventaire de locataire lire | 8 |
|
||||
| `../*/plan/nomenclature.yml` | « la fédération », les locataires frères | 11 |
|
||||
| `../SITE-*/underlay.yml` | « les sites » | 2 |
|
||||
|
||||
Les indices ne concordent pas toujours, et chaque désaccord a déjà produit un défaut silencieux :
|
||||
|
||||
- **2026-08-14** : `frontiere-plan` voulait poser sur la frontière de Technolibre les règles de
|
||||
Chezlepro. « La fédération » valait « les locataires de ce site », jusqu'au second site.
|
||||
- **2026-09-16** : la console du runner du site affichait zéro machine, sans erreur. Elle
|
||||
cherchait un inventaire de locataire là où il n'y en a pas.
|
||||
- **2026-10-04** : `make ci`, sur le poste, mélangeait le modèle public et les écosystèmes
|
||||
réels. P74 lisait `SETOPS_UNDERLAY` (le modèle) ; P82 lisait le lien `underlay.yml` et les
|
||||
dossiers frères (le site réel).
|
||||
|
||||
Les rôles Ansible, eux, ne posent pas ce problème : ils sont **déjà** le tronc commun. Un même
|
||||
`serveur_postgresql` sert au site et chez un locataire.
|
||||
|
||||
## 2. Le modèle
|
||||
|
||||
```
|
||||
Ecosysteme (tronc commun)
|
||||
/ \
|
||||
Site Locataire
|
||||
\ /
|
||||
`-- contrat --' (associations : un site A des locataires,
|
||||
un locataire A un site)
|
||||
```
|
||||
|
||||
### 2.1 Le tronc commun : `Ecosysteme`
|
||||
|
||||
Ce que tout écosystème possède, quel que soit son contexte :
|
||||
|
||||
- un **nom** et un **dépôt** (`SITE-Chezlepro`, `OPS-Technolibre`) ;
|
||||
- une **voûte** et sa clé (`~/.config/setops-vault-<dépôt>`) ;
|
||||
- un **plan** (`<dépôt>/plan/`) et un **index**, dont dérive son adressage (le site
|
||||
aussi depuis le 2026-09-20) ;
|
||||
- des **machines**, déployées par les **mêmes rôles** : socle, durcissement, PKI, journaux,
|
||||
métriques, supervision, sauvegarde de son propre état ;
|
||||
- une **filiation** : le moteur et le commit dont il descend ;
|
||||
- les **preuves communes** : lint, rendu des gabarits, adressage dérivé, etc.
|
||||
|
||||
Méthodes abstraites, que chaque classe **surcharge** : `inventaire()`, `machines()`,
|
||||
`preuves()`, `verbes()`, `console()`.
|
||||
|
||||
### 2.2 `Site(Ecosysteme)`
|
||||
|
||||
- **Déclaration** : `underlay.yml` (le matériel, les réseaux `site` et `fabric`) et `plan/`
|
||||
(`10-intrants.yml`, serveurs, applications, domaines, bases).
|
||||
- **Inventaire** : dynamique (`site_inventaire.py`). Le site ne dérive rien d'un plan de services ;
|
||||
sa déclaration est sa forme finale.
|
||||
- **Ce qu'il porte en propre** : le matériel (hyperviseurs, commutateurs, frontière),
|
||||
la matérialisation des VM (Proxmox), le SDN, le pare-feu Proxmox, la frontière OPNsense,
|
||||
le DNS public, le dépôt des sauvegardes des locataires, le cache et les artefacts, la forge
|
||||
du génome.
|
||||
- **Relation** : `locataires()`, la liste de `underlay.tenants` résolue en objets
|
||||
`Locataire`. Le site n'en lit que la **face réseau** (§2.4).
|
||||
|
||||
### 2.3 `Locataire(Ecosysteme)`
|
||||
|
||||
- **Déclaration** : `plan/` (nomenclature, serveurs, applications, bases, domaines).
|
||||
- **Inventaire** : généré (`instancier.py` → `hosts.yml`).
|
||||
- **Ce qu'il porte en propre** : la configuration de ses services, la remise au client.
|
||||
- **Relation** : `site()`, l'hébergeur que nomme `parente.yml`, résolu en objet `Site`. Le
|
||||
locataire n'en lit que les **intrants exposés** (§2.4).
|
||||
|
||||
### 2.4 Ce que le site et le locataire s'apprennent l'un à l'autre
|
||||
|
||||
Les deux entités **s'informent mutuellement**. Relevé du 2026-10-04 : qui décide de chaque
|
||||
information, où elle vit, et comment elle parvient à l'autre.
|
||||
|
||||
**Ce que chacun a sous la main.** Le runner du site porte le moteur, son dépôt **et ceux de
|
||||
ses locataires** (sans leurs voûtes). Le runner d'un locataire ne porte que le moteur et
|
||||
**son propre** dépôt. Le poste porte tout.
|
||||
|
||||
#### Le site informe le locataire, par trois canaux
|
||||
|
||||
**Canal 1 : des copies écrites à la main** dans le dépôt du locataire.
|
||||
|
||||
| Information | Décidée par | Tenue chez le site dans | Copiée chez le locataire dans | Contrôle |
|
||||
|---|---|---|---|---|
|
||||
| son **index** | le site | `underlay.yml` → `tenants` | `plan/nomenclature.yml` (`index`) | `underlay valider` |
|
||||
| son **adresse publique** | le site | `opnsense.yml` → `opnsense_ips_publiques` | `10-intrants.yml` (`ip_publique`) | — |
|
||||
| les **10 intrants de service** : résolveur, cache, binaires, forge du génome, cible de sauvegarde, DNS public, plan d'administration, passerelle | le site (dérivés de son plan, par `site_intrants.py`) | son plan | `10-intrants.yml`, et `serveur_ops.yml` pour la forge | `site_intrants.py --verifier`, seulement là où les deux dépôts sont présents (le poste) |
|
||||
| sa **racine de confiance** | le site | `ac-racine-site.crt` | le même fichier, copié | — |
|
||||
| *hors contrat* : un dépôt de la forge du site désigné par son adresse | — | — | `serveur_web_dorsal.yml` (Chezlepro) | **aucun** |
|
||||
|
||||
**Canal 2 : une lecture directe, au moment de générer l'inventaire.** `instancier.py` ouvre
|
||||
l'`underlay.yml` et le plan du site pour écrire le `hosts.yml` du locataire. Mesuré sur
|
||||
Technolibre, inventaire généré avec puis sans le site monté : **quatre variables changent**.
|
||||
|
||||
| Variable du locataire | Avec le site monté | Sans le site |
|
||||
|---|---|---|
|
||||
| `chrony_serveurs` | `10.0.4.1` (la frontière) | absente |
|
||||
| `proxmox_pont` | `t23appl` (le VNet SDN) | absente |
|
||||
| `proxmox_etiquette_vlan` | aucune (le SDN étiquette) | `1236` |
|
||||
| `serveur_resolveur_zones_deleguees` | `genese.internal` → `10.37.34.11` | absente |
|
||||
|
||||
**Canal 3 : le réseau.** Le runner du locataire **tire** son génome de la forge du site ; il est
|
||||
né de l'**insémination** par le runner du site.
|
||||
|
||||
#### Le locataire informe le site : le site lit et recalcule
|
||||
|
||||
| Information | Décidée par | Tenue chez le locataire dans | Parvient au site par |
|
||||
|---|---|---|---|
|
||||
| ses **zones** et son adressage | dérivés de l'index | `plan/nomenclature.yml` | le site **lit le fichier** |
|
||||
| les **VM à matérialiser** | le locataire | `plan/serveurs.yml` → `hosts.yml` | le site **lit les fichiers** (placement, clonage, pools, SDN) |
|
||||
| ses **flux** | ses rôles et son plan | `meta/flux.yml` des rôles (moteur), croisés avec son `hosts.yml` | le site **recalcule** lui-même, avec **sa** version du moteur et la totalité de l'inventaire du locataire → frontière, NAT, pare-feu Proxmox |
|
||||
| ses **domaines publics** | le locataire | `plan/domaines.yml` (+ `applications.yml`, `serveurs.yml`) | le site **lit les fichiers** → DNS public secondaire |
|
||||
| sa **clé de sauvegarde** | le locataire | `inventories/*/group_vars/serveur_backup.yml` | le site **lit le fichier** → compte Unix sur le dépôt |
|
||||
| ses **accès d'administration** | le locataire | `plan/acces.yml`, `nftables_admin_ssh` | le site **lit les fichiers** → pairs WireGuard, règles d'administration |
|
||||
|
||||
Le SDN, lui, ne prend aucun flux : il ne filtre pas. Il ne reçoit que l'index, dont il dérive
|
||||
la zone, les 6 VNets et les 6 sous-réseaux.
|
||||
|
||||
#### À l'exécution, entre machines
|
||||
|
||||
Ces échanges-là passent par le réseau, pas par les dépôts. Ils sont déjà déclarés en flux :
|
||||
le locataire **dépose** ses sauvegardes chez le site (SFTP), **tire** ses paquets, ses binaires
|
||||
et son génome, **entre** par le tunnel d'administration du site ; le site **réplique** les
|
||||
zones publiques du locataire (AXFR signé TSIG).
|
||||
|
||||
#### Ce que le relevé montre
|
||||
|
||||
1. **Le site fouille l'intérieur du locataire.** Six fichiers de son plan et de son inventaire,
|
||||
`group_vars` compris. Rien ne dit ce que le locataire **accepte** de montrer. Renommer un
|
||||
champ chez le locataire casse le site sans bruit.
|
||||
2. **Les flux sont calculés deux fois**, par le locataire pour ses `nftables` et par le site pour
|
||||
la frontière et Proxmox, chacun avec **sa** version du moteur. Ils concordent tant que les deux
|
||||
runners tiennent le même commit (c'était le cas le 2026-10-04), mais rien ne l'impose.
|
||||
3. **Le locataire vit de copies** : douze valeurs et un certificat, recopiés à la main, plus une
|
||||
valeur hors contrat. La garde qui compare ne tourne que sur le poste ; le runner du locataire
|
||||
ne peut pas savoir que sa copie a vieilli.
|
||||
4. **L'inventaire d'un locataire dépend du site monté au moment de le générer.** Généré sur le
|
||||
runner du locataire, qui n'a pas le dépôt du site, il perdrait son serveur de temps, son SDN
|
||||
et sa délégation DNS. Ça ne s'est jamais vu, parce que l'inventaire est toujours généré sur le
|
||||
poste puis versionné.
|
||||
5. **Deux décisions du site** (l'index, l'adresse publique) vivent en double.
|
||||
|
||||
#### Proposition : deux fiches, une dans chaque sens
|
||||
|
||||
Chacun **publie** ce qu'il donne à l'autre, dans une fiche **générée** par le moteur, jamais
|
||||
écrite à la main. Chacun ne lit que la fiche que l'autre lui destine. Plus aucune lecture
|
||||
croisée, plus aucun recalcul.
|
||||
|
||||
- **La fiche du site pour un locataire.** Tout ce que le site lui **attribue** (index, adresse
|
||||
publique) et lui **offre** : les 10 intrants, sa racine de confiance, et ce que l'instancier
|
||||
allait lire en douce (serveur de temps, délégation DNS, mode SDN et nom des VNets). Une fiche
|
||||
**par locataire** : aucun ne voit le plan du site ni ses voisins. L'instancier ne lit plus que
|
||||
cette fiche, et l'inventaire devient **identique où qu'on le génère**.
|
||||
- **La face réseau du locataire.** Ce qu'il **demande** au site : VM à matérialiser, zones,
|
||||
domaines publics, clé de sauvegarde publique, accès d'administration, et **ses flux déjà
|
||||
résolus** (adresses, ports, protocoles), ceux avec l'extérieur pour la frontière et ceux de
|
||||
chaque VM pour Proxmox. Le locataire génère déjà ses flux résolus (`flux-genere/*.nft`,
|
||||
`*.connectivite.json`) : la face réseau en est la partie destinée au site. Le site ne
|
||||
recalcule plus rien : il applique ce que le locataire publie, après l'avoir confronté à sa
|
||||
propre politique.
|
||||
- **Chaque fiche porte l'empreinte de sa source**, et une preuve de chaque côté vérifie que la
|
||||
fiche reçue correspond à ce que l'autre a publié.
|
||||
|
||||
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
||||
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
||||
|
||||
#### Ce que les fiches donnent : la portabilité
|
||||
|
||||
Un locataire qui change de site, pour un déménagement, un plan de reprise ou une émancipation,
|
||||
n'a plus qu'à **recevoir la fiche de son nouveau site**. Son dépôt ne contient plus rien
|
||||
d'interne à l'ancien : ni copie d'adresse, ni inventaire généré avec l'ancien site monté. En
|
||||
face, le nouveau site n'a qu'à lire sa **face réseau**. Aujourd'hui, la même bascule demande de
|
||||
corriger des copies dans plusieurs fichiers, puis de régénérer l'inventaire avec le nouveau site
|
||||
monté sur le poste.
|
||||
|
||||
## 3. Le poste : un sélecteur
|
||||
|
||||
Aujourd'hui, le poste monte les deux contextes **en même temps** (`instance/` + `underlay.yml`),
|
||||
et `ConsolePoste` hérite de `ConsoleLocataire`. Désormais :
|
||||
|
||||
- **Le poste choisit un contexte actif** : un site **ou** un locataire. La console ouvre celui-là,
|
||||
et seulement celui-là.
|
||||
- **Une opération qui traverse les deux** nomme ses objets au lieu de les deviner.
|
||||
`reconstruire-locataire` en est l'exemple : le site matérialise, puis le locataire monte.
|
||||
L'orchestration devient `site.materialiser(locataire)` puis `locataire.monter()`.
|
||||
- **Les runners ne changent pas** : celui du site n'a qu'un `Site`, celui d'un locataire qu'un
|
||||
`Locataire`. Leur contexte est désormais **dit**, plus déduit.
|
||||
|
||||
## 4. Qui surcharge quoi
|
||||
|
||||
| Méthode | Tronc commun | Site | Locataire |
|
||||
|---|---|---|---|
|
||||
| `inventaire()` | abstraite | script dynamique (`underlay.yml`) | `hosts.yml` généré du plan |
|
||||
| `machines()` | abstraite | VM du site + équipements | VM du plan |
|
||||
| `adressage()` | dérivé de l'index | zones `site` dérivées, liens `fabric` écrits | 6 zones dérivées |
|
||||
| `sauvegardes()` | son propre état, vérifié par restauration | + héberge les dépôts des locataires | dépose chez son site |
|
||||
| `supervision()` | sondes déclarées par les rôles | + matériel, fabric, frontière | — |
|
||||
| `raser()` / `reconstruire()` | — | `site_raser.py` | `raser.py`, `reconstruire_locataire.py` |
|
||||
| `preuves()` | preuves communes | + preuves de site (P23, P74, P82…) | + preuves de locataire |
|
||||
| `verbes()` | `verifier`, `publier`… | `site-*`, `frontiere-*`, `proxmox-*` | `appliquer`, `instancier`, `remise-*` |
|
||||
| `console()` | — | console SITE | console LOCATAIRE |
|
||||
|
||||
## 5. Ce qui ne change pas
|
||||
|
||||
- Les **rôles Ansible**, déjà communs.
|
||||
- Les **formats de plan** : pas dans ce chantier.
|
||||
- La **doctrine** d'`AGENTS.md`.
|
||||
|
||||
## 6. Le chemin, chaque pas prouvé avant le suivant
|
||||
|
||||
1. **`scripts/contexte.py`** : `Ecosysteme`, `Site`, `Locataire`, `contexte_actif()`,
|
||||
`Site.charger(nom)`, `Locataire.charger(nom)`. Tests unitaires. Rien ne l'utilise encore.
|
||||
2. **Les deux fiches**, générées à côté de l'existant sans rien remplacer :
|
||||
`site.fiche_pour(locataire)` et `locataire.face_reseau()`. Une preuve vérifie que chaque
|
||||
fiche dit **exactement** ce que les lectures croisées d'aujourd'hui produisent.
|
||||
3. **Les consommateurs basculent sur les fiches**, un par un : l'instancier sur la fiche du site
|
||||
(l'inventaire généré doit rester identique, octet pour octet) ; la frontière, Proxmox, le DNS
|
||||
public et les comptes de sauvegarde sur la face réseau (chaque devis doit rester inchangé).
|
||||
4. **`prouver.py`** : chaque preuve déclare son contexte (commun, site, locataire) et reçoit son
|
||||
écosystème du module.
|
||||
5. **Les autres scripts**, un par un, vérifiés par `make verifier` et par un devis inchangé.
|
||||
6. **Une preuve « aucune devinette, aucune lecture croisée »** : les indices du §1, et toute
|
||||
ouverture d'un fichier de l'autre contexte, interdits hors de `contexte.py`.
|
||||
7. **Les verbes du Makefile** rangés par contexte.
|
||||
8. **Les consoles** : le sélecteur et deux consoles (chantier suivant).
|
||||
9. **OPS-Modele** : un locataire modèle, et sans doute un site modèle, vérifiés chacun dans son
|
||||
contexte.
|
||||
|
||||
Après les étapes 3 et 5, une reconstruction prouve que la flotte n'a pas bougé.
|
||||
|
||||
## 7. Décisions et questions ouvertes
|
||||
|
||||
### Tranché par l'exploitant
|
||||
|
||||
- **Deux classes, `Site` et `Locataire`, qui héritent d'un tronc commun** (2026-10-04).
|
||||
- **Le poste est un sélecteur** : un contexte actif à la fois (2026-10-04).
|
||||
- **On commence par le moteur**, les consoles viennent ensuite (2026-10-04).
|
||||
- **Le site dépose sa fiche dans le dépôt du locataire** (2026-10-04), comme il y amorce déjà
|
||||
son runner. Le runner du locataire n'a besoin d'aucun accès au dépôt du site, et ne voit
|
||||
ni le plan du site ni ses voisins.
|
||||
|
||||
- **Le contexte actif se nomme dans un fichier `contexte` explicite** (2026-10-04), une seule
|
||||
valeur : `site:SITE-Chezlepro` ou `locataire:OPS-Technolibre`. Un sélecteur qui monte deux
|
||||
liens à la fois contredirait sa propre règle. Les liens `instance/` et `underlay.yml` restent
|
||||
le temps de la bascule, lus par le seul `contexte.py`.
|
||||
- **Un modèle SITE public, `SITE-Modele`**, à côté d'`OPS-Modele` (2026-10-04). Sans site, la
|
||||
CI ne peut exercer ni les preuves de site (P74, P81, P82) ni la fiche que le site dépose chez
|
||||
le locataire.
|
||||
- **Le site a aussi son `parente.yml`** (2026-10-04) : la filiation est dans le tronc commun.
|
||||
Loading…
Reference in a new issue