meta/metriques.yml : le role declare son exportateur, le moteur derive

Aucune metrique de SERVICE n etait collectee — seulement du systeme. Le crochet
serveur_prometheus_cibles_supplementaires existait, documente, et personne ne le
remplissait.

Le pendant de meta/supervision.yml et son contraire : une sonde rend un verdict
avec un TTL, un exportateur expose une serie. Est-ce casse, contre depuis quand
et vers ou.

Le critere des panneaux : une serie a sa place si elle PRECEDE un verdict ou si
elle n en aura JAMAIS. Le taux de succes du cache n en aura jamais — quand les
donnees depassent shared_buffers, rien ne casse et tout devient lent.

Compte en lecture seule (pg_monitor), et flux en clair avec sa dette inscrite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-14 11:35:14 -04:00
parent 8eb482e0c6
commit f4d57009ab
11 changed files with 321 additions and 8 deletions

View file

@ -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

View file

@ -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 |

View file

@ -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

View file

@ -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: ""

View file

@ -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('') }}"

View file

@ -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

View file

@ -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`.

View file

@ -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.

View file

@ -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

View file

@ -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/<role>/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

View file

@ -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/<role>/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 }}