From a8af5410598239b3a9099351da050d9eb4f58893 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Wed, 9 Sep 2026 23:39:19 -0400 Subject: [PATCH] D-86 : la supervision se deploie juste apres la PKI, pas a la fin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On n allume pas la lumiere une fois la maison finie. L observabilite et le moteur de supervision etaient deployes APRES presque tout ce qu ils surveillent. Une reconstruction depuis zero est pourtant le moment ou l on a le plus besoin de voir, et c etait le seul moment ou l on ne voyait rien. 4. observabilite postgresql, prometheus, loki, grafana, icinga 5. agents_supervision client_metrique, client_journal, client_sante DEPLACER LES SERVEURS SANS LES AGENTS N AURAIT RIEN CHANGE : ce sont les agents qui rapportent, et ils etaient en derniere couche. Les deux couches vont donc ensemble. Des la couche 5, chaque hote expedie ses metriques, ses journaux et l etat de ses unites — tout ce qui se deploie ensuite est mesure pendant qu on le construit. COUT : serveur_postgresql monte aussi (icinga l exige, il n exige rien). C est le seul entrainement. RESTE TARD A DESSEIN : icingaweb2 et oauth2_proxy reclament LDAP et Keycloak. C est la CONSOLE, pas la mesure. CE QUI REND CE DEPLACEMENT POSSIBLE : le DNS reste en couche 6, donc apres la supervision, alors qu icinga joint sa base par un NOM. Ca tient grace au plancher /etc/hosts pose des la couche 1 — 34 entrees, verifie. C est exactement ce pour quoi il existe. LIMITE : les notifications dependent de client_smtp, encore en derniere couche. Pendant une reconstruction, l etat est mesure et consultable, mais rien ne part par courriel avant la fin. ET : l ordre est VALIDE, pas EPROUVE. P08 accepte, le graphe accepte, site.yml regenere passe le syntax-check sur 782 lignes de plan. La seule preuve reelle d un ordre de reconstruction, c est une reconstruction. make prouver : CONFORME, 64 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++ docs/carte-set-ops.md | 2 +- docs/catalogue-services.md | 10 ++++++ docs/couches-deploiement.yml | 40 ++++++++++++++++------- docs/decisions-architecture.md | 1 + playbooks/site.yml | 38 +++++++++++++--------- 6 files changed, 122 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e57cac..3da394c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # CHANGELOG — Set-OPS +## 2026-09-09 (9) — La supervision passe juste apres la PKI (D-86) + +*On n'allume pas la lumiere une fois la maison finie.* + +L'observabilite et le moteur de supervision etaient en couches 4 et 5 sur six — donc +deployes APRES presque tout ce qu'ils surveillent. Une reconstruction depuis zero est +pourtant le moment ou l'on a le plus besoin de voir, et c'etait le seul moment ou l'on ne +voyait rien. + + 1. socle serveur_debian, serveur_durci + 2. pki_racine serveur_step_ca + 3. pki_client client_pki + 4. observabilite postgresql, prometheus, loki, grafana, icinga <- NOUVEAU + 5. agents_supervision client_metrique, client_journal, client_sante <- NOUVEAU + 6. services openldap, powerdns, resolveur, nginx, postfix... + 7. apps keycloak, forgejo, icingaweb2, nextcloud... + 8. agents client_smtp, client_backup, client_resolveur, client_artefacts + +### Deplacer les serveurs sans les agents n'aurait rien change + +C'est le point qui a decide de la forme : ce sont les AGENTS qui rapportent, et ils etaient +en derniere couche. Monter `prometheus` et `icinga` en laissant `client_metrique` et +`client_sante` a la fin aurait produit une supervision allumee et aveugle. Les deux +couches vont donc ensemble. + +Des la couche 5, chaque hote expedie ses metriques, ses journaux et l'etat de ses unites +systemd. Tout ce qui se deploie ensuite est mesure PENDANT qu'on le construit. + +### Ce que ca a coute, et ce qui reste tard a dessein + +`serveur_postgresql` monte aussi : `icinga` l'exige, et lui n'exige rien. C'est le seul +entrainement. + +Restent en couche 7 : **`icingaweb2` et `oauth2_proxy`**, qui reclament LDAP et Keycloak. +C'est la CONSOLE, pas la mesure — l'interface humaine peut attendre, l'etat non. + +### Ce qui rend ce deplacement possible + +Le DNS (`powerdns`, `resolveur`) reste en couche 6, donc APRES la supervision. Or `icinga` +joint sa base par un NOM, et `client_sante` pousse vers un NOM. + +Ca tient parce que le **plancher `/etc/hosts`**, pose des la couche 1 par +`hosts_statiques`, porte deja les 34 entrees de l'ecosysteme — verifie sur `mon-01`. C'est +exactement ce pour quoi il existe : *« il ne s'installe pas, il rend installable »*. Sans +lui, cette decision serait impossible. + +### La limite, dite franchement + +Les **notifications** dependent de `client_smtp`, encore en derniere couche. Pendant une +reconstruction, l'etat est mesure et consultable — mais rien ne part par courriel avant la +fin. Le deplacer demanderait de monter `postfix` et son relais, ce qui entrainerait bien +plus que PostgreSQL. + +Et ceci : **l'ordre est valide, pas eprouve**. P08 accepte (aucune arete en arriere), le +graphe des dependances accepte, `playbooks/site.yml` est regenere et passe le +`--syntax-check` sur ses 782 lignes de plan. Mais la seule preuve reelle d'un ordre de +reconstruction, c'est une RECONSTRUCTION — et celle-la se decide, elle ne se glisse pas +dans une soiree. + ## 2026-09-09 (8) — L'hote de supervision saturait par sa propre demonstration « mon-01 tape dans l'fond. » Il tapait, en effet. diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index e9a54c9..eb8914b 100644 --- a/docs/carte-set-ops.md +++ b/docs/carte-set-ops.md @@ -28,7 +28,7 @@ README de rôles). Cette page comble ces deux trous. | documents | 40 | `docs/*.md` | | pièces d'audit | 40 | `docs/audit/*` | | unités de wiki | 27 | `wiki/*.md` | -| décisions en vigueur | 82 | lignes `\| **D-nn** \|` de `decisions-architecture.md` | +| décisions en vigueur | 83 | lignes `\| **D-nn** \|` de `decisions-architecture.md` | | décisions renversées | 3 | lignes `\| **D-nn** —` du même document | ## 1. À lire d'abord (dans l'ordre) diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index 8cfb454..555da35 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -208,6 +208,16 @@ dans le plan. L'ordre ci-dessous privilégie les dépendances structurantes avant les applications. +> **Ce raisonnement a été révisé le 2026-09-09 sur un point** : l'observabilité et la +> supervision ne viennent plus en phases 3 et 4, mais **juste après la PKI** — donc avant +> presque tout ce qu'elles surveillent. *On n'allume pas la lumière une fois la maison +> finie.* Une reconstruction depuis zéro est précisément le moment où l'on a le plus +> besoin de voir. Les phases ci-dessous gardent leur numérotation, qui dit une **parenté +> logique** ; l'ordre exécutable, lui, est dans `docs/couches-deploiement.yml` (D-86). +> +> Ce qui reste tard, et à dessein : la **console** (`icingaweb2`, `oauth2_proxy`), qui +> réclame LDAP et Keycloak. L'interface humaine peut attendre ; la mesure, non. + ### Phase 1 - Fondations transversales 1. `serveur_powerdns` diff --git a/docs/couches-deploiement.yml b/docs/couches-deploiement.yml index 082fb32..26ac1b0 100644 --- a/docs/couches-deploiement.yml +++ b/docs/couches-deploiement.yml @@ -33,18 +33,44 @@ couches: groupes: - client_pki + - nom: observabilite + raison: >- + VOIR AVANT DE CONSTRUIRE. La mesure vient juste après la PKI, donc avant tout ce + qu'elle devra surveiller — et non après, comme si l'on n'allumait la lumière qu'une + fois la maison finie. Une reconstruction depuis zéro est précisément le moment où + l'on a le plus besoin de voir ce qui se passe : chaque rôle déployé ensuite l'est + sous l'œil de la supervision, et une unité qui casse se voit à la minute plutôt qu'à + la fin. `obs` ne dépend de rien ; `serveur_icinga` n'exige que PostgreSQL, qui + n'exige rien lui-même. Ce qui reste plus tard, c'est la CONSOLE (`icingaweb2`, + `oauth2_proxy`), qui demande LDAP et Keycloak — l'interface humaine peut attendre, + la mesure non. + groupes: + - serveur_postgresql + - serveur_prometheus + - serveur_loki + - serveur_grafana + - serveur_icinga + + - nom: agents_supervision + raison: >- + Les agents qui FONT voir, poses juste apres leurs serveurs. Deplacer la supervision + en amont sans eux n'aurait rien change : ce sont eux qui rapportent. Des cette + couche, chaque hote expedie ses metriques, ses journaux, et l'etat de ses unites + systemd — donc tout ce qui se deploie apres est mesure pendant qu'on le construit. + groupes: + - client_metrique + - client_journal + - client_sante + - nom: services raison: "Les services d'infrastructure dont dépendent les applications : bases, annuaire, DNS, cache, métriques, journaux, edge, courriel." groupes: - - serveur_postgresql - serveur_openldap - serveur_powerdns # Le resolveur du tenant vient APRES son autoritatif : il le prend en stub-zone, # et sa validation exige que la zone souveraine reponde deja. - serveur_resolveur - serveur_redis - - serveur_prometheus - - serveur_loki - serveur_nginx - serveur_rspamd - serveur_dovecot @@ -72,9 +98,7 @@ couches: - serveur_keycloak - serveur_oauth2_proxy - serveur_forgejo - - serveur_icinga - serveur_icingaweb2 - - serveur_grafana - serveur_collabora - serveur_nextcloud - serveur_web_frontal @@ -93,13 +117,7 @@ couches: - nom: agents raison: "Les intégrations clientes qui expédient vers les services centraux (métriques, journaux, courriel, sauvegardes, résolution locale). Déployées en dernier, quand leurs cibles sont debout." groupes: - - client_metrique - - client_journal - client_smtp - client_backup - client_resolveur - client_artefacts - # Le rapport de sante vient en DERNIER de la couche : il constate ce que tout le - # reste a laisse derriere lui. Le poser plus tot ferait rapporter une machine - # a moitie deployee, et le premier verdict serait faux. - - client_sante diff --git a/docs/decisions-architecture.md b/docs/decisions-architecture.md index b7cbec9..041ebed 100644 --- a/docs/decisions-architecture.md +++ b/docs/decisions-architecture.md @@ -59,6 +59,7 @@ sont les seules vérifiables. | **D-83** | **Patient 0 a été retiré** — ses machines n'existent plus (constaté le 2026-09-06) | D-82 lui avait laissé une raison d'être : la mise en œuvre de référence du modèle `origine`, et un **témoin** de plus du génome. Le retrait solde la première et **abaisse la seconde de trois copies vivantes à deux** (`eregion`, la forge du site). Ce qu'il devait éliminer — le SPOF `eregion` — reste entier, et sans lui il n'y a plus de miroir indépendant pour l'absorber. **Son plan reste sur disque et la fédération lui réserve toujours l'index 29** : tant que ce n'est pas tranché, le site ouvre SSH, apt, DNS et HTTPS à `10.29.0.0/16` — un périmètre vide | `OPS-Patient0/`, `SITE-Chezlepro/flux-genere/` | **P21** (index), **P23** | | **D-84** | **Le plan de contrôle reste gelé — c'est la CARTE DES SEUILS qui était fausse** | La question « et si on retirait le gel ? » a mis à l'épreuve les cinq seuils de `positionnement.md`, et deux ne tenaient pas. **RBAC** : couvert depuis que trois classes d'acteurs aux pouvoirs disjoints existent — poste, runner de site, runners de tenant — séparés **cryptographiquement** (une voûte, une clé, 2026-08-28) et non par une table de permissions qu'une faille applicative contournerait ; adopter AWX pour ce besoin serait **régresser**. **IPAM** : sans objet par construction — rien ne s'alloue, tout dérive du seed, et P20/P21/P23/P28/P33 tiennent déjà ce qu'un IPAM vérifierait *a posteriori*. Les deux lignes sont retirées du tableau : les garder aurait fait adopter un outil pour un besoin déjà rempli. **Et un seuil manquait** — l'**émancipation** : le GUI est mono-utilisateur (`127.0.0.1` + jeton), or la trajectoire mène à plusieurs humains aux portées disjointes, sur des machines qui ne sont pas les nôtres. Ce seuil n'appelle pas AWX, il appelle une décision non prise. *Un seuil qu'on ne nomme pas est un seuil qu'on franchit sans le voir.* Corollaire consigné : le gel porte sur les **fonctions**, jamais sur les **vues** — montrer à l'écran ce que le moteur sait déjà ne franchit aucun seuil | `positionnement.md` §3, §4, §5 | — | | **D-85** | **cloud-init naît avec la VM et ne lui survit pas** | cloud-init n'est pas un logiciel d'installation : c'est une **source de vérité externe**, qui se réveille à *chaque* démarrage et relit le lecteur attaché par l'hyperviseur — lequel peut redéfinir comptes, clés SSH autorisées, mots de passe et réseau. Sur une machine que le plan possède, c'est un **second maître** : le plan ne le décrit pas, `make valider` ne le mesure pas, et il parle en premier. Sa tâche est pourtant finie à la première seconde — c'est parce qu'il a **réussi** à poser l'adresse et les clés qu'Ansible a pu entrer. **Trois moitiés, qui se défont séparément** : le **gabarit** le garde (sans lui un clone ne naît pas — P56) ; le **socle** ne l'installe plus (le garder produisait un va-et-vient à chaque déploiement : le socle installe, le durcissement retire, deux `changed` par passage) ; le **durcissement** le retire (`cloud_init_retrait`, en dernier). **Ce qui rend le retrait sûr est mesuré, pas supposé** (2026-09-09, `obs-01`) : `/etc/network/interfaces.d/50-cloud-init` n'appartient à aucun paquet — `dpkg -S` ne le trouve pas — et le `postrm` ne le nomme jamais, même en `purge`. L'adresse survit. Le rôle le **vérifie** malgré tout, avant et après : une VM qui perd ce fichier ne se plaint pas, elle repart sans adresse et plus personne ne peut entrer pour le constater. **⚠ CE QUE CETTE DÉCISION NE FERME PAS — et il faut le dire, sinon elle se lit comme une émancipation qu'elle n'est pas.** Retirer cloud-init **n'ôte aucun pouvoir à l'hébergeur**. `qemu-guest-agent` est au gabarit (P56 : il doit y être — c'est par lui que `creer-vm` confirme la matérialisation sans entrer chez le tenant), et l'API Proxmox expose sur son dos, sur toute VM vivante de la flotte, un pouvoir **strictement plus grand** que le lecteur cloud-init : `exec`, `file-write`, `file-read`, `set-user-password`, `shutdown` (relevé le 2026-09-09 sur `edge-mta-01`, jeton d'API du site). Ce que D-85 ferme est donc **précis et étroit** : (a) une réapplication **automatique, à chaque démarrage**, depuis un support que le plan ne possède pas et qu'aucune preuve ne lit ; (b) le code de cloud-init lui-même — un interpréteur Python complet, exécuté en root au démarrage, et ses ~29 dépendances. Elle ne ferme **pas** la mainmise de l'hyperviseur sur ses invités : celle-là est une propriété de la virtualisation, pas de cloud-init, et elle appelle sa propre décision — non prise. **Le seuil où le remplacer deviendrait juste** : le jour où une première seconde ne peut plus être amorcée par Proxmox (autre hyperviseur, métal nu, hébergeur sans API), le chemin par l'agent invite cesse d'être une réimplémentation d'un standard — que `positionnement.md` interdit — et devient **le chemin portable**. Tant que ce seuil n'est pas atteint, écrire soi-même l'amorçage serait échanger un standard éprouvé contre du code maison au moment le plus fragile, dont le mode de panne est le pire : une VM injoignable | `roles/cloud_init_retrait/`, `serveur_durci.yml`, `positionnement.md` | **P63** | +| **D-86** | **La supervision se déploie juste après la PKI, pas à la fin** | L'observabilité (`prometheus`, `loki`, `grafana`) et le **moteur** de supervision (`icinga`) passent en couche 4, immédiatement après `client_pki` ; les agents qui les nourrissent (`client_metrique`, `client_journal`, `client_sante`) en couche 5. **Le raisonnement** : ce qui se déploie ensuite l'est *sous l'œil* de la supervision — une unité qui casse se voit à la minute, pas à la fin. Une **reconstruction depuis zéro** est précisément le moment où l'on a le plus besoin de voir, et c'était le seul moment où l'on ne voyait rien. **Déplacer les serveurs sans les agents n'aurait rien changé** : ce sont les agents qui rapportent, et ils étaient en dernière couche. **Ce que ça a coûté en dépendances** : `serveur_postgresql` monte aussi (il n'exige rien lui-même, et `icinga` l'exige). **Ce qui reste tard, à dessein** : `icingaweb2` et `oauth2_proxy` réclament LDAP et Keycloak — c'est la CONSOLE, pas la mesure. L'interface humaine peut attendre. **Limite dite franchement** : les *notifications* dépendent de `client_smtp`, encore en dernière couche — pendant une reconstruction, l'état est mesuré et consultable, mais rien ne part par courriel avant la fin. **CE QUI REND CE DÉPLACEMENT POSSIBLE**, et qui n'est pas un détail : le DNS (`powerdns`, `resolveur`) reste en couche 6, donc *après* la supervision. Or `icinga` joint sa base par un **nom** (`data-sql-01.chezlepro.internal`), et `client_sante` pousse vers un **nom**. Ça tient parce que le **plancher `/etc/hosts`**, posé dès la couche 1 par `hosts_statiques`, porte déjà les 34 entrées de l'écosystème — vérifié. C'est exactement ce pour quoi il existe : *« il ne s'installe pas, il rend installable »*. Sans lui, cette décision serait impossible. **P08** valide l'ordre : aucune arête en arrière | `docs/couches-deploiement.yml`, `catalogue-services.md` §Ordre | **P08** | | **D-81** | **La forge du SITE fait autorité pour le génome.** Toute autre copie — y compris celle d'où le moteur a été poussé jusqu'ici — est un **miroir**. Le poste de l'exploitant ne route pas jusqu'à elle : c'est le **runner du site** qui publie, par `make genome-pousser` | un écosystème se reproduit depuis la forge de son site : c'est de là qu'il clone son moteur, ses plans, ses modèles. Si l'autorité est ailleurs, cette forge devient un cache qu'on croit à jour — et le 2026-08-26 elle était **quatre commits en arrière** sans que rien ne le signale, dont le correctif qui désarme le pare-feu Proxmox. **Un écosystème qui se reproduit depuis une forge en retard reproduit ses défauts.** Le poste n'a de patte que sur l'administration, et on ne perce pas de chemin pour lui : le runner existe pour ce travail | `playbooks/maintenance/genome_pousser.yml`, `scripts/genome_colis.py`, `Makefile` §genome-pousser | — | | **D-13** | Un **hébergeur** sert plusieurs **tenants** et a son tenant par défaut | Chezlepro est les deux à la fois, ce qui masquait la distinction | `frontiere-opnsense.md` §2 | — | | **D-14** | `underlay.yml` appartient à l'**hébergeur**, monté par symlink | ce sont ses commutateurs, ses câbles ; le moteur est générique, un tenant n'en possède pas | `sdn-evpn.md`, `underlay.yml.example` | — | diff --git a/playbooks/site.yml b/playbooks/site.yml index 0f32443..93ecd47 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -19,19 +19,33 @@ - name: Couche pki_client — groupe client_pki import_playbook: groupes/client_pki.yml -# --- couche services --- -- name: Couche services — groupe serveur_postgresql +# --- couche observabilite --- +- name: Couche observabilite — groupe serveur_postgresql import_playbook: groupes/serveur_postgresql.yml +- name: Couche observabilite — groupe serveur_prometheus + import_playbook: groupes/serveur_prometheus.yml +- name: Couche observabilite — groupe serveur_loki + import_playbook: groupes/serveur_loki.yml +- name: Couche observabilite — groupe serveur_grafana + import_playbook: groupes/serveur_grafana.yml +- name: Couche observabilite — groupe serveur_icinga + import_playbook: groupes/serveur_icinga.yml + +# --- couche agents_supervision --- +- name: Couche agents_supervision — groupe client_metrique + import_playbook: groupes/client_metrique.yml +- name: Couche agents_supervision — groupe client_journal + import_playbook: groupes/client_journal.yml +- name: Couche agents_supervision — groupe client_sante + import_playbook: groupes/client_sante.yml + +# --- couche services --- - name: Couche services — groupe serveur_openldap import_playbook: groupes/serveur_openldap.yml - name: Couche services — groupe serveur_powerdns import_playbook: groupes/serveur_powerdns.yml - name: Couche services — groupe serveur_redis import_playbook: groupes/serveur_redis.yml -- name: Couche services — groupe serveur_prometheus - import_playbook: groupes/serveur_prometheus.yml -- name: Couche services — groupe serveur_loki - import_playbook: groupes/serveur_loki.yml - name: Couche services — groupe serveur_nginx import_playbook: groupes/serveur_nginx.yml - name: Couche services — groupe serveur_rspamd @@ -62,18 +76,14 @@ import_playbook: groupes/serveur_oauth2_proxy.yml - name: Couche apps — groupe serveur_forgejo import_playbook: groupes/serveur_forgejo.yml -- name: Couche apps — groupe serveur_icinga - import_playbook: groupes/serveur_icinga.yml -- name: Couche apps — groupe serveur_grafana - import_playbook: groupes/serveur_grafana.yml +- name: Couche apps — groupe serveur_icingaweb2 + import_playbook: groupes/serveur_icingaweb2.yml - name: Couche apps — groupe serveur_collabora import_playbook: groupes/serveur_collabora.yml - name: Couche apps — groupe serveur_web_frontal import_playbook: groupes/serveur_web_frontal.yml - name: Couche apps — groupe serveur_web_dorsal import_playbook: groupes/serveur_web_dorsal.yml -- name: Couche apps — groupe serveur_icingaweb2 - import_playbook: groupes/serveur_icingaweb2.yml - name: Couche apps — groupe serveur_nextcloud import_playbook: groupes/serveur_nextcloud.yml - name: Couche apps — groupe serveur_ops @@ -84,10 +94,6 @@ import_playbook: groupes/serveur_ops_site.yml # --- couche agents --- -- name: Couche agents — groupe client_metrique - import_playbook: groupes/client_metrique.yml -- name: Couche agents — groupe client_journal - import_playbook: groupes/client_journal.yml - name: Couche agents — groupe client_smtp import_playbook: groupes/client_smtp.yml - name: Couche agents — groupe client_backup