sonde tableaux : la sante de chaque source de donnees de Grafana

Elle etait verte pendant que les tableaux etaient vides. Eprouvee avec une
source recreant le defaut du jour, puis retiree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-28 17:51:53 -04:00
parent 8f699eb3ce
commit b938897e17
3 changed files with 65 additions and 4 deletions

View file

@ -1,5 +1,19 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-09-28 (24) — La sonde « tableaux » vérifie aussi les sources de données
Elle était verte pendant que les tableaux des locataires n'affichaient rien : elle
demandait si Grafana répond et si sa base tient — pas s'il peut montrer quelque chose.
Elle interroge désormais la santé de CHAQUE source de données (`/api/datasources/uid/…/health`)
et passe CRITIQUE en nommant celle qui est en défaut. Le mot de passe d'administration est
lu là où il est, dans le drop-in systemd de Grafana (0600 root), pas recopié ; illisible, la
sonde le dit (avertissement « sources non vérifiées ») au lieu de se taire.
Éprouvée sur `obs-01` de Technolibre : saine ; puis une source Loki temporaire recréant le
défaut du jour (`http://localhost:3100` face à un Loki en HTTPS) → CRITIQUE, source nommée ;
source retirée → saine. Déployée sur les trois Grafana : `tableaux` au vert, « sources
saines (Loki, Prometheus) ».
## 2026-09-28 (23) — Grafana des locataires : aucune donnée, parce que l'URL de Loki ne suivait pas son TLS ## 2026-09-28 (23) — Grafana des locataires : aucune donnée, parce que l'URL de Loki ne suivait pas son TLS
**Signalé par l'exploitant** : une erreur de plugin et aucun panneau rempli sur les Grafana **Signalé par l'exploitant** : une erreur de plugin et aucun panneau rempli sur les Grafana

View file

@ -3,5 +3,6 @@
sondes: sondes:
- nom: tableaux - nom: tableaux
ttl: 5400 ttl: 5400
raison: 'Grafana repond-il, et sa base tient-elle ? Un Grafana dont la base est tombee sert encore raison: 'Grafana repond-il, sa base tient-elle, et ses SOURCES DE DONNEES aussi ? Un Grafana
sa page de connexion : il a l''air en ligne et ne peut plus rien montrer.' dont la base est tombee sert encore sa page de connexion ; un Grafana dont la source Loki
parle en clair a un Loki en HTTPS repond aussi — et n''affiche rien (2026-09-28).'

View file

@ -1,17 +1,63 @@
#!/bin/bash #!/bin/bash
# GENERE par Set-OPS (role serveur_grafana). Ne pas editer a la main. # GENERE par Set-OPS (role serveur_grafana). Ne pas editer a la main.
# #
# SONDE « tableaux » — Grafana repond et sa base tient # SONDE « tableaux » — Grafana repond, sa base tient, et ses SOURCES DE DONNEES aussi.
# #
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. # Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
# Mise en defaut PAR PARAMETRE : `serveur_grafana_sonde_url` (port ferme). # Mise en defaut PAR PARAMETRE : `serveur_grafana_sonde_url` (port ferme).
#
# LES SOURCES, DEPUIS LE 2026-09-28. Cette sonde etait verte pendant que les tableaux des
# locataires n'affichaient RIEN : Grafana repondait, sa base tenait — mais sa source Loki
# parlait en clair a un Loki qui servait en HTTPS. Les tableaux tirent leur liste d'hotes de
# Loki : vide, aucun panneau ne se remplissait. « Grafana repond » n'etait pas la question ;
# « Grafana peut-il montrer quelque chose » l'est.
#
# LE MOT DE PASSE EST LU LA OU IL EST, pas recopie : dans le drop-in systemd de Grafana,
# en 0600 root. La sonde tourne en root ; rien d'autre ne le voit, et il n'est pas affiche.
set -uo pipefail set -uo pipefail
URL={{ serveur_grafana_sonde_url }} URL={{ serveur_grafana_sonde_url }}
DROPIN=/etc/systemd/system/grafana-server.service.d/setops.conf
s=$(curl -s --max-time 10 "${URL}/api/health" 2>/dev/null) s=$(curl -s --max-time 10 "${URL}/api/health" 2>/dev/null)
[[ -n "${s}" ]] || { echo "Grafana ne repond pas sur ${URL}."; exit 2; } [[ -n "${s}" ]] || { echo "Grafana ne repond pas sur ${URL}."; exit 2; }
base=$(printf '%s' "${s}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("database","?"))' 2>/dev/null) base=$(printf '%s' "${s}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("database","?"))' 2>/dev/null)
ver=$(printf '%s' "${s}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("version","?"))' 2>/dev/null) ver=$(printf '%s' "${s}" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("version","?"))' 2>/dev/null)
[[ "${base}" == "ok" ]] || { echo "Grafana repond mais sa base est en defaut (${base:-illisible})."; exit 2; } [[ "${base}" == "ok" ]] || { echo "Grafana repond mais sa base est en defaut (${base:-illisible})."; exit 2; }
echo "Grafana ${ver} : base ok."
MDP=$(grep -hoP 'GF_SECURITY_ADMIN_PASSWORD=\K\S+' "${DROPIN}" 2>/dev/null | head -1)
if [[ -z "${MDP}" ]]; then
echo "Grafana ${ver} : base ok, mais sources NON verifiees (mot de passe illisible dans ${DROPIN})."
exit 1
fi
GRAFANA_MDP="${MDP}" python3 - "${URL}" "${ver}" <<'EOF'
import base64, json, os, sys, urllib.request
url, ver = sys.argv[1], sys.argv[2]
auth = "Basic " + base64.b64encode(f"admin:{os.environ['GRAFANA_MDP']}".encode()).decode()
def lire(chemin):
req = urllib.request.Request(url + chemin, headers={"Authorization": auth})
try:
with urllib.request.urlopen(req, timeout=8) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
try:
return json.loads(e.read())
except ValueError:
return {"status": "ERROR", "message": f"HTTP {e.code}"}
except OSError as e:
return {"status": "ERROR", "message": str(e)[:80]}
sources = lire("/api/datasources")
if not isinstance(sources, list):
print(f"Grafana {ver} : base ok, mais la liste des sources est illisible ({str(sources)[:80]}).")
sys.exit(1)
ko, ok = [], []
for d in sources:
h = lire(f"/api/datasources/uid/{d['uid']}/health")
(ok if h.get("status") == "OK" else ko).append(f"{d['name']} ({str(h.get('message'))[:70]})" if h.get("status") != "OK" else d["name"])
perf = f"| sources_ok={len(ok)} sources_ko={len(ko)}"
if ko:
print(f"Grafana {ver} repond, mais source(s) EN DEFAUT : {'; '.join(ko)} — les tableaux restent vides. {perf}")
sys.exit(2)
print(f"Grafana {ver} : base ok, sources saines ({', '.join(ok) or 'aucune'}). {perf}")
EOF