- tableau-synthese : faut-il agir, marge, activité, où regarder ensuite ; hyperviseurs au site seulement ; chaque panneau dit quoi faire - CHANGELOG 70 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
216 lines
18 KiB
Django/Jinja
216 lines
18 KiB
Django/Jinja
{#- GENERE par Set-OPS (role serveur_grafana). Ne pas editer a la main.
|
|
|
|
LE TABLEAU DE SYNTHESE (2026-10-04) — la porte d'entree de l'observatoire.
|
|
|
|
Quatre rangees, quatre questions : faut-il agir ? reste-t-il de la marge ? que se
|
|
passe-t-il ? ou regarder ensuite ? Chaque panneau dit ce qu'il mesure, ou sont ses
|
|
seuils, et QUOI FAIRE quand sa couleur change — meme patron que le tableau Materiel.
|
|
|
|
LE MEME GABARIT AU SITE ET CHEZ UN LOCATAIRE. Au site, `setops_materiel` existe : la
|
|
synthese ajoute les hyperviseurs (tuile materiel, memoire et swap) et regarde leur
|
|
activite. Chez un locataire, elle ne voit que ses propres machines.
|
|
|
|
LE NOM DES MACHINES vient de `node_uname_info` (joint sur `instance`) : les locataires
|
|
n'ont pas l'etiquette `hote` du site, et une adresse IP ne parle a personne.
|
|
-#}
|
|
{%- set fabric = setops_materiel is defined -%}
|
|
{#- La tuile materiel rend un NIVEAU (0, 1, 2), pas un compte : `count(x) > 0` garde la
|
|
valeur du compte — trois secteurs en avertissement s'affichaient « 3 ». -#}
|
|
{%- set cat = setops_materiel | default({"temperatures": [], "controles": []}) -%}
|
|
{%- set nom = ' * on(instance) group_left(nodename) node_uname_info' -%}
|
|
{%- set ds = {"type": "prometheus", "uid": "${ds_prom}"} -%}
|
|
{%- set panneaux = [] -%}
|
|
{%- set y = namespace(v=0, id=0) -%}
|
|
|
|
{#- ---- MODE D'EMPLOI ---------------------------------------------------------------- #}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "text", "id": y.id, "title": "",
|
|
"gridPos": {"h": 3, "w": 24, "x": 0, "y": y.v},
|
|
"options": {"mode": "markdown", "content":
|
|
"**Synthèse " ~ ("du site" if fabric else "de l'écosystème") ~ ".** Quatre rangées, quatre questions : **faut-il agir ?** — **reste-t-il de la marge ?** — **que se passe-t-il ?** — **où regarder ensuite ?** "
|
|
~ "Vert : rien à faire. Orange : à planifier. Rouge : à traiter. Survoler le titre d'un panneau (ⓘ) dit ce qu'il mesure et quoi faire s'il change de couleur."}}) -%}
|
|
{%- set y.v = y.v + 3 -%}
|
|
|
|
{#- ---- RANGEE 1 : FAUT-IL AGIR ? ----------------------------------------------------- #}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({"type": "row", "id": y.id, "title": "Faut-il agir ?", "collapsed": false,
|
|
"gridPos": {"h": 1, "w": 24, "x": 0, "y": y.v}}) -%}
|
|
{%- set y.v = y.v + 1 -%}
|
|
|
|
{%- set tuiles = [] -%}
|
|
{%- if fabric -%}
|
|
{%- set crit = [] -%}
|
|
{%- set avert = [] -%}
|
|
{%- for c in cat.controles -%}
|
|
{%- set _ = (crit if c.niveau == "crit" else avert).append(c.expr) -%}
|
|
{%- endfor -%}
|
|
{%- for f in cat.temperatures -%}
|
|
{%- if f.crit is not none -%}{%- set _ = crit.append("(" ~ f.expr_juge ~ ") > " ~ f.crit) -%}{%- endif -%}
|
|
{%- if f.avert is not none -%}{%- set _ = avert.append("(" ~ f.expr_juge ~ ") > " ~ f.avert) -%}{%- endif -%}
|
|
{%- endfor -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Matériel des hyperviseurs",
|
|
"expr": "(count((" ~ (crit | join(") or (")) ~ ")) > 0) * 0 + 2 or (count((" ~ (avert | join(") or (")) ~ ")) > 0) * 0 + 1 or vector(0)",
|
|
"unite": "none",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "orange", "value": 1}, {"color": "red", "value": 2}],
|
|
"mappings": [{"type": "value", "options": {"0": {"text": "Rien à faire"}, "1": {"text": "À planifier"}, "2": {"text": "À traiter"}}}],
|
|
"desc": "Le pire verdict du tableau Matériel : températures et contrôles des disques, ventilateurs et NVMe, avec les mêmes seuils que le service Icinga « materiel ».\n\n**Si c'est orange ou rouge** : ouvrir le tableau « Set-OPS — Matériel » ; la tuile colorée y dit quel équipement, et sa description dit quoi faire."}) -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Swap le plus plein (hyperviseurs)",
|
|
"expr": "max(100 * (1 - node_memory_SwapFree_bytes{job=\"fabric\"} / node_memory_SwapTotal_bytes{job=\"fabric\"}))",
|
|
"unite": "percent",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "orange", "value": 50}, {"color": "red", "value": 90}],
|
|
"desc": "Le swap le plus rempli des trois hyperviseurs. Un hyperviseur qui swappe ralentit toutes les VM qu'il porte, sans aucune erreur visible.\n\n**Seuils** : orange à 50 %, rouge à 90 %.\n\n**Si c'est orange ou rouge** : la mémoire allouée aux VM dépasse ce que la machine peut tenir. Réduire la RAM des VM qui en ont trop (voir « Mémoire utilisée par machine »), arrêter les VM inutiles, ou déplacer une VM vers l'hyperviseur le moins chargé."}) -%}
|
|
{%- endif -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Disque le plus plein (machines)",
|
|
"expr": "max(100 * (1 - node_filesystem_avail_bytes{job=\"node\",mountpoint=\"/\"} / node_filesystem_size_bytes{job=\"node\",mountpoint=\"/\"}))",
|
|
"unite": "percent",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "orange", "value": 80}, {"color": "red", "value": 90}],
|
|
"desc": "Le disque système le plus rempli parmi les machines de cet écosystème.\n\n**Seuils** : orange à 80 %, rouge à 90 % — les mêmes que la sonde Icinga « disque ».\n\n**Si c'est orange ou rouge** : trouver la machine dans « Disque utilisé par machine », plus bas ; chercher ce qui grossit (journaux, cache apt, sauvegardes locales) ou agrandir son disque."}) -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Correctifs de sécurité en attente",
|
|
"expr": "sum(apt_upgrades_pending{origin=~\"(?i).*securit.*\"}) or vector(0)",
|
|
"unite": "none",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "orange", "value": 1}],
|
|
"desc": "Nombre de paquets de sécurité Debian prêts à être installés, toutes machines confondues" ~ (" (hyperviseurs compris)" if fabric else "") ~ ".\n\n**Seuil** : orange dès 1.\n\n**Si c'est orange** : les machines du socle les appliquent d'elles-mêmes dans la nuit ; si le compte ne redescend pas d'ici le lendemain, la sonde Icinga « correctifs » dit lesquelles bloquent." ~ (" Les hyperviseurs, eux, se mettent à jour à la main." if fabric else "")}) -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Redémarrages en attente",
|
|
"expr": "count(node_reboot_required == 1) or vector(0)",
|
|
"unite": "none",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "orange", "value": 1}],
|
|
"desc": "Machines dont une mise à jour (noyau, bibliothèque système) ne prendra effet qu'au prochain redémarrage.\n\n**Seuil** : orange dès 1.\n\n**Si c'est orange** : planifier les redémarrages, une machine à la fois, en dehors des heures d'usage. Tant qu'elles ne redémarrent pas, le correctif est installé mais pas actif."}) -%}
|
|
{%- set _ = tuiles.append({
|
|
"titre": "Machines muettes",
|
|
"expr": "count(up == 0) or vector(0)",
|
|
"unite": "none",
|
|
"seuils": [{"color": "green", "value": none}, {"color": "red", "value": 1}],
|
|
"desc": "Cibles que Prometheus n'arrive plus à interroger : machine arrêtée, exportateur tombé, ou flux coupé.\n\n**Seuil** : rouge dès 1.\n\n**Si c'est rouge** : ce tableau ne voit plus cette machine, et tous ses chiffres sont figés. Vérifier qu'elle tourne, puis la sonde Icinga « collecte », qui la nomme."}) -%}
|
|
|
|
{%- set largeur = 24 // (tuiles | length) -%}
|
|
{%- for t in tuiles -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "stat", "id": y.id, "title": t.titre, "description": t.desc, "datasource": ds,
|
|
"gridPos": {"h": 5, "w": largeur, "x": loop.index0 * largeur, "y": y.v},
|
|
"fieldConfig": {"defaults": {"unit": t.unite, "decimals": 0, "noValue": "—",
|
|
"mappings": t.mappings | default([]),
|
|
"thresholds": {"mode": "absolute", "steps": t.seuils}}, "overrides": []},
|
|
"options": {"colorMode": "background", "graphMode": "none", "justifyMode": "center",
|
|
"textMode": "value", "orientation": "auto",
|
|
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}},
|
|
"targets": [{"refId": "A", "datasource": ds, "expr": t.expr}]}) -%}
|
|
{%- endfor -%}
|
|
{%- set y.v = y.v + 5 -%}
|
|
|
|
{#- ---- RANGEE 2 : RESTE-T-IL DE LA MARGE ? ----------------------------------------- #}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({"type": "row", "id": y.id, "title": "Reste-t-il de la marge ?", "collapsed": false,
|
|
"gridPos": {"h": 1, "w": 24, "x": 0, "y": y.v}}) -%}
|
|
{%- set y.v = y.v + 1 -%}
|
|
{%- set w = 8 if fabric else 12 -%}
|
|
{%- set x = namespace(v=0) -%}
|
|
{%- if fabric -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "timeseries", "id": y.id, "title": "Mémoire et swap des hyperviseurs", "datasource": ds,
|
|
"description": "Pour chaque hyperviseur : la part de sa RAM utilisée, et celle de son swap.\n\n**Comment le lire** : une RAM plate autour de 80 % avec un swap qui monte veut dire que les VM réclament plus que la machine n'a. Le swap n'est pas une marge, c'est un ralentissement.\n\n**Seuils** (pointillés) : 80 % et 90 %.\n\n**Si une courbe de swap reste haute** : réduire la RAM allouée aux VM de cet hyperviseur, ou en déplacer une.",
|
|
"gridPos": {"h": 9, "w": w, "x": x.v, "y": y.v},
|
|
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100, "decimals": 0, "color": {"mode": "palette-classic"},
|
|
"thresholds": {"mode": "absolute", "steps": [{"color": "green", "value": none}, {"color": "orange", "value": 80}, {"color": "red", "value": 90}]},
|
|
"custom": {"drawStyle": "line", "lineWidth": 2, "fillOpacity": 5, "showPoints": "never", "spanNulls": true, "thresholdsStyle": {"mode": "dashed"}}},
|
|
"overrides": []},
|
|
"options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"]}, "tooltip": {"mode": "multi", "sort": "desc"}},
|
|
"targets": [
|
|
{"refId": "A", "datasource": ds, "legendFormat": "{{nodename}} · RAM",
|
|
"expr": "100 * (1 - node_memory_MemAvailable_bytes{job=\"fabric\"} / node_memory_MemTotal_bytes{job=\"fabric\"})" ~ nom},
|
|
{"refId": "B", "datasource": ds, "legendFormat": "{{nodename}} · swap",
|
|
"expr": "100 * (1 - node_memory_SwapFree_bytes{job=\"fabric\"} / node_memory_SwapTotal_bytes{job=\"fabric\"})" ~ nom}]}) -%}
|
|
{%- set x.v = x.v + w -%}
|
|
{%- endif -%}
|
|
{%- for p in [
|
|
{"titre": "Disque utilisé par machine", "seuils": [80, 90],
|
|
"expr": "100 * (1 - node_filesystem_avail_bytes{job=\"node\",mountpoint=\"/\"} / node_filesystem_size_bytes{job=\"node\",mountpoint=\"/\"})",
|
|
"desc": "Part utilisée du disque système de chaque machine, de la plus pleine à la moins pleine.\n\n**Seuils** : orange à 80 %, rouge à 90 %.\n\n**Si une barre passe à l'orange** : se connecter à la machine et chercher ce qui grossit (`du -xh / | sort -h | tail`) — le plus souvent des journaux ou un cache ; sinon, agrandir son disque."},
|
|
{"titre": "Mémoire utilisée par machine", "seuils": [85, 95],
|
|
"expr": "100 * (1 - node_memory_MemAvailable_bytes{job=\"node\"} / node_memory_MemTotal_bytes{job=\"node\"})",
|
|
"desc": "Part de la RAM que chaque machine utilise vraiment (cache disque exclu).\n\n**Comment le lire** : une barre très courte sur une machine qui a beaucoup de RAM, c'est de la mémoire qu'on pourrait rendre à l'hyperviseur" ~ (" — et c'est exactement ce qui manque quand le swap des hyperviseurs monte." if fabric else ".") ~ "\n\n**Seuils** : orange à 85 %, rouge à 95 %.\n\n**Si une barre passe au rouge** : la machine manque de mémoire ; lui en donner un peu plus, ou alléger ce qu'elle fait tourner."}
|
|
] -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "bargauge", "id": y.id, "title": p.titre, "description": p.desc, "datasource": ds,
|
|
"gridPos": {"h": 9, "w": w, "x": x.v, "y": y.v},
|
|
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "max": 100, "decimals": 0,
|
|
"thresholds": {"mode": "absolute", "steps": [{"color": "green", "value": none}, {"color": "orange", "value": p.seuils[0]}, {"color": "red", "value": p.seuils[1]}]}},
|
|
"overrides": []},
|
|
"options": {"displayMode": "gradient", "orientation": "horizontal", "showUnfilled": true, "valueMode": "color",
|
|
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}},
|
|
"targets": [{"refId": "A", "datasource": ds, "legendFormat": "{{nodename}}",
|
|
"expr": "sort_desc(" ~ p.expr ~ nom ~ ")"}]}) -%}
|
|
{%- set x.v = x.v + w -%}
|
|
{%- endfor -%}
|
|
{%- set y.v = y.v + 9 -%}
|
|
|
|
{#- ---- RANGEE 3 : QUE SE PASSE-T-IL ? ---------------------------------------------- #}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({"type": "row", "id": y.id, "title": "Que se passe-t-il ?", "collapsed": false,
|
|
"gridPos": {"h": 1, "w": 24, "x": 0, "y": y.v}}) -%}
|
|
{%- set y.v = y.v + 1 -%}
|
|
{%- set job = 'job="fabric"' if fabric else 'job="node"' -%}
|
|
{%- set qui = "hyperviseur" if fabric else "machine" -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "timeseries", "id": y.id, "title": "Processeur par " ~ qui, "datasource": ds,
|
|
"description": "Part du processeur occupée, par " ~ qui ~ ".\n\n**Comment le lire** : une courbe basse et plate est normale" ~ (" — 72 cœurs pour la flotte, occupés à quelques pour cent" if fabric else "") ~ ". Les pics racontent ce qui se passe : une reconstruction, une sauvegarde, un déploiement. Une courbe qui reste haute sans raison mérite qu'on cherche quelle machine consomme.\n\n**Si une courbe reste au-dessus de 80 %** : ouvrir le tableau « client metrique » pour voir quelle machine en est la cause.",
|
|
"gridPos": {"h": 8, "w": 12, "x": 0, "y": y.v},
|
|
"fieldConfig": {"defaults": {"unit": "percent", "min": 0, "decimals": 0, "color": {"mode": "palette-classic"},
|
|
"custom": {"drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never", "spanNulls": true}}, "overrides": []},
|
|
"options": {"legend": {"displayMode": "table", "placement": "right", "calcs": ["mean", "max"]}, "tooltip": {"mode": "multi", "sort": "desc"}},
|
|
"targets": [{"refId": "A", "datasource": ds, "legendFormat": "{{nodename}}",
|
|
"expr": "100 * (1 - avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\"," ~ job ~ "}[5m])))" ~ nom}]}) -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "timeseries", "id": y.id, "title": "Trafic réseau par " ~ qui, "datasource": ds,
|
|
"description": "Débit entrant et sortant additionnés, par " ~ qui ~ " (interfaces physiques seulement).\n\n**Comment le lire** : " ~ ("le trafic courant est surtout celui de Ceph et des sauvegardes ; les grands pics marquent une reconstruction ou un déplacement de VM." if fabric else "les pics marquent les sauvegardes de la nuit, les mises à jour et les échanges entre services.") ~ "\n\n**Si un pic dure sans explication** : croiser avec le tableau « Journaux de la flotte » à la même heure.",
|
|
"gridPos": {"h": 8, "w": 12, "x": 12, "y": y.v},
|
|
"fieldConfig": {"defaults": {"unit": "Bps", "min": 0, "decimals": 1, "color": {"mode": "palette-classic"},
|
|
"custom": {"drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never", "spanNulls": true}}, "overrides": []},
|
|
"options": {"legend": {"displayMode": "table", "placement": "right", "calcs": ["mean", "max"]}, "tooltip": {"mode": "multi", "sort": "desc"}},
|
|
"targets": [{"refId": "A", "datasource": ds, "legendFormat": "{{nodename}}",
|
|
"expr": "sum by (instance) (rate(node_network_receive_bytes_total{" ~ job ~ ",device=~\"(en|eth).*\"}[5m]) + rate(node_network_transmit_bytes_total{" ~ job ~ ",device=~\"(en|eth).*\"}[5m]))" ~ nom}]}) -%}
|
|
{%- set y.v = y.v + 8 -%}
|
|
|
|
{#- ---- RANGEE 4 : OU REGARDER ENSUITE ? -------------------------------------------- #}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({"type": "row", "id": y.id, "title": "Où regarder ensuite ?", "collapsed": false,
|
|
"gridPos": {"h": 1, "w": 24, "x": 0, "y": y.v}}) -%}
|
|
{%- set y.v = y.v + 1 -%}
|
|
{%- set liens = [] -%}
|
|
{%- if fabric -%}{%- set _ = liens.append("- [**Matériel**](/d/setops-materiel) — températures, ventilateurs, disques des hyperviseurs et de la frontière.") -%}{%- endif -%}
|
|
{%- set _ = liens.append("- [**Machines**](/d/setops-client-metrique) — processeur, mémoire et disque de chaque machine, en détail.") -%}
|
|
{%- set _ = liens.append("- [**PostgreSQL**](/d/setops-postgresql) — bases de données : taille, connexions, requêtes.") -%}
|
|
{%- set _ = liens.append("- [**Journaux de la flotte**](/d/setops-journaux) — ce que les machines ont écrit, et quand.") -%}
|
|
{%- set _ = liens.append("- [**Vigie**](https://vigie." ~ domaine_interne ~ ") — les verdicts d'Icinga : ce qui est cassé, service par service.") -%}
|
|
{%- set y.id = y.id + 1 -%}
|
|
{%- set _ = panneaux.append({
|
|
"type": "text", "id": y.id, "title": "",
|
|
"gridPos": {"h": 5, "w": 24, "x": 0, "y": y.v},
|
|
"options": {"mode": "markdown", "content": liens | join("\n")}}) -%}
|
|
|
|
{{ {
|
|
"uid": "setops-synthese",
|
|
"title": "Set-OPS — Synthèse",
|
|
"description": "La porte d'entrée de l'observatoire : faut-il agir, reste-t-il de la marge, que se passe-t-il, où regarder ensuite.",
|
|
"tags": ["set-ops", "derive", "synthese"],
|
|
"timezone": "browser",
|
|
"refresh": "1m",
|
|
"time": {"from": "now-7d", "to": "now"},
|
|
"schemaVersion": 39,
|
|
"version": 1,
|
|
"graphTooltip": 1,
|
|
"templating": {"list": [
|
|
{"name": "ds_prom", "label": "Source Prometheus", "type": "datasource", "query": "prometheus", "hide": 2, "current": {}}
|
|
]},
|
|
"panels": panneaux
|
|
} | tojson }}
|