grafana : le tableau Matériel dit quoi faire

- materiel.py : textes accentués et champ action par mesure (seuils inchangés)
- tableau-materiel : mode d'emploi en tête ; descriptions en trois temps
  (ce que ça mesure, seuils, quoi faire)
- CHANGELOG 69

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-10-04 00:49:28 -04:00
parent 49f3d28865
commit b3debd6ca9
3 changed files with 84 additions and 32 deletions

View file

@ -1,5 +1,29 @@
# CHANGELOG — Set-OPS
## 2026-10-04 (69) — Le tableau Matériel dit quoi faire
**Demande de l'exploitant** : rendre les tableaux Grafana plus parlants — « phrases et
contexte » d'abord, sur le tableau Matériel ; un tableau de synthèse ensuite.
**Constat** : les panneaux avaient déjà leur « pourquoi », mais au survol seulement, sans
accents (écrits comme des commentaires de code : « CONCU », « annoncee »), et aucun ne disait
quoi faire quand il change de couleur.
**Fait** :
- `scripts/materiel.py` : les 14 textes réécrits en français accentué, et un champ `action`
par mesure — quoi faire à l'orange ou au rouge. Seuils et expressions inchangés : la sonde
Icinga n'en lit pas les textes.
- `tableau-materiel.json.j2` : un mode d'emploi en tête (à quelle question répond chaque
rangée, ce que disent les couleurs) ; chaque description composée en trois temps — ce que
ça mesure, les seuils, **quoi faire** ; les descriptions écrites à la main (ventilateurs,
puissance, usure, secteurs, heures) reprises sur le même patron.
**Appliqué au site** (seul à porter ce tableau) : rendu vérifié en local (JSON valide,
28 panneaux), puis servi par Grafana sans erreur de chargement. Le passage a aussi monté
Grafana du site de 13.2.1 à 13.2.3, son épingle (66).
**Validation** : `ansible-lint` (0 violation), `make verifier` conforme (83/83).
## 2026-10-04 (68) — Forgejo 16.0.5 et Nextcloud 34.0.4 ; Nextcloud apprend à monter, et vérifie sa signature
**Demande de l'exploitant** : poursuivre le relevé des logiciels en retard (Forgejo 16.0.2,

View file

@ -21,6 +21,30 @@
{"color": "red", "value": f.crit}]} | tojson }}
{%- endmacro -%}
{#- LES PHRASES DES PANNEAUX (2026-10-04). Trois temps, toujours les memes : ce que ca
mesure (`raison`), ou sont les seuils, et QUOI FAIRE quand la couleur change (`action`,
dans le catalogue). Grafana les montre au survol du titre (icone i), en Markdown. -#}
{%- macro desc_temp(f) -%}
{{ f.raison ~ "\n\n**Seuils** : orange à " ~ f.avert ~ " °C, rouge à " ~ f.crit ~ " °C — le verdict Icinga est lissé sur " ~ (f.jugee_sur | replace("m", " min")) ~ " : un pic d'une seconde n'est pas une panne.\n\n**Si c'est orange ou rouge** : " ~ f.action }}
{%- endmacro -%}
{%- macro desc_ctrl(c) -%}
{{ c.raison ~ "\n\n**Si la tuile n'affiche pas 0** : " ~ c.action }}
{%- endmacro -%}
{#- ---- MODE D'EMPLOI ---------------------------------------------------------------- #}
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "text", "id": y.id, "title": "",
"gridPos": {"h": 4, "w": 24, "x": 0, "y": y.v},
"options": {"mode": "markdown", "content":
"**Comment lire ce tableau.** Chaque rangée répond à une question.\n\n"
~ "- **État en direct** — y a-t-il quelque chose à faire maintenant ? Vert : rien. Orange : à planifier. Rouge : à traiter.\n"
~ "- **Températures** — depuis quand, et sur quelle machine ? Les pointillés sont les seuils.\n"
~ "- **Ventilateurs et puissance** — une chaleur vient-elle de la charge ou du refroidissement ?\n"
~ "- **Stockage** — quels disques vieillissent, et lesquels se dégradent ?\n\n"
~ "Survoler le **titre** d'un panneau (icône ⓘ) : il dit ce qu'il mesure, pourquoi cela compte, et **quoi faire** s'il change de couleur. Seuils identiques à ceux du service Icinga « materiel »."}}) -%}
{%- set y.v = y.v + 4 -%}
{#- ---- RANGEE : ETAT EN DIRECT ------------------------------------------------------ #}
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({"type": "row", "id": y.id, "title": "État en direct", "collapsed": false,
@ -32,7 +56,7 @@
{%- set _ = panneaux.append({
"type": "stat", "id": y.id,
"title": f.titre ~ " — maximum",
"description": f.raison,
"description": desc_temp(f),
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 5, "w": 6, "x": loop.index0 * 6, "y": y.v},
"fieldConfig": {"defaults": {"unit": "celsius", "decimals": 0,
@ -53,7 +77,7 @@
{%- set _ = panneaux.append({
"type": "stat", "id": y.id,
"title": c.titre,
"description": c.raison,
"description": desc_ctrl(c),
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 4, "w": largeur, "x": (loop.index0 * largeur) % 24, "y": y.v + ((loop.index0 * largeur) // 24) * 4},
"fieldConfig": {"defaults": {"unit": "none", "decimals": 0,
@ -80,7 +104,7 @@
{%- set _ = panneaux.append({
"type": "timeseries", "id": y.id,
"title": f.titre,
"description": f.raison ~ " — Avertissement " ~ f.avert ~ " °C, critique " ~ f.crit ~ " °C (verdict Icinga lisse sur " ~ f.jugee_sur ~ ").",
"description": desc_temp(f),
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 12, "x": 0 if loop.index0 % 2 == 0 else 12, "y": y.v + (loop.index0 // 2) * 9},
"fieldConfig": {"defaults": {
@ -107,7 +131,7 @@
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "timeseries", "id": y.id, "title": "Ventilateurs",
"description": "Seuls les connecteurs qui ont tourne dans les dernieres 24 h : un connecteur vide lit 0 depuis toujours et n'apprend rien.",
"description": "Vitesse de chaque ventilateur, en tours par minute. Seuls les connecteurs qui ont tourné dans les dernières 24 h sont tracés : un connecteur vide lit 0 depuis toujours et n'apprend rien.\n\n**À lire avec les températures** : une température qui monte pendant que les ventilateurs restent bas parle d'une courbe de ventilation trop douce ; une courbe qui tombe à 0, d'un ventilateur arrêté (tuile rouge plus haut).",
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 12, "x": 0, "y": y.v},
"fieldConfig": {"defaults": {"unit": "rotrpm", "decimals": 0, "color": {"mode": "palette-classic"},
@ -121,7 +145,7 @@
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "timeseries", "id": y.id, "title": "Puissance du processeur (PPT)",
"description": "Puissance du boitier processeur annoncee par le pilote amdgpu (APU integre). Elle suit la charge : une temperature qui monte SANS puissance qui monte parle du refroidissement.",
"description": "Puissance du processeur (PPT), annoncée par le pilote amdgpu de l'APU intégré. Ce n'est pas la consommation de la machine entière : aucune mesure ne la donne.\n\n**À lire avec les températures** : puissance et température qui montent ensemble, c'est la charge. Une température qui monte **sans** que la puissance monte, c'est le refroidissement : ventilateurs, poussière, pâte thermique.",
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 12, "x": 12, "y": y.v},
"fieldConfig": {"defaults": {"unit": "watt", "decimals": 1, "color": {"mode": "palette-classic"},
@ -142,7 +166,7 @@
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "bargauge", "id": y.id, "title": "Usure des NVMe",
"description": "Part de l'endurance annoncee deja consommee. Avertissement a 80 % : a commander, pas encore a remplacer.",
"description": "Part de l'endurance garantie déjà consommée par chaque NVMe, selon le disque lui-même.\n\n**Seuils** : orange à 80 %, rouge à 90 %.\n\n**Si c'est orange** : commander un remplaçant, rien ne presse encore. **Si c'est rouge** : planifier le remplacement ; pour un OSD Ceph, le sortir du cluster avant.",
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 8, "x": 0, "y": y.v},
"fieldConfig": {"defaults": {"unit": "percentunit", "min": 0, "max": 1, "decimals": 0,
@ -156,7 +180,7 @@
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "bargauge", "id": y.id, "title": "Secteurs défaillants",
"description": "En attente + irreparables + realloues, par disque SATA. Un compte stable se surveille ; un compte qui MONTE se remplace (controle « secteurs en progression »).",
"description": "Secteurs en attente, irréparables et réalloués, additionnés par disque SATA.\n\n**Un compte stable se surveille, un compte qui monte se remplace** : la tuile « secteurs en progression » passe au rouge dès qu'il augmente.\n\n**Si ce n'est pas 0** : prévoir le remplacement ; pour un OSD Ceph, le sortir du cluster (« out ») avant que la dégradation s'accélère.",
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 8, "x": 8, "y": y.v},
"fieldConfig": {"defaults": {"unit": "none", "min": 0, "decimals": 0,
@ -170,7 +194,7 @@
{%- set y.id = y.id + 1 -%}
{%- set _ = panneaux.append({
"type": "bargauge", "id": y.id, "title": "Heures de fonctionnement",
"description": "Age reel des disques. Un disque mecanique au-dela de 40 000 heures (4,5 ans) entre dans la zone ou les pannes se multiplient.",
"description": "Âge réel de chaque disque, en heures sous tension.\n\n**Seuil** : orange au-delà de 40 000 heures (4,5 ans), quand un disque mécanique entre dans la zone où les pannes se multiplient.\n\n**Si c'est orange** : en prévoir le remplacement dans le budget, surtout s'il porte aussi des secteurs défaillants.",
"datasource": {"type": "prometheus", "uid": "${ds_prom}"},
"gridPos": {"h": 9, "w": 8, "x": 16, "y": y.v},
"fieldConfig": {"defaults": {"unit": "h", "min": 0, "decimals": 0,
@ -185,7 +209,7 @@
{{ {
"uid": "setops-materiel",
"title": "Set-OPS — Matériel",
"description": "Hyperviseurs et frontiere : temperatures, ventilateurs, puissance, disques. Meme catalogue et memes seuils que le service Icinga « materiel » (scripts/materiel.py).",
"description": "Hyperviseurs et frontière : températures, ventilateurs, puissance, disques. Même catalogue et mêmes seuils que le service Icinga « materiel » (scripts/materiel.py).",
"tags": ["set-ops", "derive", "materiel", "fabric"],
"timezone": "browser",
"refresh": "1m",

View file

@ -59,9 +59,8 @@ TEMPERATURES = [
# Le verdict ne regarde que Tctl : les Tccd sont des sondes de coeur, plus bruitees.
"expr_juge": f'label_replace({_hwmon("k10temp", "Tctl")}, "capteur", "$1", "label", "(.*)")',
"avert": 90, "crit": 95, "jugee_sur": "5m",
"raison": ("Ryzen 9 7900X : il est CONCU pour monter a 95 °C sous charge soutenue et s'y "
"brider. Un Tctl a 95 pendant cinq minutes veut dire qu'il bride — la machine "
"rend moins que ce qu'on croit. Au-dessus, c'est le refroidissement."),
"raison": "Ryzen 9 7900X : il est conçu pour monter à 95 °C sous charge soutenue et s'y brider. Un Tctl à 95 pendant cinq minutes veut dire qu'il bride — la machine rend moins que ce qu'on croit. Au-dessus, c'est le refroidissement.",
"action": "regarder la puissance du processeur plus bas. Si elle monte aussi, c'est la charge : voir quelle VM consomme. Si elle reste basse, c'est le refroidissement : ventilateurs, poussière, pâte thermique.",
},
{
"cle": "carte_mere",
@ -71,9 +70,8 @@ TEMPERATURES = [
"juge": 'capteur="SYSTIN"',
"expr_juge": f'label_replace({_hwmon("nct6799", "SYSTIN")}, "capteur", "$1", "label", "(.*)")',
"avert": 50, "crit": 60, "jugee_sur": "10m",
"raison": ("SYSTIN est l'air du boitier. Il monte avec la piece, pas avec la charge : un "
"SYSTIN qui grimpe sur les trois machines a la fois parle de la salle, pas "
"d'une machine."),
"raison": "SYSTIN est l'air du boîtier. Il monte avec la pièce, pas avec la charge : un SYSTIN qui grimpe sur les trois machines à la fois parle de la salle, pas d'une machine.",
"action": "comparer les trois hyperviseurs. Tous ensemble : vérifier la climatisation ou l'aération de la salle. Un seul : son flux d'air (filtres, ventilateurs de façade).",
},
{
"cle": "nvme",
@ -82,8 +80,8 @@ TEMPERATURES = [
f'"nvme_(.*)")'),
"juge": "",
"avert": 70, "crit": 80, "jugee_sur": "5m",
"raison": ("WD_BLACK SN850X : il ralentit ses ecritures vers 80-85 °C pour se proteger. "
"Ceph et les VM le vivent comme une latence, sans aucune erreur."),
"raison": "WD_BLACK SN850X : il ralentit ses écritures vers 80-85 °C pour se protéger. Ceph et les VM le vivent comme une latence, sans aucune erreur.",
"action": "vérifier le flux d'air autour du disque (dissipateur, ventilateur du boîtier). Une chaleur durable abrège aussi sa vie : à surveiller avec l'usure plus bas.",
},
{
"cle": "disques",
@ -95,9 +93,8 @@ TEMPERATURES = [
f'"capteur", "$1", "disk", "/dev/(.*)")'),
"juge": "",
"avert": 50, "crit": 60, "jugee_sur": "15m",
"raison": ("WD Red Plus 10 To (OSD HDD de Ceph) et SSD Samsung 850 : la vie d'un disque "
"mecanique chute au-dessus de 50 °C, bien avant sa limite de 65. Celui de "
"gandalf a deja vu 63 °C (maximum a vie, releve SMART du 2026-09-17)."),
"raison": "WD Red Plus 10 To (OSD HDD de Ceph) et SSD Samsung 850 : la vie d'un disque mécanique chute au-dessus de 50 °C, bien avant sa limite de 65. Celui de gandalf a déjà vu 63 °C (maximum à vie, relevé SMART du 2026-09-17).",
"action": "améliorer la ventilation de la baie des disques. Un disque mécanique durablement au-dessus de 50 °C vieillit vite : croiser avec les secteurs défaillants, plus bas.",
},
{
"cle": "memoire",
@ -106,8 +103,8 @@ TEMPERATURES = [
f'"i2c_(.*)")'),
"juge": "",
"avert": 70, "crit": 80, "jugee_sur": "5m",
"raison": ("Capteur JEDEC des barrettes (noyau 6.14, vishnu seulement). La DDR5 "
"commence a corriger des erreurs au-dela de 85 °C."),
"raison": "Capteur JEDEC des barrettes (noyau 6.14, vishnu seulement). La DDR5 commence à corriger des erreurs au-delà de 85 °C.",
"action": "vérifier le flux d'air sur les barrettes. Au-delà de 85 °C, chercher des erreurs mémoire corrigées dans le journal du noyau (EDAC).",
},
{
"cle": "reseau",
@ -116,7 +113,8 @@ TEMPERATURES = [
f'"(.*)")'),
"juge": "",
"avert": 95, "crit": 110, "jugee_sur": "5m",
"raison": "Realtek RTL8125 (noyau 6.14, vishnu seulement) ; limite annoncee 120 °C.",
"raison": "Realtek RTL8125 (noyau 6.14, vishnu seulement) ; limite annoncée 120 °C.",
"action": "vérifier le flux d'air autour de la carte. Une carte réseau trop chaude perd des liens : regarder aussi les erreurs d'interface.",
},
{
"cle": "frontiere",
@ -125,8 +123,8 @@ TEMPERATURES = [
f'"cpu", "(.*)")'),
"juge": "",
"avert": 80, "crit": 90, "jugee_sur": "5m",
"raison": ("Intel i3-5020U, Tjunction 100 °C. A 90, il bride — et c'est tout le trafic du "
"site qui ralentit."),
"raison": "Intel i3-5020U, Tjunction 100 °C. À 90, il bride — et c'est tout le trafic du site qui ralentit.",
"action": "la frontière n'a qu'un petit refroidissement passif : dégager son emplacement, vérifier la poussière. Elle bride à 90 °C, et tout le trafic du site avec elle.",
},
]
@ -143,6 +141,7 @@ CONTROLES = [
f'(max_over_time(node_hwmon_fan_rpm{{{JOBS}}}[1d]) > 300), "capteur", "$1", '
f'"sensor", "(.*)")'),
"raison": "Un ventilateur qui tournait hier et plus maintenant : la chaleur suit dans l'heure.",
"action": "aller voir la machine sans attendre : ventilateur bloqué, débranché ou mort. Surveiller les températures de cette machine jusqu'à la réparation.",
},
{
"cle": "smart_echec",
@ -150,7 +149,8 @@ CONTROLES = [
"niveau": "crit",
"expr": (f'label_replace(smartmon_device_smart_healthy{{{JOBS},type=~"sat|nvme"}} == 0, '
f'"capteur", "$1", "disk", "/dev/(.*)")'),
"raison": "Le disque declare lui-meme qu'il va lacher. Il faut le remplacer, pas l'observer.",
"raison": "Le disque déclare lui-même qu'il va lâcher. Il faut le remplacer, pas l'observer.",
"action": "remplacer le disque. Pour un OSD Ceph : le sortir du cluster (« out ») et laisser Ceph recopier ses données avant de l'arrêter.",
},
{
"cle": "secteurs_degradation",
@ -164,7 +164,8 @@ CONTROLES = [
f'(delta(smartmon_reallocated_sector_ct_raw_value{{{JOBS}}}[1d]) > 0) or '
f'(delta(smartmon_offline_uncorrectable_raw_value{{{JOBS}}}[1d]) > 0), '
f'"capteur", "$1", "disk", "/dev/(.*)")'),
"raison": "Le disque perd des secteurs EN CE MOMENT : sa degradation s'accelere, le remplacer.",
"raison": "Le disque perd des secteurs en ce moment : sa dégradation s'accélère, le remplacer.",
"action": "commander et remplacer le disque rapidement. Pour un OSD Ceph : le sortir du cluster (« out ») dès maintenant, pour que les données soient recopiées ailleurs pendant qu'il lit encore.",
},
{
"cle": "secteurs_defaillants",
@ -174,8 +175,8 @@ CONTROLES = [
f'(smartmon_offline_uncorrectable_raw_value{{{JOBS}}} > 0) or '
f'(smartmon_reallocated_sector_ct_raw_value{{{JOBS}}} > 0), "capteur", "$1", '
f'"disk", "/dev/(.*)")'),
"raison": ("Le disque a des secteurs illisibles ou deja remplaces. Stable, il tient ; "
"a remplacer a la prochaine occasion."),
"raison": "Le disque a des secteurs illisibles ou déjà remplacés. Stable, il tient ; à remplacer à la prochaine occasion.",
"action": "noter le compte et le surveiller : tant qu'il ne bouge pas, pas d'urgence. Prévoir le remplacement ; s'il augmente, le contrôle « secteurs en progression » passe au rouge.",
},
{
"cle": "nvme_alerte",
@ -185,7 +186,8 @@ CONTROLES = [
f'(nvme_available_spare_ratio{{{JOBS}}} < on(instance, device) group_left() '
f'max by (instance, device) (nvme_available_spare_threshold_ratio{{{JOBS}}})), '
f'"capteur", "$1", "device", "(.*)")'),
"raison": "Le controleur NVMe signale un avertissement critique ou a epuise sa reserve.",
"raison": "Le contrôleur NVMe signale un avertissement critique ou a épuisé sa réserve.",
"action": "remplacer le NVMe. S'il porte un OSD Ceph, le sortir du cluster avant ; s'il porte le système, planifier l'intervention.",
},
{
"cle": "nvme_usure",
@ -193,7 +195,8 @@ CONTROLES = [
"niveau": "avert",
"expr": (f'label_replace(nvme_percentage_used_ratio{{{JOBS}}} > 0.8, "capteur", "$1", '
f'"device", "(.*)")'),
"raison": "L'endurance annoncee est presque consommee : a commander, pas encore a remplacer.",
"raison": "L'endurance annoncée est presque consommée : à commander, pas encore à remplacer.",
"action": "commander un remplaçant. Le disque fonctionne encore, mais il approche de la fin de son endurance garantie.",
},
{
"cle": "donnees_perimees",
@ -205,7 +208,8 @@ CONTROLES = [
"expr": (f'label_replace(time() - node_textfile_mtime_seconds{{{JOBS},'
f'file=~".*(smartmon|nvme)[.]prom"}} > 3600, "capteur", "$1", "file", '
f'".*/(.*)[.]prom")'),
"raison": "Les releves SMART/NVMe n'ont pas ete rafraichis depuis une heure : les verdicts ci-dessus sont figes.",
"raison": "Les relevés SMART/NVMe n'ont pas été rafraîchis depuis une heure : les verdicts ci-dessus sont figés.",
"action": "vérifier sur l'hyperviseur les minuteries prometheus-node-exporter-smartmon et -nvme (systemctl list-timers). Tant qu'elles sont arrêtées, les autres voyants de disque ne veulent rien dire.",
},
]