Set-OPS-Public/docs/multi-instances.md
Daniel Allaire ac85278366 preuve : P34 — chaque document declare son lecteur (D-74)
La refonte de ce matin posait une convention. Une convention qu'on n'outille
pas tient tant que quelqu'un y pense : c'est le raisonnement de D-70, applique
au corpus documentaire.

Etat de depart mesure : 2 documents sur 34 declaraient leur lecteur. Les 32
autres disaient leur SUJET — ce qui avait enfoui le runbook de reprise le plus
utile du depot au §6 de autorisation.md.

Les 38 le declarent desormais, lecteur determine document par document et non
colle au gabarit : l'exploitant (devis, migration de tenant, cycle de vie,
gabarit d'or), le mainteneur (conceptions, registres, carte), le lecteur
externe (ecosysteme-chezlepro), l'agent IA (MISE-A-JOUR-CODEX-CLAUDE).

Deux exemptions DERIVEES, pas listees — un chemin en dur aurait vieilli a la
premiere page ajoutee : un document qui s'annonce genere, et un fragment sans
titre. Les 13 exemptes verifies un par un ; aucun document ecrit a la main
n'est exempte par accident. La preuve ne lit que l'EN-TETE, ce qui empeche
frontiere-opnsense.md et plan-et-generation.md — qui parlent de generation
dans leur corps — d'etre exemptes a tort.

Eprouvee dans les deux sens. Elle a echoue seule des sa premiere execution en
nommant deux documents que mon inventaire avait manques (docs/audit/). Puis
test negatif delibere : declaration retiree de meta-classe.md -> ECHEC la
nommant ; restauree -> OK.

Ce qu'elle ne teste pas : que le lecteur declare soit le BON. Ca se juge en
revue ; elle garantit qu'on a du y penser.

P01–P34. Comptes perimes corriges au passage (AGENTS.md et devis-services.md
annoncaient encore 30 preuves).

Verifie : prouver.py 0 (34 OK), plan-recette inchange.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 07:53:04 -04:00

9 KiB
Raw Blame History

Multi-instances — un moteur, N écosystèmes

Pour qui : le mainteneur — la séparation entre le moteur et une instance.

Set-OPS sépare le moteur (ce dépôt : rôles, playbooks, scripts, GUI) de l'instance (un dépôt distinct : le plan d'un écosystème + son inventaire généré + ses secrets). Un seul moteur pilote autant d'instances que voulu.

« lab vs prod » n'est qu'un cas particulier : ce sont deux instances. Le même mécanisme sert un bac à sable, une production, ou les écosystèmes de plusieurs tenants — c'est le socle multi-tenant souverain.

Une instance = un écosystème autonome

Chaque instance est un dépôt séparé, monté dans le moteur par symlink (instance →). Elle possède tout ce qui lui est propre :

<instance>/
├── plan/                         ← la « méta-classe » (cf. meta-classe.md)
│   ├── nomenclature.yml          réseau, VLAN, fonctions
│   ├── serveurs.yml              VM = fonction + état + overrides
│   ├── applications.yml
│   ├── bases-donnees.yml
│   └── domaines.yml
└── inventories/principal/        ← un seul inventaire par instance
    ├── hosts.yml                 généré (make instancier-appliquer)
    └── group_vars/
        ├── all/
        │   ├── 00-instance.yml   setops_plan_dir, setops_production
        │   ├── 10-intrants.yml   identité (domaine_interne, fuseau)
        │   └── vault.yml         🔒 voûte UNIQUE de l'instance (chiffrée)
        ├── proxmox.yml           cible Proxmox de l'instance
        └── modeles_vm.yml        construction du golden template

Isolation totale entre instances : plan, inventaire, voûte, identité (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.

Gérer la flotte

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 :

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) :

make instance-courante                          # quelle instance est montée ?
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) :

make inventaire-ui SETOPS_INSTANCE=../OPS-ClientX
# ou viser un inventaire précis :
make deployer SETOPS_INVENTAIRE=../OPS-ClientX/inventories/principal/hosts.yml HOTE=

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 :

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 infra). Le drapeau setops_production (dans group_vars/all/) ne bloque rien : il marque la PRODUCTION pour exiger une confirmation renforcée au déploiement (badge rouge « PROD », bouton Déployer rouge, re-saisie du nom d'hôte). Une instance bac à sable met false et affiche « bac à sable ».

Créer une nouvelle instance

  1. Créer un dépôt d'instance frère de Set-OPS-public/ (ex. ../OPS-ClientX), avec la 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 : 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) : 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, make inventaire-ui.

Multi-tenant

Chaque tenant est simplement une instance de plus. Un client = make instance-utiliser NOM=OPS-<Client>, tout son écosystème instancié par le même moteur, isolé. C'est la base concrète de la portabilité des tenants.

Garde-fou de positionnement : si le nombre d'instances explose, le besoin d'un registre (lister/choisir, IPAM, secrets centralisés) relève d'outils établis (NetBox, AWX, un coffre type Vault HashiCorp) à adopter aux seuils, pas à réimplémenter dans le moteur. Cf. positionnement.md.

Adressage fédéré : tout dérive du seed index

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é.

Ce que index dérive (modèle à 6 zones) :

É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

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.

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.

Voir aussi