sondes : le contrat ETAIT celui de Nagios, sans le savoir

Question posee : « tu connais le paquet monitoring-plugins ? » Oui — et le
contrat que docs/supervision-conception.md decrivait quelques heures plus
tot EST le sien, mot pour mot. Je l avais reinvente sans le nommer, alors
que positionnement.md dit l inverse : adopter aux seuils, ne pas
reimplementer.

Le document nomme desormais l API des greffons Nagios, ajoute le code 3
(INCONNU) et la partie « | metriques » qui manquaient, et dit la
consequence : un greffon standard EST une sonde valide, sans colle. Le
paquet en fournit 54. On n ecrit du shell que lorsque la verite a mesurer
est propre a Set-OPS. Le porteur separe maintenant texte et
performance_data.

ESSAYER UN VRAI GREFFON A REVELE DEUX DEFAUTS DU PORTEUR.

Un envoi refuse faisait taire TOUTES les sondes suivantes : rapporter
sortait en exit 1. Une sonde deposee mais non declaree supprimait le
rapport des autres, sante comprise — le tableau ne devenait pas rouge, il
devenait vide, et le ttl le perimait des heures plus tard sans dire
pourquoi.

Aucun delai de garde sur les sondes. check_disk 2.4.0-3+deb13u1 sur mon-01
tourne sans fin (etat R) quels que soient ses arguments : un greffon
STANDARD, sur une machine saine, qui boucle. Sans garde il figeait le
rapport entier toutes les quinze minutes. Chaque sonde tourne desormais
sous timeout 20 ; au-dela on rapporte INCONNU en le disant.

La lecon n est pas que les greffons standards sont mauvais : c est qu
adopter un standard ne dispense pas de l eprouver, et que le porteur doit
survivre a une sonde qui se comporte mal.

CONSTATE AU PASSAGE, NON CORRIGE : la configuration d exemple d Icinga
crie en permanence sur mon-01 — swap sur une VM sans swap, http sur un
port ou rien n ecoute, apt pour un paquet. Trois alarmes qui ne peuvent
que rester rouges, dans le seul endroit qui doit rester lisible.

Etat : certificat 14/14, sante 14/14 au tenant ; 7/7 au site.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

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-09 22:14:30 -04:00
parent 90228cb55e
commit 988d35745e
4 changed files with 119 additions and 11 deletions

View file

@ -1,5 +1,58 @@
# CHANGELOG — Set-OPS
## 2026-09-09 (7) — Le contrat des sondes ETAIT celui de Nagios, sans le savoir
Question posee : « tu connais le paquet `monitoring-plugins` ? »
Oui — et le contrat que `docs/supervision-conception.md` decrivait quelques heures plus
tot EST le sien, mot pour mot : une ligne sur stdout, `0/1/2` en code de sortie. Je l'avais
donc **reinvente sans le nommer**, alors que `positionnement.md` dit exactement l'inverse :
*adopter aux seuils, ne pas reimplementer*.
Le document nomme desormais l'**API des greffons Nagios**, ajoute `3 = INCONNU` et la
partie `| metriques` qui manquaient, et dit la consequence : **un greffon standard EST une
sonde valide, sans la moindre colle**. Le paquet en fournit 54 — `check_disk`,
`check_load`, `check_procs`, `check_ntp_time`, `check_smtp`, `check_pgsql`... On n'ecrit du
shell que lorsque la verite a mesurer est propre a Set-OPS, comme
`client_pki/certificat`, qui compare l'empreinte SERVIE a celle du disque : aucun greffon
ne sait ca.
Le porteur separe maintenant le texte des metriques (`performance_data`), parce que c'est
ce que le contrat porte et qu'Icinga sait les tracer.
### Essayer un vrai greffon a revele DEUX defauts du porteur
**1. Un envoi refuse faisait taire toutes les sondes suivantes.** `rapporter` sortait en
`exit 1` : une sonde deposee mais non declaree — donc refusee par le filtre d'API —
supprimait le rapport de toutes les autres, `sante` comprise. Le tableau ne devenait pas
rouge, il devenait VIDE, et le `ttl` le perimait des heures plus tard sans dire pourquoi.
*Un rapporteur qui ne peut pas dire UNE chose doit quand meme dire les autres.*
**2. Aucun delai de garde sur les sondes.** Et ce n'est pas theorique :
check_disk 2.4.0-3+deb13u1 sur mon-01 : etat R, ne rend jamais la main
quels que soient ses arguments (-p /, sans -p, seuils en % ou en G)
Un greffon STANDARD, sur une machine saine, qui **boucle**. Sans delai de garde il figeait
le rapport entier, toutes les quinze minutes, indefiniment. Chaque sonde tourne desormais
sous `timeout 20` ; au-dela, on rapporte INCONNU en le disant.
*La lecon n'est pas « les greffons standards sont mauvais ».* C'est qu'adopter un standard
ne dispense pas de l'eprouver — et que le porteur doit survivre a une sonde qui se comporte
mal, standard ou maison.
### Trois voyants rouges permanents, sur l'hote de supervision
Constate en cherchant : la configuration d'exemple livree par Icinga surveille `localhost`
et crie en permanence sur `mon-01` —
swap : SWAP CRITICAL - 0% free (une VM sans swap)
http : connect to 127.0.0.1:80 (rien n'ecoute la)
apt : 1 package upgradable
Trois alarmes qui ne peuvent que rester rouges, dans le seul endroit qui doit rester
lisible. **Non corrige** — c'est une decision d'exploitation, pas une correction de code.
## 2026-09-09 (6) — La supervision se DECLARE dans le role, comme les flux
**64 preuves (P01-P64).** Nouveau document : `docs/supervision-conception.md`.

View file

@ -28,18 +28,34 @@ C'est la classe d'échec que ce dépôt nomme partout ailleurs, en version docum
carte dit ce qui est surveillé, et personne ne surveille. *Une intention écrite n'est pas
une mesure.*
## Le contrat d'une sonde
## Le contrat d'une sonde : c'est celui des greffons Nagios
Une sonde est un **script local**, déposé par le rôle qui la possède, dans
`/usr/local/lib/setops/sondes/<nom>.sh` (0750, root).
| | |
|---|---|
| **sortie standard** | une ligne, le message lisible par un humain |
| **code de sortie** | `0` OK · `1` AVERTISSEMENT · `2` CRITIQUE |
| **sortie standard** | `TEXTE lisible` puis, optionnellement, `\| métriques` |
| **code de sortie** | `0` OK · `1` AVERTISSEMENT · `2` CRITIQUE · `3` INCONNU |
| **réseau** | aucun besoin : c'est le porteur qui pousse le résultat |
| **durée** | courte ; le porteur passe toutes les 15 min |
> **Ce contrat n'est pas de nous.** C'est l'**API des greffons Nagios**, que
> `monitoring-plugins` implémente depuis vingt ans et qu'Icinga parle nativement. La
> première rédaction de ce document la décrivait sans la nommer — autrement dit la
> réinventait. `positionnement.md` dit l'inverse : *adopter aux seuils, ne pas
> réimplémenter*.
**La conséquence pratique est grande.** Un greffon standard **est** une sonde valide, sans
la moindre colle : `check_disk`, `check_load`, `check_procs`, `check_ntp_time`,
`check_file_age`, `check_smtp`, `check_pgsql`… Le paquet en fournit **54**. Une sonde ne
s'écrit à la main que lorsque la vérité à mesurer est propre à Set-OPS — et c'était le cas
pour `client_pki/certificat`, qui compare l'empreinte *servie* à celle du disque : aucun
greffon ne sait ça.
Écrire du shell là où un greffon existe, c'est se donner du code à maintenir *et* se priver
de vingt ans de cas limites déjà rencontrés par d'autres.
Le rôle qui possède la sonde la **déploie lui-même** : il connaît ses chemins, ses
secrets, sa vérité de terrain. Il la **déclare** dans `meta/supervision.yml`, et c'est
cette déclaration que le moteur lit.

View file

@ -36,3 +36,7 @@ client_sante_unites_tolerees: []
# plus lente (une poignee TLS, une requete), et on ne veut pas qu'un hoquet la perime.
# Six passages du porteur.
client_sante_ttl_sondes: 5400
# Delai de garde de CHAQUE sonde. Une sonde est cense repondre vite ; celle qui depasse
# n'a pas de verdict, et on le dit (INCONNU) plutot que de figer tout le rapport.
client_sante_delai_sonde: 20

View file

@ -28,20 +28,41 @@ API="https://{{ client_sante_icinga_hote }}.{{ domaine_interne }}:5665"
TTL={{ client_sante_ttl_icinga }}
MOI="{{ inventory_hostname }}"
MOTDEPASSE="$(cat /etc/setops/icinga-api.pass)"
# Compte des envois refuses : l'unite systemd doit finir en echec si le rapporteur n'a
# pas pu parler, sinon rien ne distingue « tout va bien » de « personne n'ecoute ».
ECHECS_RAPPORT=0
rapporter() { # $1=service $2=code $3=texte $4=ttl
rapporter() { # $1=service $2=code $3=texte[|metriques] $4=ttl
local charge reponse
charge=$(python3 -c 'import json,sys; print(json.dumps({
"type": "Service", "service": sys.argv[1], "exit_status": int(sys.argv[2]),
"plugin_output": sys.argv[3], "ttl": int(sys.argv[4])}))' \
# LES METRIQUES SONT SEPAREES DU TEXTE, parce que le contrat des greffons Nagios les
# separe : `TEXTE | cle=valeur;avert;crit;min;max`. Tout pousser dans `plugin_output`
# afficherait une ligne illisible a l'ecran ET priverait Icinga de donnees qu'il sait
# tracer. C'est en adoptant le contrat qu'on herite de ce qu'il porte.
charge=$(python3 -c 'import json,sys
texte, _, perf = sys.argv[3].partition("|")
charge = {"type": "Service", "service": sys.argv[1], "exit_status": int(sys.argv[2]),
"plugin_output": texte.strip() or "(aucun message)", "ttl": int(sys.argv[4])}
if perf.strip():
charge["performance_data"] = perf.split()
print(json.dumps(charge))' \
"${MOI}!$1" "$2" "$3" "$4")
reponse=$(curl -sS --cacert "{{ client_sante_ca_verification }}" --max-time 20 \
-u "{{ client_sante_icinga_utilisateur }}:${MOTDEPASSE}" \
-H 'Accept: application/json' -H 'Content-Type: application/json' \
-X POST "${API}/v1/actions/process-check-result" -d "${charge}" 2>&1)
if ! printf '%s' "${reponse}" | grep -q '"code": *200'; then
echo "ECHEC du rapport Icinga : ${reponse}" >&2
exit 1
# ON N'INTERROMPT PAS — mesure du 2026-09-09.
#
# `exit 1` ici faisait taire TOUTES LES SONDES SUIVANTES. Une sonde inconnue d'Icinga
# (deposee mais pas declaree, donc refusee par le filtre d'API) suffisait a supprimer
# le rapport de toutes les autres — y compris `sante`. Le tableau ne devenait pas
# rouge : il devenait VIDE, et le `ttl` le perimait des heures plus tard sans que rien
# ne dise pourquoi.
#
# Un rapporteur qui ne peut pas dire UNE chose doit quand meme dire les autres.
echo "ECHEC du rapport Icinga pour « $1 » : ${reponse}" >&2
ECHECS_RAPPORT=$((ECHECS_RAPPORT + 1))
return 1
fi
}
@ -93,7 +114,20 @@ sondes() {
for f in /usr/local/lib/setops/sondes/*.sh; do
[[ -x "$f" ]] || continue
nom=$(basename "$f" .sh)
sortie=$("$f" 2>&1); code=$?
# DELAI DE GARDE SUR CHAQUE SONDE — mesure du 2026-09-09.
#
# Sans lui, une sonde qui BOUCLE fige le rapport entier, toutes les quinze minutes,
# indefiniment. Ce n'est pas theorique : `check_disk` 2.4.0-3+deb13u1 tourne sans fin
# sur `mon-01`, quels que soient ses arguments (etat `R`, pas un blocage d'E/S).
# Un greffon STANDARD, sur une machine saine.
#
# Une sonde qui n'a pas rendu son verdict en 20 s n'a pas de verdict : on rapporte
# INCONNU (3) en le disant, plutot que de laisser le porteur mourir en silence.
sortie=$(timeout {{ client_sante_delai_sonde }} "$f" 2>&1); code=$?
if (( code == 124 )); then
rapporter "$nom" 3 "Sonde ${nom} sans reponse apres {{ client_sante_delai_sonde }} s — aucun verdict." "{{ client_sante_ttl_sondes }}"
continue
fi
if (( code > 3 )); then
rapporter "$nom" 2 "Sonde ${nom} en erreur (code ${code}) : ${sortie:0:160}" "{{ client_sante_ttl_sondes }}"
else
@ -106,7 +140,7 @@ n=${#echecs[@]}
if [[ $n -eq 0 ]]; then
rapporter "sante" 0 "Aucune unite systemd en echec." "${TTL}"
sondes
exit 0
exit $(( ECHECS_RAPPORT > 0 ? 1 : 0 ))
fi
# CRITIQUE des la premiere, et non un seuil. Une unite en echec est soit un vrai
@ -114,3 +148,4 @@ fi
# ferait vivre le bruit indefiniment, et c'est exactement ce qu'on vient de corriger.
rapporter "sante" 2 "$n unite(s) systemd en echec : ${echecs[*]}" "${TTL}"
sondes
exit $(( ECHECS_RAPPORT > 0 ? 1 : 0 ))