Aucun des trois SVI de sleipnir-01 n'avait de consommateur : les VTEP sont dans le même sous-réseau, les nœuds de sortie sont adjacents à la frontière, et les commutateurs peuvent sortir par l'OPNsense qui a déjà une patte sur le VLAN 10. Deux décisions séparées avaient vidé ce rôle sans qu'on regarde leur effet cumulé : le passage à l'EVPN a retiré les VLAN tenants du fil, puis la fusion du lien de sortie dans le VLAN 40 a rendu les nœuds de sortie adjacents. sleipnir-01 disparaît, pas seulement son rôle : en étoile, le centre est sur tous les chemins, donc un point de panne unique du plan de données — ce qui vidait aussi de son sens l'ajout d'une seconde carte à bond3. Deux switches L2 reliés, bond3 répartis. D-51 ; D-05 renversée. Le devis perd trois SVI, quatre routes, et surtout sa section 5 — celle qui coupait l'accès d'administration au switch en cas d'erreur. D-50 : `passerelle` signifiait « adresse du SVI du switch », une hypothèse déguisée en donnée. Elle signifie maintenant « la passerelle de ce sous-réseau, où qu'elle vive », et le devis dérive s'il doit émettre une interface routée — uniquement si le porteur déclaré a le rôle switch. Le même moteur sert les deux postures : le modèle public démontre celle où le switch route. Deux gardes remplacées, pas affaiblies. À la place de « passerelle_sortie exige passerelle » et « routeur.ip == passerelle », une règle plus forte : une passerelle doit être l'adresse d'un hôte déclaré sur ce réseau. Elle attrape en plus les passerelles fantômes. Éprouvée par trois sabotages, tous attrapés — et elle a trouvé une sous-déclaration dans le modèle public. Quatre trous corrigés, tous de la même famille (une liste figée finit par mentir) : port de frontière figé sur le transit, trunk Proxmox excluant le transit, switches d'accès sautant sa déclaration, et le switch de tête privé d'adresse de gestion par la suppression du SVI. D-03 renversée : le /29 élargi en /24 fait tomber l'exemption d'invariant, le .1 revient à la passerelle. 30 preuves OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
9.5 KiB
Markdown
91 lines
9.5 KiB
Markdown
# Carte d'orientation Set-OPS
|
|
|
|
> **À lire en premier.** Point d'entrée vers le corpus documentaire, et **catalogue des
|
|
> mécanismes transverses** — ceux qui vivent dans le code et qu'on *re-découvre* sinon.
|
|
> Créée le 2026-07-03 après un audit du dépôt, **revue le 2026-07-29**. But : ne plus
|
|
> re-déterrer ce qui existe.
|
|
|
|
Le dépôt est **déjà bien documenté** (26 docs + 7 pièces d'audit + 21 unités de wiki, et un
|
|
README par rôle). Le manque n'était pas la doc du *modèle*,
|
|
mais (a) un index « par où commencer » et (b) une carte des *mécanismes* (dispersés dans le
|
|
code + les README de rôles). Cette page comble ces deux trous.
|
|
|
|
## 1. À lire d'abord (dans l'ordre)
|
|
|
|
| Sujet | Documents |
|
|
|---|---|
|
|
| **Autorité / gouvernance** | `AGENTS.md` (source d'autorité), `CLAUDE.md`, `docs/MISE-A-JOUR-CODEX-CLAUDE.md` |
|
|
| **Le modèle (plan)** | `docs/architecture-set-ops.md` (survol) → `docs/plan-et-generation.md` (à fond) → `docs/meta-classe.md` (concept) |
|
|
| **Services, maturité, dette** | `docs/catalogue-services.md` (**la carte de maturité + la cruft y sont déjà**) |
|
|
| **Exploitation / VM** | `docs/vm-lifecycle.md`, `docs/procedure-template-debian13-proxmox.md`, `docs/config-proxmox.md`, `docs/nomenclature-vm.md`, `docs/multi-instances.md` |
|
|
| **Conceptions de domaine** | `docs/identite-sso.md`, `docs/courriel-conception.md`, `docs/bindings-conception.md`, `docs/dns-interne.md`, `docs/dimensionnement-ressources.md`, `docs/integrations-vm.md` |
|
|
| **Réseau / pare-feu** | `docs/flux-conception.md` (le modèle) → `docs/registre-flux.md` (**généré**, matrice d'audit) → `docs/frontiere-opnsense.md` (la bordure nord/sud) ; underlay : `underlay.yml.example` + `make underlay` |
|
|
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
|
|
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md`, `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
|
|
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **28 décisions**, pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
|
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
|
|
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
|
|
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
|
|
| **Pédagogie (le wiki)** | `wiki/` — 21 unités (+ `_Sidebar`) publiées par `make wiki-publier` ; entrer par `wiki/Home.md` |
|
|
| **Vision / positionnement** | `docs/ecosysteme-chezlepro.md`, `docs/positionnement.md`, `docs/pouvoirs-set-ops.md` |
|
|
|
|
## 2. Les mécanismes transverses (et OÙ ils vivent)
|
|
|
|
Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanisme.**
|
|
|
|
| Mécanisme | Ce que c'est | Où, dans le code | Doc |
|
|
|---|---|---|---|
|
|
| Plan → inventaire | `plan/*.yml` → `hosts.yml` généré | `scripts/instancier.py`, `scripts/inventory_rules.py` | `plan-et-generation.md` |
|
|
| Nomenclature dérivée | VMID / IP / VLAN / FQDN dérivés | `inventory_rules.deriver_nomenclature` + `plan/nomenclature.yml` | `nomenclature-vm.md` |
|
|
| Dimensionnement | RAM/CPU/disque sommés par logiciel | `roles/*/meta/empreinte.yml` → `deriver_ressources` | `dimensionnement-ressources.md` |
|
|
| **Bindings app→app** | lien **côté app** (`liens`) résolu en host_vars | `plan/applications.yml` `liens:` + `roles/*/meta/liens.yml` + `instancier.resoudre_liens` | `bindings-conception.md` |
|
|
| **Bindings app→base** | lien **côté base** (`consommateur`/`portee`) résolu **dans le rôle** | `plan/bases-donnees.yml` + rôle utilitaire `resoudre_base` (`lookup('vars', secret)`, `no_log`) inclus par le consommateur | `bindings-conception.md` §5 |
|
|
| Résolution d'annuaire | connexion LDAP (uri/base DN/bind) **dérivée**, jamais recopiée | rôle utilitaire `resoudre_annuaire` (inclus par dovecot/postfix/keycloak/icingaweb2) | `identite-sso.md` |
|
|
| Plancher de résolution | `/etc/hosts` généré depuis l'inventaire + alias d'`expose` → l'écosystème se résout **DNS éteint** | rôle `hosts_statiques` (appliqué dans la couche socle) | `dns-interne.md` |
|
|
| Pont de certificat | cert step_ca → service, resync au renouvellement | script `*-cert-sync` + unité `.path`, dans chaque rôle serveur ; cert déposé par `client_pki` | — |
|
|
| Ordonnancement socle-first | socle/durci avant les `client_*` | `serveur_debian`/`serveur_durci` d'abord (posent `/etc/hosts` via `hosts_statiques`) | — |
|
|
| Sûreté check-mode | dry-run fiable | `when: not ansible_check_mode` sur les tâches de service + handlers | — |
|
|
| Voûte au déploiement | secret jamais en clair | `ANSIBLE_VAULT_PASSWORD_FILE` / `~/.config/setops-vault-pass` ; déréférencé par `lookup('vars', <nom>)` | — |
|
|
| Multi-instance | un dépôt par écosystème ; l'active = symlink `instance/`, les autres **découvertes par convention** (dossiers frères, aucun registre) | active : symlink `instance/` ; découverte : `scripts/instances.py` / `devis_reseau.py` (glob `../*/plan/nomenclature.yml` avec `index`) ; garde-fou collision : preuve **P21** | `multi-instances.md` |
|
|
| Exposition → edge | app expose un FQDN public servi par un edge | `plan/domaines.yml` + `expose` (applications) | `bindings-conception.md` §4 |
|
|
| Exploitation de l'hébergeur | ses **opérations** (supervision de la fabric, sauvegarde des configs, DNS d'underlay) n'appartiennent à aucun tenant et restent **hors overlay** | décidé, **non construit** : aucun équipement d'hébergeur n'est encore dans un inventaire | `hebergeur-exploitation.md` |
|
|
| Authentification | web → Keycloak ; LDAP source unique ; secours par `sudo`, formulaire local non annoncé | `<rôle>_connexion_locale: false` (grafana, forgejo, nextcloud) ; garde de version Forgejo ≥ 10 | `authentification.md` |
|
|
| SDN EVPN | ajouter un tenant implique **1 zone + 6 VNets + 6 sous-réseaux**, tous dérivés du seed | `scripts/devis_sdn.py` (`make devis-sdn`) ; nommage dérivé du tenant (`CHEZ17`, `chez174`), ≤ 8 caractères ; garde **P30** | `sdn-evpn.md` §2 |
|
|
| Pools Proxmox | un pool par tenant : les noms courts de VM sont **volontairement identiques** d'un tenant à l'autre (même fonction, même nom), et seule la console Proxmox en souffrait | `scripts/devis_proxmox_pools.py` (`make devis-proxmox-pools`) ; nom dérivé de l'`index` ; garde de collision = preuve **P28** | `decisions-architecture.md` D-37 |
|
|
| Routage | **aucun commutateur ne route** : la frontière est le seul équipement L3 ; les switches commutent | `passerelle` dit qui porte la passerelle, le SVI se dérive du rôle du porteur | `decisions-architecture.md` D-49/50 |
|
|
| Frontière nord/sud | les flux `pair: externe` — **sautés** par le pare-feu d'hôte — sont la politique de bordure | `scripts/devis_opnsense.py` (`make devis-opnsense`) ; garde d'accès admin = preuve **P24** | `frontiere-opnsense.md` |
|
|
|
|
> ⚠️ **Deux directions de binding, assumées** : `app→app` côté app (instancier),
|
|
> `app→base` côté base (registre, résolu en rôle pour que le secret ne quitte jamais le rôle).
|
|
> Ne pas unifier l'un dans l'autre sans raison. Cf. `bindings-conception.md`.
|
|
|
|
## 3. Maturité & dette
|
|
|
|
- **Maturité des rôles, échafaudages, rôles-catégories inertes** : voir
|
|
`docs/catalogue-services.md` « État d'implémentation » (source de vérité, tenue à jour).
|
|
- **Dette (audit 2026-07-03, revu le 2026-07-29)** :
|
|
- ✅ **soldé** — README de rôles : **tous les rôles en ont un** (les 12 manquants écrits le
|
|
2026-07-29 : `serveur_debian`, `hosts_statiques`, `resoudre_base`, `resoudre_annuaire`,
|
|
`serveur_dovecot`, `serveur_postfix`, `serveur_rspamd`, `client_backup`, `serveur_backup`,
|
|
`client_unbound`, `serveur_oauth2_proxy`, `serveur_icingaweb2`).
|
|
- ✅ **soldé** — `expose` **est** consommé au déploiement : `plan/applications.yml` →
|
|
filtre `expositions_des_applications` → vhosts nginx dérivés
|
|
(`roles/serveur_nginx/tasks/main.yml`, template `expositions.conf.j2`, drapeau
|
|
`serveur_nginx_publier_expositions`), + alias `/etc/hosts` posés par `hosts_statiques`,
|
|
+ SANs des certificats d'edge dérivés par `scripts/instancier.py`.
|
|
- ⏳ **ouvert** — `meta/liens.yml` seulement sur `serveur_postfix` (`mailstore`, `milter`) :
|
|
les autres liens app→app viendront.
|
|
- ⏳ **ouvert** — `requiert` : câblé **côté GUI** (édition + validation
|
|
`valider_applications`) mais pas consommé au déploiement ; c'est un indice de dépendance
|
|
*applicative*. Les dépendances de **groupes** (celles qui pilotent l'ordre) vivent dans
|
|
`docs/dependances-groupes.yml` + `docs/couches-deploiement.yml`, et sont bel et bien
|
|
consommées par l'orchestrateur (`scripts/orchestrer.py` → `playbooks/site.yml`).
|
|
|
|
## 4. Discipline (pour ne plus re-déterrer)
|
|
|
|
Avant de concevoir ou d'ajouter un mécanisme :
|
|
1. lire **cette carte** + le doc du sujet ;
|
|
2. **arpenter le code** (`grep`) et **lire les README des rôles** concernés ;
|
|
3. **étendre / factoriser** l'existant plutôt qu'ajouter un chemin parallèle.
|
|
|
|
Un seul agent IA travaille dans le dépôt à la fois (Codex **ou** Claude) — cf. `AGENTS.md`.
|