cache-apt scindee — et la scission a revele que moteur aurait alarme a tort
LA SCISSION. cache-apt fondait deux causes aux DELAIS differents : « ne repond pas » arrete tout apt de l ecosysteme, on agit dans la minute ; « volume a 90 % » est un billet pour demain. Les fondre obligeait soit a reveiller quelqu un pour un disque, soit a traiter une panne comme un billet. Deux sondes, chacune avec son etat, son historique, son acquittement — et prouvees INDEPENDANTES : port ferme -> cache-apt CRITIQUE, cache-apt-volume OK seuil impossible -> cache-apt OK, cache-apt-volume AVERTISSEMENT CE QUE LA VERIFICATION A REVELE. Un resultat pousse et accepte (code 200) n apparaissait pas en base. Hypothese testee et confirmee : IcingaDB n ECRIT service_state QUE SUR CHANGEMENT D ETAT. Un OK identique repete ne produit aucune ecriture ; un AVERTISSEMENT pousse ensuite est ecrit en 13 s. Or la sonde moteur, ecrite quelques heures plus tot, lisait exactement max(last_update) de service_state pour juger de la fraicheur. Elle mesurait le CHANGEMENT. Sur un ecosysteme parfaitement STABLE — celui qu on veut — plus rien ne change, last_update vieillit, et elle serait passee en avertissement a 15 min puis en critique a 90. Une alarme qui se declenche PARCE QUE tout va bien, avec un delai qui l aurait rendue difficile a rattacher a sa cause. La bonne source existait : icingadb_instance porte le battement du synchroniseur, ecrit en continu. Releve a 1 s sur un systeme sain. Seuils ramenes de 15/90 min a 1/5 min. Controle negatif rejoue. Dix-huit services, tous verts sauf sauvegarde 7/9 (deja connu). 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:
parent
3e8e052670
commit
a5d9040a11
8 changed files with 132 additions and 33 deletions
46
CHANGELOG.md
46
CHANGELOG.md
|
|
@ -1,5 +1,51 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-10 (2) — `cache-apt` scindee, et le defaut que la scission a revele
|
||||
|
||||
### La scission
|
||||
|
||||
`cache-apt` fondait deux causes dont les DELAIS different : « ne repond pas » arrete tout
|
||||
`apt` de l'ecosysteme — on agit dans la minute — tandis que « volume a 90 % » est un billet
|
||||
pour demain. Les fondre obligeait soit a reveiller quelqu'un pour un disque, soit a traiter
|
||||
une panne comme un billet.
|
||||
|
||||
Deux sondes desormais, chacune avec son etat, son historique et son acquittement. Prouve
|
||||
qu'elles sont INDEPENDANTES, et c'etait tout l'enjeu :
|
||||
|
||||
port ferme -> cache-apt [2] CRITIQUE cache-apt-volume [0] OK
|
||||
seuil impossible-> cache-apt [0] OK cache-apt-volume [1] AVERTISSEMENT
|
||||
|
||||
### Ce que la verification a revele : `moteur` aurait alarme PARCE QUE tout allait bien
|
||||
|
||||
En verifiant que les deux services arrivaient bien dans Icinga, un resultat pousse et
|
||||
accepte (`code 200`) n'apparaissait pas en base. Hypothese testee et confirmee :
|
||||
**IcingaDB n'ECRIT `service_state` QUE SUR CHANGEMENT D'ETAT.** Un OK identique repete ne
|
||||
produit aucune ecriture ; un AVERTISSEMENT pousse ensuite est ecrit en 13 secondes.
|
||||
|
||||
Or la sonde `moteur`, ecrite quelques heures plus tot, lisait exactement
|
||||
`max(last_update)` de `service_state` pour juger que « l'etat est frais ». Elle mesurait
|
||||
donc le CHANGEMENT, pas la fraicheur.
|
||||
|
||||
**Consequence : sur un ecosysteme parfaitement stable — celui qu'on veut — plus rien ne
|
||||
change, `last_update` vieillit, et la sonde serait passee en avertissement a 15 minutes
|
||||
puis en critique a 90.** Une alarme qui se declenche PARCE QUE tout va bien, avec un delai
|
||||
qui l'aurait rendue difficile a rattacher a sa cause.
|
||||
|
||||
C'est le piege que `docs/supervision-conception.md` interdit — *une alarme toujours allumee
|
||||
apprend a ne plus regarder* — sous sa forme la plus sournoise : differee.
|
||||
|
||||
**La bonne source existait** : `icingadb_instance` porte le battement du synchroniseur,
|
||||
ecrit en continu qu'il y ait ou non des changements. Releve a **1 seconde** sur un systeme
|
||||
sain. Les seuils passent de 15/90 minutes a **1/5 minutes** : sur un battement, une minute
|
||||
de silence est deja anormale.
|
||||
|
||||
Controle negatif rejoue par parametre.
|
||||
|
||||
### Etat
|
||||
|
||||
Dix-huit services, tous verts sauf `sauvegarde` a 7/9 — deux noeuds sans donnee a
|
||||
emporter, deja connu et confirme.
|
||||
|
||||
## 2026-09-10 — Chaque role porte desormais sa sonde
|
||||
|
||||
**Quatorze sondes**, declarees dans `meta/supervision.yml`, deposees par le role qui
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
|
||||
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
|
||||
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 14 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_pki/certificat, serveur_artefacts/cache-apt, serveur_dovecot/boites, serveur |
|
||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 15 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_pki/certificat, serveur_artefacts/cache-apt, serveur_artefacts/cache-apt-vol |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,9 @@
|
|||
sondes:
|
||||
- nom: cache-apt
|
||||
ttl: 5400
|
||||
raison: Le cache d'artefacts repond-il sur son port, et lui reste-t-il de la place ? Un cache muet
|
||||
fait echouer tout apt de l'ecosysteme ; un cache plein le fait en silence.
|
||||
raison: Le cache d'artefacts repond-il ? S'il se tait, TOUT apt de l'ecosysteme echoue — on agit dans
|
||||
la minute.
|
||||
- nom: cache-apt-volume
|
||||
ttl: 5400
|
||||
raison: 'Reste-t-il de la place au cache ? Un cache plein cesse d''ecrire sans se plaindre, et l''echec
|
||||
apparait chez les clients. Verdict distinct de la disponibilite : il appelle un billet, pas un reveil.'
|
||||
|
|
|
|||
|
|
@ -137,3 +137,11 @@
|
|||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
||||
- name: Deposer la sonde « cache-apt-volume »
|
||||
ansible.builtin.template:
|
||||
src: sonde-cache-apt-volume.sh.j2
|
||||
dest: /usr/local/lib/setops/sondes/cache-apt-volume.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/bash
|
||||
# GENERE par Set-OPS (role serveur_artefacts). Ne pas editer a la main.
|
||||
#
|
||||
# SONDE « cache-apt-volume » — reste-t-il de la place au cache ?
|
||||
#
|
||||
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
|
||||
# Mise en defaut PAR PARAMETRE : `serveur_artefacts_sonde_pct_avert` / `_pct_crit`.
|
||||
#
|
||||
# SEPAREE DE LA DISPONIBILITE (2026-09-10) : ce verdict-ci appelle un billet, pas un
|
||||
# reveil. Un cache plein ne se plaint pas — il cesse simplement de servir ce qu'il n'a pas
|
||||
# pu ecrire, et l'echec apparait chez les CLIENTS, loin d'ici. D'ou des seuils qui
|
||||
# previennent au lieu de constater.
|
||||
set -uo pipefail
|
||||
|
||||
REP={{ serveur_artefacts_sonde_repertoire }}
|
||||
AVERT={{ serveur_artefacts_sonde_pct_avert }}
|
||||
CRIT={{ serveur_artefacts_sonde_pct_crit }}
|
||||
|
||||
pct=$(df --output=pcent "${REP}" 2>/dev/null | tail -1 | tr -dc '0-9')
|
||||
[[ -n "${pct}" ]] || { echo "Volume du cache illisible (${REP})."; exit 2; }
|
||||
taille=$(du -sh "${REP}" 2>/dev/null | cut -f1)
|
||||
|
||||
if (( pct >= CRIT )); then
|
||||
echo "Volume du cache a ${pct}% (${taille}) — il va cesser d'ecrire, et l'echec se verra chez les clients.|pct=${pct}%;${AVERT};${CRIT};0;100"
|
||||
exit 2
|
||||
fi
|
||||
if (( pct >= AVERT )); then
|
||||
echo "Volume du cache a ${pct}% (${taille}), seuil ${AVERT}%.|pct=${pct}%;${AVERT};${CRIT};0;100"
|
||||
exit 1
|
||||
fi
|
||||
echo "Volume du cache : ${taille} sur un volume a ${pct}%.|pct=${pct}%;${AVERT};${CRIT};0;100"
|
||||
|
|
@ -1,34 +1,26 @@
|
|||
#!/bin/bash
|
||||
# GENERE par Set-OPS (role serveur_artefacts). Ne pas editer a la main.
|
||||
#
|
||||
# SONDE « cache-apt » — le cache apt repond, et il lui reste de la place
|
||||
# SONDE « cache-apt » — le cache repond-il ?
|
||||
#
|
||||
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne sur la
|
||||
# sortie standard, 0 OK / 1 AVERTISSEMENT / 2 CRITIQUE. Le porteur `client_sante` pousse.
|
||||
# Mise en defaut PAR PARAMETRE : `serveur_artefacts_sonde_port` (port ferme) ou `_pct_crit` (seuil impossible).
|
||||
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
|
||||
# Mise en defaut PAR PARAMETRE : `serveur_artefacts_sonde_port` (port ferme).
|
||||
#
|
||||
# ELLE NE MESURE PLUS LE VOLUME, ET C'EST LE POINT (2026-09-10). Les deux etaient fondus
|
||||
# dans une seule sonde. Or « le cache ne repond pas » arrete TOUT `apt` de l'ecosysteme —
|
||||
# on agit dans la minute — tandis que « le volume est a 90 % » est un billet pour demain.
|
||||
# Meme personne, meme geste peut-etre, mais pas le meme DELAI : les fondre obligeait soit
|
||||
# a reveiller quelqu'un pour un disque, soit a traiter une panne comme un billet.
|
||||
#
|
||||
# Le volume vit desormais dans `cache-apt-volume`, avec son propre etat, son propre
|
||||
# historique et son propre acquittement.
|
||||
set -uo pipefail
|
||||
|
||||
PORT={{ serveur_artefacts_sonde_port }}
|
||||
REP={{ serveur_artefacts_sonde_repertoire }}
|
||||
AVERT={{ serveur_artefacts_sonde_pct_avert }}
|
||||
CRIT={{ serveur_artefacts_sonde_pct_crit }}
|
||||
|
||||
# ON DEMANDE UNE PAGE, ON NE SE CONTENTE PAS D'UN connect(). Un `connect()` reussit contre
|
||||
# un processus mort-vivant qui n'a plus rien a servir ; seule une reponse prouve le
|
||||
# service. C'est la lecon du depot : « un connect() TCP ne prouve rien ».
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "http://127.0.0.1:${PORT}/acng-report.html" 2>/dev/null)
|
||||
[[ "${code}" == "200" ]] || { echo "Le cache ne repond pas sur ${PORT} (HTTP ${code:-aucune reponse})."; exit 2; }
|
||||
|
||||
pct=$(df --output=pcent "${REP}" 2>/dev/null | tail -1 | tr -dc '0-9')
|
||||
[[ -n "${pct}" ]] || { echo "Cache joignable, mais ${REP} illisible."; exit 1; }
|
||||
taille=$(du -sh "${REP}" 2>/dev/null | cut -f1)
|
||||
|
||||
if (( pct >= CRIT )); then
|
||||
echo "Cache a ${pct}% du volume (${taille}) — apt echouera bientot pour tout l'ecosysteme."
|
||||
exit 2
|
||||
fi
|
||||
if (( pct >= AVERT )); then
|
||||
echo "Cache a ${pct}% du volume (${taille}), seuil ${AVERT}%."
|
||||
exit 1
|
||||
fi
|
||||
echo "Cache apt : repond sur ${PORT}, ${taille} sur un volume a ${pct}%.|pct=${pct}%;${AVERT};${CRIT};0;100"
|
||||
[[ "${code}" == "200" ]] || { echo "Le cache ne repond pas sur ${PORT} (HTTP ${code:-aucune reponse}) — tout apt de l'ecosysteme echoue."; exit 2; }
|
||||
echo "Cache apt : repond sur ${PORT}."
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ serveur_icinga_destinataire: ""
|
|||
|
||||
# --- Sonde de supervision -----------------------------------------------------------
|
||||
serveur_icinga_sonde_config_db: /etc/icingadb/config.yml
|
||||
# Les controles passifs ont un `ttl` de 45 min au plus long ; une base qui n'a rien recu
|
||||
# depuis deux fois cela ne reflete plus rien.
|
||||
serveur_icinga_sonde_age_avert: 900
|
||||
serveur_icinga_sonde_age_crit: 5400
|
||||
# SEUILS SUR LE BATTEMENT D'ICINGADB, qui bat en continu (releve : 1 s sur un systeme
|
||||
# sain). Les premiers seuils — 15 et 90 min — visaient la fraicheur des ETATS, et
|
||||
# auraient alarme sur un ecosysteme stable ou plus rien ne change. Sur un battement, une
|
||||
# minute de silence est deja anormale.
|
||||
serveur_icinga_sonde_age_avert: 60
|
||||
serveur_icinga_sonde_age_crit: 300
|
||||
|
|
|
|||
|
|
@ -23,17 +23,33 @@ base=$(grep -m1 -A5 '^database:' "${CONF}" 2>/dev/null | grep -m1 'database:' |
|
|||
util=$(grep -m1 -A6 '^database:' "${CONF}" 2>/dev/null | grep -m1 'user:' | awk '{print $2}')
|
||||
[[ -n "${hote}" && -n "${mdp}" ]] || { echo "Configuration IcingaDB illisible (${CONF})."; exit 2; }
|
||||
|
||||
# LE BATTEMENT D'ICINGADB, PAS LA FRAICHEUR DES ETATS (correction du 2026-09-10).
|
||||
#
|
||||
# La premiere version lisait `max(last_update)` de `service_state`. Mesure : IcingaDB
|
||||
# n'ECRIT QUE SUR CHANGEMENT D'ETAT. Un resultat identique repete ne produit aucune
|
||||
# ecriture — verifie en poussant un OK (rien ne bouge) puis un AVERTISSEMENT (ecrit en
|
||||
# 13 s).
|
||||
#
|
||||
# La sonde mesurait donc le CHANGEMENT, pas la fraicheur. Sur un ecosysteme parfaitement
|
||||
# STABLE — celui qu'on veut — plus rien ne change, `last_update` vieillit, et la sonde
|
||||
# serait passee en avertissement a 15 min puis en critique a 90 min. Une alarme qui se
|
||||
# declenche PARCE QUE tout va bien : exactement le piege que ce document interdit, avec
|
||||
# un delai qui l'aurait rendue difficile a comprendre.
|
||||
#
|
||||
# `icingadb_instance` porte le battement du synchroniseur, ecrit en continu qu'il y ait
|
||||
# ou non des changements. C'est lui qui dit « la chaine moteur -> Redis -> base est
|
||||
# vivante » — releve a 1 seconde sur un systeme sain.
|
||||
age=$(PGPASSWORD="${mdp}" psql -h "${hote}" -U "${util:-icingadb}" -d "${base:-icingadb}" -Atc \
|
||||
"select coalesce(extract(epoch from now()) - max(last_update)/1000, 999999)::bigint from service_state" 2>/dev/null)
|
||||
"select coalesce((extract(epoch from now()) - max(heartbeat)/1000)::bigint, 999999) from icingadb_instance" 2>/dev/null)
|
||||
[[ "${age}" =~ ^-?[0-9]+$ ]] || { echo "La base de supervision ne repond pas (${hote})."; exit 2; }
|
||||
(( age < 0 )) && age=0
|
||||
|
||||
if (( age >= CRIT )); then
|
||||
echo "Etat FIGE : rien de neuf en base depuis ${age} s — la supervision montre le passe.|age=${age}s;${AVERT};${CRIT};0;"
|
||||
echo "Synchronisation FIGEE : IcingaDB n'a pas battu depuis ${age} s — la supervision montre le passe.|age=${age}s;${AVERT};${CRIT};0;"
|
||||
exit 2
|
||||
fi
|
||||
if (( age >= AVERT )); then
|
||||
echo "Etat vieillissant : ${age} s sans mise a jour (seuil ${AVERT}).|age=${age}s;${AVERT};${CRIT};0;"
|
||||
echo "Battement d'IcingaDB vieillissant : ${age} s (seuil ${AVERT}).|age=${age}s;${AVERT};${CRIT};0;"
|
||||
exit 1
|
||||
fi
|
||||
echo "Moteur et synchronisation vivants : base rafraichie il y a ${age} s.|age=${age}s;${AVERT};${CRIT};0;"
|
||||
echo "Moteur et synchronisation vivants : IcingaDB a battu il y a ${age} s.|age=${age}s;${AVERT};${CRIT};0;"
|
||||
|
|
|
|||
Loading…
Reference in a new issue