Set-OPS-Public/docs/multi-instances.md
Daniel Allaire c0f610be33 patient 0 efface : l index 29 est libere, et le site n ouvre plus rien a 10.29.0.0/16
Ses machines n existaient plus depuis le 2026-09-06 (D-83), mais son plan
restait sur disque : la federation lui reservait l index 29 et quatre machines
du site lui ouvraient SSH, apt, DNS et HTTPS. Les commentaires et documents
vivants gardent leur lecon sans le nommer ; les archives restent telles quelles.

Pas encore sur le reseau : les regles regenerees attendent le runner du site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:55:27 -04:00

192 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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** :
```bash
make instances
```
```
INSTANCE INDEX VLAN FEDERE PROD
OPS-Chezlepro-lab 13 1131-1136 LOCAL non
* OPS-Chezlepro 17 1171-1176 oui oui
OPS-Technolibre 23 1231-1236 oui non
* = instance active (symlink 'instance'). Basculer : make instance-utiliser NOM=<depot>
```
*(Sortie réelle du 2026-09-06. **Ne pas se fier aux index d'un exemple** : ils bougent —
celui de Chezlepro a changé au moins une fois, Technolibre est passé de 11 à 23. Le seul
endroit qui dit vrai est `plan/nomenclature.yml` de chaque dépôt, et cette commande.)*
**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-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 — **et sait basculer**,
par le bouton « Activer » de chaque instance. *(Ce paragraphe affirmait le contraire — « la
bascule reste au CLI » — jusqu'au 2026-09-06 ; c'était vrai avant que le bouton n'existe.)*
**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
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 :
```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** (`exemples/modeles/…` ici, `Set-OPS-modeles/…` pour les modèles privés) 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`](config-proxmox.md)) :
`cp exemples/vault.exemple.yml …/group_vars/all/vault.yml` puis `ansible-vault encrypt`.
**Et poser SA clé** — une voûte, une clé : `~/.config/setops-vault-ops-clientx`, nom
dérivé du dossier en minuscules. `python3 scripts/voutes.py etat` confirme que le moteur
la trouve.
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`](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.py` : `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.<index>.0.0/16` | `10.13.0.0/16` |
| Sous-réseau de zone | `10.<index>.(15+zone).0/24` | `10.13.16.0/24` (Frontière) |
| Passerelle | `…(15+zone).1` | `10.13.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 : le 2ᵉ octet IPv4.** `valider_index` borne l'index à **0–255**
(`INDEX_MIN`/`INDEX_MAX`, `scripts/inventory_rules.py`) — la garde est posée **à la source
de la dérivation**, donc aucune fonction ne peut fabriquer une adresse hors bornes, d'où
qu'on l'appelle. En pratique, **éviter 0** : `10.0.x` porte déjà les réseaux de service du
site. Le décalage de +10, retiré le 2026-08-12, confisquait dix valeurs — et surtout, il
empêchait de lire l'index directement dans l'adresse. Le VLAN (≤ 4094) autoriserait
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.
- [`config-proxmox.md`](config-proxmox.md) — voûte unique + cible Proxmox par instance.
- [`intrants-communs.md`](intrants-communs.md) — intrants de base d'une instance.