diff --git a/CHANGELOG.md b/CHANGELOG.md index a74eef1..a08a105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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, diff --git a/roles/serveur_grafana/templates/tableau-materiel.json.j2 b/roles/serveur_grafana/templates/tableau-materiel.json.j2 index 6a53de9..f6e7925 100644 --- a/roles/serveur_grafana/templates/tableau-materiel.json.j2 +++ b/roles/serveur_grafana/templates/tableau-materiel.json.j2 @@ -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", diff --git a/scripts/materiel.py b/scripts/materiel.py index 0ef5d59..b8ff272 100644 --- a/scripts/materiel.py +++ b/scripts/materiel.py @@ -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.", }, ]