This repository has been archived on 2026-06-26. You can view files and clone it, but cannot push or open issues or pull requests.
Set-OPS/docs/plan-et-generation.md

180 lines
8.3 KiB
Markdown
Raw Permalink Normal View History

# Le plan et la génération d'inventaire (méta-classe)
Set-OPS ne s'édite plus comme un inventaire à la main : on **décrit un plan**, et
l'inventaire Ansible en est **généré**. Le dépôt est la définition ; chaque VM en
est une instance. Ce document décrit le modèle, les registres, les commandes et
le flux de travail.
> Règle d'or : **`instance/inventories/production/hosts.yml` est GÉNÉRÉ. Ne jamais l'éditer
> à la main.** On édite le *plan* puis on régénère (`make instancier-appliquer`).
---
## 1. Le modèle : deux ancres, cinq liens
L'écosystème = des **serveurs** (VM) et des **applications**, reliés à leurs
**bases**, leurs **domaines** et leurs dépendances :
```
serveur (VM) ──fournit──▶ capacités (= groupes/rôles Ansible)
application ──tourne_sur──▶ serveur (une VM peut porter N applications)
application ──requiert──▶ application(s) (DNS, PKI, BD…)
application ──utilise──▶ base(s) (via le DSN)
application ──expose──▶ domaine(s) public(s) (le FQDN, derrière l'edge)
base ──hébergée_sur──▶ serveur de BD
```
L'**application** est l'entité pivot : tout ce qui décrit « ce qui tourne et à
quoi c'est connecté » pend d'elle. Le **groupe** Ansible n'est plus une cible de
liaison — seulement une **capacité** qu'une VM fournit (le rôle appliqué).
---
## 2. Les registres (source unique de vérité)
Tous sous `docs/`, machine-lisibles, validés, consommés par le GUI, le CLI et Ansible.
| Registre | Décrit | Champs clés |
| --- | --- | --- |
| `nomenclature.yml` | nommage & adressage | `fonctions` (catégorie/service), `categories` (VLAN/sous-réseau/passerelle), `supernet` |
| `serveurs.yml` | les VM du plan | `fonction`, `etat` (actif/planifie), placement Proxmox (`noeud`/`stockage`/`disque`/`memoire`/`coeurs`), `integrations` (les `clients_*`) |
| `applications.yml` | les applications | `groupe` (capacité/rôle), `hote` (VM), `port`, `requiert`, `expose`, (+ bases via consommateur) |
| `bases-donnees.yml` | serveurs de BD + bases | `serveurs_bd` ; `bases_donnees` : `serveur`/`base`/`proprietaire`/`secret`(Vault), `consommateur` + `portee` (`application`/`groupe`/`hote`), `usage` |
| `domaines.yml` | zones DNS publiques | `domaines_publics` : `autorite`, `edge`, `secondaires`, `dnssec`, `mail` |
| `dependances-groupes.yml` | prérequis entre groupes | `requiert_groupes_actifs` |
### Dérivations clés
- **Nommage/adressage** : tout part de la `fonction` de l'hôte (`web-frontal-03`).
`VMID = 9·catégorie·service·NN`, `VLAN = catégorie.vlan`,
`IP = 10.0.<vlan>.(service×10 + NN)`. Voir `docs/nomenclature-vm.md`.
- **DSN** (lien application↔base) : `<type>://<proprietaire>:<secret>@<hôte>:<port>/<base>`.
Une application reçoit les bases où `(portee=application ET consommateur=elle)`
OU `(portee=groupe ET consommateur=son groupe)` OU `(portee=hote ET consommateur=son hôte)`.
- **Exposition DNS** : `application.expose: [fqdn]` + l'`edge` du domaine parent →
`serveur_nginx` génère le vhost (`application → hôte → IP:port`).
---
## 3. La génération (méta-classe)
`make instancier` produit `hosts.yml` **depuis le plan** :
- **host vars** : `ansible_host`/`ansible_user` + `proxmox_*` (IP/VMID/VLAN/passerelle
**dérivés** de la nomenclature ; placement/taille depuis `serveurs.yml`).
- **groupes** d'une VM = socle (`serveur_debian` + `serveur_durci`)
+ **services** (les `groupe` des applications de l'hôte)
+ **intégrations** (`serveurs.yml: integrations`)
+ **état** (`hotes_actifs` / `hotes_planifies`).
La comparaison est **sémantique** (via `ansible-inventory --list`, formatage
ignoré). « **Diff vide** » = le plan reproduit exactement l'inventaire courant ;
c'est le feu vert pour appliquer.
---
## 4. Le flux de travail
```
éditer le PLAN ──▶ make instancier (revoir le diff) ──▶ make instancier-appliquer ──▶ déployer
(GUI ou CLI) (que va-t-il changer ?) (régénère hosts.yml) (make deployer)
```
### Via le GUI — `make inventaire-ui`
Cinq vues :
| Vue | Rôle |
| --- | --- |
| **Inventaire** | **lecture seule** (inventaire généré) — vue d'ensemble des hôtes |
| **Serveurs** | éditer les VM du plan : fonction/état/placement/intégrations (VMID·IP·VLAN dérivés en direct) ; bouton **« Appliquer le plan »** |
| **Chaîne** | vue holistique par hôte : groupes → rôles, et par application ses `expose` / `requiert` / bases (DSN) |
| **Applications** | éditer les applications : groupe/hôte/port/requiert/expose |
| **Bases** | éditer serveurs de BD et bases (portée + consommateur), DSN affiché |
Édition → **Sauvegarder** (écrit le registre) → **Appliquer le plan** (régénère
`hosts.yml`). L'écriture directe de l'inventaire est refusée (409).
### Via le CLI / `make`
Chaque registre a son script miroir et ses cibles `make` :
| Domaine | Lister | Vérifier | Bootstrap (depuis l'inventaire) |
| --- | --- | --- | --- |
| Serveurs | `make serveurs` | `make serveurs-verifier` | `make serveurs-bootstrap` |
| Applications | `make applications` | `make applications-verifier` | `make applications-bootstrap` |
| Bases | `make bases` | `make bases-verifier` | — |
| Domaines | `make domaines` | `make domaines-verifier` | — |
Génération :
```bash
make instancier # génère hosts.genere.yml (gitignoré) + diff sémantique
make instancier-appliquer # régénère hosts.yml (refuse si diff non vide ; FORCE=1 pour forcer)
```
Validation globale : `make inventaire-verifier` (ansible-inventory + tous les
registres + **`node --check` du JS du GUI**).
---
## 5. Garde-fous
- **Validateurs** : chaque registre est validé (références connues, énumérations,
unicité). Un `expose` sans domaine parent, un `requiert` fantôme, une `portee`
inconnue, une `fonction` absente → rejet.
- **Diff vide** : `instancier-appliquer` refuse d'écraser l'inventaire si le plan
ne le reproduit pas (sauf `FORCE=1` pour un changement intentionnel).
- **`node --check`** : le JS embarqué du GUI est vérifié (`scripts/verifier_gui.py`,
intégré à `make inventaire-verifier`) — une erreur de syntaxe JS casse toute la page.
- **git** : `hosts.yml` est versionné ; `git diff` / `git checkout` est le filet.
- **Secrets** : jamais en clair ; `secret:` nomme une variable Ansible Vault.
---
## 6. Réutilisation de la règle (Ansible)
La règle de résolution vit **une seule fois**, en Python (`scripts/inventory_rules.py`),
et est exposée à Ansible par un *filter plugin* (`filter_plugins/registres.py`) :
`bases_de_application`, `applications_de_hote`, `expositions_des_applications`,
`chaine_connexion`. Les playbooks par application (`serveur_web_dorsal`/`_frontaux`)
itèrent ainsi sur les applications de l'hôte et résolvent leurs DSN.
---
## 7. Amorçage / reconstruction
Pour (re)construire le plan depuis un inventaire existant :
```bash
make serveurs-bootstrap # VM -> instance/plan/serveurs.yml (fonction/état/placement/integrations)
make applications-bootstrap # services serveurs_* (hors socle) -> instance/plan/applications.yml
make instancier # vérifier le diff vide
```
C'est ainsi que le plan a été initialisé sans perte, avec diff vide vérifié.
---
## 8. Moteur et instance : deux dépôts
Le **moteur** (ce dépôt, `Set-OPS`) est générique et partageable ; il ne contient
aucune donnée d'instance. Une **instance** (le plan + l'inventaire d'un loup) vit
dans son **propre dépôt** (ex. `OPS-monatelier`).
Le moteur localise l'instance via **`SETOPS_INSTANCE`** (défaut : `instance`). Deux
modèles :
- **Modèle A — dépôts frères** (en cours) : moteur et instance côte à côte ; un
symlink `instance -> ../OPS-monatelier` (gitignoré) fait que le défaut résout
l'instance sans configuration. Idéal quand on développe le moteur *et* l'instance.
- **Modèle B — moteur en sous-module** (futur, pour la meute) : l'instance épingle
une version du moteur ; `SETOPS_INSTANCE` pointe la racine de l'instance.
Pour brancher une instance (modèle A) :
```bash
cd Set-OPS
ln -s ../OPS-monatelier instance # ou : export SETOPS_INSTANCE=/chemin/instance
make inventaire-verifier # lit l'instance via le symlink
```
Le moteur écrit `hosts.yml` (généré) **dans le dépôt d'instance**, jamais dans le sien.