diff --git a/CHANGELOG.md b/CHANGELOG.md index ea65af2..11e35ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,50 @@ # CHANGELOG — Set-OPS +## 2026-09-14 (7) — `meta/metriques.yml` : le second versant de la supervision + +Aucune metrique de SERVICE n'etait collectee. Prometheus ne scrutait que les +`node_exporter` : du systeme, et rien de PostgreSQL, de l'annuaire, des boites ou du +cache. Le crochet existait pourtant — +`serveur_prometheus_cibles_supplementaires`, documente, et que **personne ne remplissait**. + +### Le pendant de `meta/supervision.yml`, et son contraire + +| fichier | ce qu'il declare | la question | +|---|---|---| +| `supervision.yml` | une **sonde** rend un verdict avec un TTL | *est-ce casse ?* | +| `metriques.yml` | un **exportateur** expose une serie | *depuis quand, et vers ou ?* | + +Ce n'est pas une frontiere inventee : `docs/supervision-conception.md` la pose deja dans +l'autre sens — « une metrique a seuil appartient a Prometheus et Grafana ». Ce fichier est +l'autre moitie de cette phrase. + +### Le critere qui choisit les panneaux + +**Une serie a sa place ici si elle PRECEDE un verdict, ou si elle n'en aura JAMAIS.** + +Le taux de succes du cache n'aura jamais de verdict, et c'est pourquoi il compte : quand +les donnees depassent `shared_buffers`, la base va chercher sur disque de plus en plus +souvent. Rien ne casse, rien n'alerte, tout devient lent. C'est la panne qu'un graphe voit +et qu'une sonde ne verra jamais. + +### Ce que le moteur derive, et ce qu'il ne derive pas + +Il derive la **cible de scrutation** — le nom du dossier du role est le nom du GROUPE, +donc les cibles sont ses hotes actifs. Un role declare sans hote ne produit aucun job. + +Il ne derive **pas le flux** : le port 9187 s'ouvre par `meta/flux.yml`, la ou vivent deja +tous les flux de ce role. Deux fichiers pour un meme fait finissent par diverger. + +### Deux choses dites plutot que tues + +**Le compte de metriques est en lecture seule** (`pg_monitor`), et ne lit que les vues de +statistiques — pas une ligne de donnee applicative. Faire tourner l'exportateur en +`postgres` serait donner les cles de la base pour lire des compteurs. + +**Le flux est en CLAIR, et c'est une dette inscrite au fichier.** `client_metrique` sert +deja ses metriques en TLS ; celui-ci pas encore. La dette est ecrite dans la `raison` du +flux, avec son remede — `--web.config.file` + `client_pki`. + ## 2026-09-14 (6) — Le site avait l'orchestration, pas le moyen de la lancer `playbooks/site.yml` est genere par `orchestrer.py` et ordonne les couches pour diff --git a/docs/audit/preuve-2026-09-14.md b/docs/audit/preuve-2026-09-14.md index 9da4129..7eadf55 100644 --- a/docs/audit/preuve-2026-09-14.md +++ b/docs/audit/preuve-2026-09-14.md @@ -21,7 +21,7 @@ | P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | | P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. | | P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. | -| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 109 flux, schéma + matrice OK. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 110 flux, schéma + matrice OK. | | P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). | | P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site | | P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. | @@ -30,14 +30,14 @@ | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | -| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 29 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) | +| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 30 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 6 instance(s) federee(s), aucun index en collision. | | P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). | | P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. | | P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. | -| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 51 groupe(s), 90 regle(s). | +| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 4 tenant(s), 51 groupe(s), 92 regle(s). | | P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. | @@ -45,7 +45,7 @@ | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 65 scripts expliques et atteignables, 122 cibles make documentees, 68 roles avec README. | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). | -| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | +| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 35 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 45 document(s) declarent leur lecteur (40 genere(s) exempte(s)). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | @@ -55,14 +55,14 @@ | P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. | | P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 61 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre | -| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. | +| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 154 regle(s) du site. | | P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. | | P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). | | P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos | | P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). | | P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. | -| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (127 lignes). | -| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 240 regles `pass`), tous non consignes et tous motives. | +| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (128 lignes). | +| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 241 regles `pass`), tous non consignes et tous motives. | | P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 | | P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. | | P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 14 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | diff --git a/docs/registre-flux.md b/docs/registre-flux.md index 7dd2991..e082cb2 100644 --- a/docs/registre-flux.md +++ b/docs/registre-flux.md @@ -95,6 +95,7 @@ | `serveur_postfix` | egress | 11332 | tcp | localhost | clair | Filtre milter rspamd co-localisé (antispam + signature DKIM). | | `serveur_postfix` | egress | 12345 | tcp | serveur_dovecot | tls | Validation SASL des identifiants de soumission contre Dovecot. | | `serveur_postgresql` | ingress | 5432 | tcp | serveur_keycloak, serveur_forgejo, serveur_icinga, serveur_nextcloud | tls-requis | Connexions applicatives à PostgreSQL (verify-full ; pg_hba hostssl). | +| `serveur_postgresql` | ingress | 9187 | tcp | serveur_prometheus | clair | Metriques PostgreSQL lues par l'observatoire. Series, pas verdicts : ce qui derive lentement — cache qui decroche, connexions qui montent, bases qui grossissent — n'a que le graphe pour se faire voir. En clair pour l'instant, contrairement a `client_metrique` : dette inscrite, a fermer par `--web.config.file` + `client_pki`. | | `serveur_powerdns` | ingress | derive | udp | flotte | clair | Zone souveraine. 53 seul sur son hôte, 5300 sur la loopback derrière le résolveur. | | `serveur_powerdns` | ingress | derive | tcp | flotte | clair | Idem en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips). | | `serveur_powerdns` | egress | 53 | udp | externe | clair | Résolution sortante du serveur autoritatif POUR SES PROPRES besoins (apt, NTP) — il ne récurse pour aucun autre hôte. | @@ -118,7 +119,7 @@ ## Synthèse chiffrement -- **clair** : 39 flux +- **clair** : 40 flux - **n-a** : 8 flux - **ssh** : 8 flux - **starttls** : 6 flux diff --git a/exemples/vault.exemple.yml b/exemples/vault.exemple.yml index 54a79a7..7fc22f7 100644 --- a/exemples/vault.exemple.yml +++ b/exemples/vault.exemple.yml @@ -41,3 +41,14 @@ vault_forgejo_internal_token: "" # --- Observabilité / divers --- vault_grafana_admin: "" vault_redis: "" + +# LE COMPTE DE METRIQUES DE POSTGRESQL — lecture seule, role `pg_monitor`. +# +# Il ne lit que les vues de statistiques : pas une ligne de donnee applicative. Faire +# tourner l'exportateur en `postgres` serait donner les cles de la base pour lire des +# compteurs. +# +# VIDE = PAS D'EXPORTATEUR DU TOUT. Le role ne le pose pas, ne cree pas le compte, et +# Prometheus ne derive aucune cible. Degrader, jamais deviner — et surtout jamais un mot +# de passe par defaut. +vault_pg_exportateur: "" diff --git a/roles/serveur_postgresql/defaults/main.yml b/roles/serveur_postgresql/defaults/main.yml index f9a35d1..d301f7f 100644 --- a/roles/serveur_postgresql/defaults/main.yml +++ b/roles/serveur_postgresql/defaults/main.yml @@ -53,3 +53,22 @@ serveur_postgresql_tls_force: false # --- Sonde de supervision ----------------------------------------------------------- serveur_postgresql_sonde_pct_avert: 70 serveur_postgresql_sonde_pct_crit: 90 + +# --- L'EXPORTATEUR DE METRIQUES ------------------------------------------------------- +# +# Le pendant de la sonde : elle dit « est-ce casse ? », il dit « depuis quand, et vers +# ou ? ». Voir `meta/metriques.yml`, qui declare ce que le moteur en derive. +# +# UN COMPTE DEDIE, EN LECTURE SEULE, ET RIEN D'AUTRE. `pg_monitor` est un role fourni par +# PostgreSQL depuis la version 10 : il donne acces aux vues de statistiques et A ELLES +# SEULES — aucune donnee applicative. Faire tourner un exportateur en `postgres` serait +# donner les cles de la base pour lire des compteurs. +# +# LE MOT DE PASSE VIENT DE LA VOUTE, comme tous les autres. Vide, l'exportateur n'est pas +# pose du tout — degrader, jamais deviner, et surtout jamais un mot de passe par defaut. +serveur_postgresql_exportateur_actif: "{{ (vault_pg_exportateur | default('')) | length > 0 }}" +serveur_postgresql_exportateur_paquet: prometheus-postgres-exporter +serveur_postgresql_exportateur_service: prometheus-postgres-exporter +serveur_postgresql_exportateur_port: 9187 +serveur_postgresql_exportateur_utilisateur: setops_metriques +serveur_postgresql_exportateur_mot_de_passe: "{{ vault_pg_exportateur | default('') }}" diff --git a/roles/serveur_postgresql/handlers/main.yml b/roles/serveur_postgresql/handlers/main.yml index 4073f35..b0c9fef 100644 --- a/roles/serveur_postgresql/handlers/main.yml +++ b/roles/serveur_postgresql/handlers/main.yml @@ -10,3 +10,9 @@ ansible.builtin.systemd: name: "{{ serveur_postgresql_service_name }}" state: restarted + +- name: Redemarrer l exportateur de metriques + ansible.builtin.systemd: + name: "{{ serveur_postgresql_exportateur_service }}" + state: restarted + when: not ansible_check_mode diff --git a/roles/serveur_postgresql/meta/flux.yml b/roles/serveur_postgresql/meta/flux.yml index 7a8e20d..e763870 100644 --- a/roles/serveur_postgresql/meta/flux.yml +++ b/roles/serveur_postgresql/meta/flux.yml @@ -7,3 +7,33 @@ flux: pair: [serveur_keycloak, serveur_forgejo, serveur_icinga, serveur_nextcloud] chiffrement: tls-requis raison: "Connexions applicatives à PostgreSQL (verify-full ; pg_hba hostssl)." + # L'EXPORTATEUR DE METRIQUES — declare ICI, et pas dans `meta/metriques.yml`. + # + # Un flux est un flux : il vit la ou vivent les autres flux de ce role. `metriques.yml` + # declare QUOI mesurer et OU le lire ; ce fichier declare QUI a le droit d'y aller. + # Deux fichiers pour un meme fait finiraient par diverger — assez d'exemples ici. + # + # `serveur_prometheus` SEUL. Pas `flotte`, pas une source vide : l'exportateur publie + # l'etat interne de la base — nombre de connexions, tailles, ratios — et ca ne regarde + # que l'observatoire. + - sens: ingress + port: 9187 + protocole: tcp + pair: [serveur_prometheus] + # `clair`, ET C'EST UNE DETTE QU'ON ECRIT PLUTOT QUE DE LA TAIRE (2026-09-14). + # + # `client_metrique` sert deja ses metriques en TLS, certificat synchronise par + # `client_pki`. Cet exportateur-ci ne le fait pas encore : il faudrait lui poser un + # `--web.config.file` et l'abonner au meme renouvellement. + # + # CE QUI TRANSITE N'EST PAS ANODIN : nombre de connexions, tailles des bases, ratios + # de cache. Pas de donnee applicative, mais l'etat interne d'un serveur de donnees. + # Le flux ne sort pas de la zone, et la frontiere ne le laisse passer que depuis + # l'observatoire — ce n'est pas une excuse, c'est ce qui rend la dette tenable en + # attendant. + chiffrement: clair + raison: >- + Metriques PostgreSQL lues par l'observatoire. Series, pas verdicts : ce qui derive + lentement — cache qui decroche, connexions qui montent, bases qui grossissent — n'a + que le graphe pour se faire voir. En clair pour l'instant, contrairement a + `client_metrique` : dette inscrite, a fermer par `--web.config.file` + `client_pki`. diff --git a/roles/serveur_postgresql/meta/metriques.yml b/roles/serveur_postgresql/meta/metriques.yml new file mode 100644 index 0000000..fe428bd --- /dev/null +++ b/roles/serveur_postgresql/meta/metriques.yml @@ -0,0 +1,77 @@ +--- +# Metriques derivees du role. Le pendant de `meta/supervision.yml`, et son contraire. +# +# POURQUOI UN SECOND FICHIER, ET PAS UNE SECTION DU PREMIER. +# +# Les deux repondent a des questions DIFFERENTES, sur des donnees differentes, pour des +# consommateurs differents : +# +# `supervision.yml` -> une SONDE rend un VERDICT avec un TTL. « est-ce casse ? » +# `metriques.yml` -> un EXPORTATEUR expose une SERIE. « depuis quand, +# et vers ou ? » +# +# `docs/supervision-conception.md` le dit deja dans l'autre sens : « une metrique a seuil +# appartient a Prometheus et Grafana ; melanger les deux rendrait les deux moins +# lisibles ». Ce fichier est l'autre moitie de cette phrase. +# +# CE QUE LE MOTEUR EN DERIVE : la cible de scrutation de Prometheus +# (`serveur_prometheus_cibles_supplementaires`, un crochet qui existait et que personne ne +# remplissait) et les panneaux du tableau de bord. +# +# CE QU'IL N'EN DERIVE PAS, ET C'EST VOULU : le FLUX. Le port 9187 doit s'ouvrir depuis +# l'observatoire, et c'est `meta/flux.yml` qui le declare — la ou vivent deja tous les +# flux de ce role. Deux fichiers pour un meme fait finiraient par diverger ; on l'a vu +# assez souvent ici. + +# L'EXPORTATEUR : ce que le role installe pour qu'il y ait quelque chose a lire. +exportateur: + paquet: prometheus-postgres-exporter + service: prometheus-postgres-exporter + port: 9187 + job: postgresql + +# LES PANNEAUX : ce qui merite d'etre REGARDE DANS LE TEMPS. +# +# Ni un par metrique — l'exportateur en publie plus de deux cents — ni un par role. Un par +# QUESTION QU'ON SE POSE VRAIMENT quand quelque chose commence a aller moins bien. +# +# LE CRITERE : une serie a sa place ici si elle PRECEDE un verdict ou si elle n'en aura +# jamais. Ce qui bascule d'un coup appartient a Icinga ; ce qui derive lentement n'a que +# le graphe pour se faire voir. +panneaux: + - titre: "Connexions utilisées" + expr: "sum by (instance) (pg_stat_activity_count)" + unite: connexions + raison: >- + La sonde Icinga crie a 70 % et a 90 %. Ce panneau dit ce qu'elle ne peut pas dire : + depuis QUAND ca monte. Une base qui passe de 20 a 60 connexions en trois semaines + n'a rien casse — elle annonce la date ou elle cassera. + + - titre: "Taux de succès du cache" + expr: >- + sum by (instance) (rate(pg_stat_database_blks_hit[5m])) + / clamp_min(sum by (instance) (rate(pg_stat_database_blks_hit[5m]) + + rate(pg_stat_database_blks_read[5m])), 1) + unite: ratio + raison: >- + CELUI-LA N'AURA JAMAIS DE VERDICT, et c'est pourquoi il compte. Quand les donnees + depassent `shared_buffers`, la base va chercher sur disque de plus en plus souvent. + Rien ne casse, rien n'alerte : tout devient lent. C'est exactement la panne qu'un + graphe voit et qu'une sonde ne verra jamais. + + - titre: "Taille des bases" + expr: "sum by (datname) (pg_database_size_bytes)" + unite: octets + raison: >- + La croissance est la seule chose qu'on ne peut pas mesurer apres coup. Savoir qu'une + base a double en deux mois se decide deux mois plus tot — ou jamais. + + - titre: "Transactions par seconde" + expr: >- + sum by (instance) (rate(pg_stat_database_xact_commit[5m]) + + rate(pg_stat_database_xact_rollback[5m])) + unite: tps + raison: >- + Le contexte des trois autres. Des connexions qui montent a charge CONSTANTE ne + disent pas la meme chose que des connexions qui montent parce que le travail a + double — et le geste qui suit n'est pas le meme non plus. diff --git a/roles/serveur_postgresql/tasks/main.yml b/roles/serveur_postgresql/tasks/main.yml index 46d8305..a4a8e98 100644 --- a/roles/serveur_postgresql/tasks/main.yml +++ b/roles/serveur_postgresql/tasks/main.yml @@ -234,3 +234,62 @@ owner: root group: root mode: "0750" + +# --- L'EXPORTATEUR DE METRIQUES ------------------------------------------------------- +# +# CE QU'IL AJOUTE A LA SONDE : la sonde repond « est-ce casse ? » et ne garde aucune +# memoire. L'exportateur publie des SERIES — ce qui derive lentement n'a que le graphe +# pour se faire voir. Voir `meta/metriques.yml` pour ce que le moteur en derive. +- name: Creer le compte de metriques (lecture seule, pg_monitor) + community.postgresql.postgresql_user: + name: "{{ serveur_postgresql_exportateur_utilisateur }}" + password: "{{ serveur_postgresql_exportateur_mot_de_passe }}" + role_attr_flags: "LOGIN" + state: present + become: true + become_user: postgres + no_log: true + when: serveur_postgresql_exportateur_actif | bool + +# `pg_monitor` DONNE LES VUES DE STATISTIQUES, ET RIEN D'AUTRE. Pas une ligne de donnee +# applicative. C'est la difference entre superviser une base et l'ouvrir. +- name: Donner a ce compte le role de supervision, et lui seul + community.postgresql.postgresql_membership: + group: pg_monitor + target_role: "{{ serveur_postgresql_exportateur_utilisateur }}" + state: present + become: true + become_user: postgres + when: serveur_postgresql_exportateur_actif | bool + +- name: Installer l'exportateur de metriques + ansible.builtin.apt: + name: "{{ serveur_postgresql_exportateur_paquet }}" + state: present + when: serveur_postgresql_exportateur_actif | bool + +# LA CHAINE DE CONNEXION NE PASSE PAS PAR LA LIGNE DE COMMANDE. Un `DATA_SOURCE_NAME` en +# argument serait lisible dans `ps` par tout le monde sur la machine ; dans un fichier a +# 0600, il ne l'est que par root et le service. +- name: Poser la chaine de connexion de l'exportateur + ansible.builtin.copy: + content: | + # Gere par Set-OPS (role serveur_postgresql). Ne pas editer a la main. + DATA_SOURCE_NAME="postgresql://{{ serveur_postgresql_exportateur_utilisateur + }}:{{ serveur_postgresql_exportateur_mot_de_passe + }}@127.0.0.1:5432/postgres?sslmode=disable" + ARGS="--web.listen-address=:{{ serveur_postgresql_exportateur_port }}" + dest: /etc/default/prometheus-postgres-exporter + owner: root + group: root + mode: "0600" + no_log: true + notify: Redemarrer l exportateur de metriques + when: serveur_postgresql_exportateur_actif | bool + +- name: Activer et demarrer l'exportateur de metriques + ansible.builtin.systemd: + name: "{{ serveur_postgresql_exportateur_service }}" + enabled: true + state: started + when: serveur_postgresql_exportateur_actif | bool diff --git a/roles/serveur_prometheus/tasks/main.yml b/roles/serveur_prometheus/tasks/main.yml index 8adb028..388424e 100644 --- a/roles/serveur_prometheus/tasks/main.yml +++ b/roles/serveur_prometheus/tasks/main.yml @@ -6,6 +6,44 @@ update_cache: true cache_valid_time: 3600 +# --- LES CIBLES QUE LES ROLES DECLARENT (2026-09-14) ---------------------------------- +# +# `serveur_prometheus_cibles_supplementaires` existait depuis longtemps — une liste libre, +# documentee, et que PERSONNE ne remplissait. Resultat : Prometheus ne scrutait que les +# `node_exporter`. Aucune metrique de SERVICE n'etait collectee — ni PostgreSQL, ni +# l'annuaire, ni les boites, ni le cache. Seulement du systeme. +# +# CE QUI LES REMPLIT DESORMAIS : `roles//meta/metriques.yml`, croise avec les hotes +# qui portent ce groupe. Exactement le patron de `client_sante` pour les sondes, et de +# `resoudre_flux` pour les pare-feu : LE ROLE DECLARE, LE MOTEUR DERIVE. +# +# UNE VALEUR DONNEE AU PLAN GAGNE : `| default` laisse un ecosysteme ajouter une cible qui +# ne vient d'aucun role — un equipement, un service tiers. La derivation complete, elle ne +# remplace pas. +- name: Relever les metriques que les roles declarent + ansible.builtin.find: + paths: "{{ role_path }}/.." + patterns: metriques.yml + recurse: true + depth: 3 + delegate_to: localhost + become: false + run_once: true + check_mode: false + register: serveur_prometheus_metas + +- name: Lire chaque declaration de metriques + ansible.builtin.slurp: + src: "{{ item.path }}" + delegate_to: localhost + become: false + run_once: true + check_mode: false + loop: "{{ serveur_prometheus_metas.files }}" + loop_control: + label: "{{ item.path | dirname | dirname | basename }}" + register: serveur_prometheus_declarations + - name: Deployer la configuration Prometheus ansible.builtin.template: src: prometheus.yml.j2 diff --git a/roles/serveur_prometheus/templates/prometheus.yml.j2 b/roles/serveur_prometheus/templates/prometheus.yml.j2 index 776af4b..fdc2d70 100644 --- a/roles/serveur_prometheus/templates/prometheus.yml.j2 +++ b/roles/serveur_prometheus/templates/prometheus.yml.j2 @@ -25,6 +25,33 @@ scrape_configs: | map(attribute='ansible_host') | map('regex_replace', '$', ':' ~ serveur_prometheus_port_node) | list | to_json }} +{# --- LES CIBLES QUE LES ROLES DECLARENT (2026-09-14) --------------------------- + Chaque `roles//meta/metriques.yml` decrit son exportateur ; le nom du DOSSIER + est le nom du GROUPE, donc les cibles sont les hotes actifs de ce groupe. + + UN ROLE DECLARE SANS HOTE NE PRODUIT AUCUN JOB. Prometheus n'a pas a porter une cible + qui n'existe pas, et son journal n'a pas a se remplir de refus previsibles. + + ORDRE STABLE : `sort` sur les hotes, comme pour `node` juste au-dessus. Sans lui, le + fichier se rend differemment a chaque passage et Prometheus redemarre pour rien — + defaut trouve le 2026-08-09, qu'on ne va pas reintroduire ici. #} +{% for decl in serveur_prometheus_declarations.results | default([]) %} +{% set role = decl.item.path | dirname | dirname | basename %} +{% set meta = decl.content | b64decode | from_yaml %} +{% set expo = meta.exportateur | default(none) %} +{% set cibles = (groups.get(role, []) | intersect(groups.get('hotes_actifs', [])) | sort) + | map('extract', hostvars) | selectattr('ansible_host', 'defined') + | map(attribute='ansible_host') + | map('regex_replace', '$', ':' ~ (expo.port | default(0))) | list %} +{% if expo and cibles %} + + - job_name: {{ expo.job }} + static_configs: + - targets: {{ cibles | to_json }} +{% endif %} +{% endfor %} +{# Les cibles ECRITES AU PLAN completent la derivation, elles ne la remplacent pas : + un equipement ou un service tiers n'a aucun role Set-OPS pour le declarer. #} {% for job in serveur_prometheus_cibles_supplementaires %} - job_name: {{ job.job }}