From 3a013834aa585a07e081d0c08f8445c3060dabfa Mon Sep 17 00:00:00 2001 From: alkatrazz Date: Thu, 14 May 2026 21:58:39 +0200 Subject: [PATCH] =?UTF-8?q?fix(webhook):=20r=C3=A9ponse=20HTTP=20200=20imm?= =?UTF-8?q?=C3=A9diate=20+=20ask-agent=20asynchrone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BrokenPipeError corrigé : send_response(200) envoyé avant subprocess. ask-agent lancé en threading.Thread daemon — AlertManager n'attend plus. NO_COLOR=1 + TERM=dumb pour éviter les codes ANSI dans les logs. docs: alertmanager-webhook.md — architecture, flux, test, dépannage Co-Authored-By: Claude Sonnet 4.6 --- admin/ia/alertmanager-webhook.md | 362 ++++++++++++++++++ admin/k8s/monitoring.md | 6 +- .../alertmanager_webhook/files/webhook.py | 29 +- 3 files changed, 384 insertions(+), 13 deletions(-) create mode 100644 admin/ia/alertmanager-webhook.md diff --git a/admin/ia/alertmanager-webhook.md b/admin/ia/alertmanager-webhook.md new file mode 100644 index 0000000..06ad409 --- /dev/null +++ b/admin/ia/alertmanager-webhook.md @@ -0,0 +1,362 @@ +# AlertManager Webhook — Pont Prometheus → Hermes + +Service Python sur **storage-01** qui reçoit les alertes Prometheus via AlertManager +et les transmet au profil `monitor` de Hermes pour analyse IA. + +--- + +## Architecture et flux + +``` +Prometheus (k8s, namespace monitoring) + │ Seuil dépassé (CPU, RAM, disk, pod crash...) + ▼ +AlertManager (k8s, namespace monitoring) + │ POST http://192.168.10.1:9093/webhook + │ Content-Type: application/json + ▼ +alertmanager-webhook.service (storage-01, port 9093) + │ Répond 200 immédiatement + │ Lance ask-agent en thread daemon + ▼ +ask-agent monitor "Alertes Prometheus reçues à HH:MM:SS : ..." + │ POST http://127.0.0.1:4000/v1/chat/completions + ▼ +LiteLLM Proxy (:4000) + │ modèle : qwen3-1.7b-monitor + ▼ +llama-server-monitor (gpu-01:1237, CPU 14 threads) + │ Répond dans les logs du service + ▼ +journalctl -u alertmanager-webhook + "ask-agent response: SERVICE SAIN — aucune action requise" +``` + +**Point clé** : le webhook répond HTTP 200 immédiatement, avant d'appeler `ask-agent`. +`ask-agent` tourne en arrière-plan (~20-60s) — AlertManager ne reste pas bloqué en attente. + +--- + +## Fonctionnement du webhook.py + +### 1. Réception de l'alerte + +AlertManager envoie un payload JSON de cette forme : + +```json +{ + "alerts": [ + { + "status": "firing", + "labels": { + "alertname": "NodeHighCPU", + "severity": "warning", + "instance": "192.168.10.1:9100" + }, + "annotations": { + "summary": "CPU élevé sur storage-01", + "description": "CPU > 90% depuis 5 minutes (valeur : 93.2%)" + } + } + ] +} +``` + +### 2. Formatage du message + +Chaque alerte est formatée en texte lisible : + +``` +[FIRING] NodeHighCPU — severity=warning instance=192.168.10.1:9100 +CPU élevé sur storage-01 +CPU > 90% depuis 5 minutes (valeur : 93.2%) +``` + +Le message final envoyé à Hermes : + +``` +Alertes Prometheus reçues à 21:35:03 : +[FIRING] NodeHighCPU — severity=warning instance=192.168.10.1:9100 +... + +Analyse ces alertes : sont-elles critiques ? Quelles actions recommandes-tu ? +``` + +### 3. Exécution asynchrone + +```python +# HTTP 200 envoyé immédiatement → AlertManager libéré +self.send_response(200) +self.end_headers() +self.wfile.write(b"ok") + +# ask-agent lancé en thread daemon +threading.Thread(target=run_agent, daemon=True).start() +``` + +`ask-agent` est lancé avec `NO_COLOR=1` et `TERM=dumb` pour éviter les codes +ANSI dans les logs. + +### 4. Réponse Hermes dans les logs + +La réponse du modèle monitor apparaît dans `journalctl` ~20-60s après l'alerte : + +``` +INFO ask-agent response: SERVICE SAIN — CPU revenue à 45% après pic temporaire. +Aucune action requise. Surveiller sur 30 min. +``` + +--- + +## Service systemd + +```bash +# État +sudo systemctl status alertmanager-webhook + +# Logs en direct +sudo journalctl -u alertmanager-webhook -f + +# Logs récents +sudo journalctl -u alertmanager-webhook -n 50 --no-pager + +# Redémarrer +sudo systemctl restart alertmanager-webhook +``` + +Le service tourne sous l'utilisateur `alertmanager-webhook` avec des permissions minimales. + +--- + +## Tester le webhook + +### Test curl manuel + +```bash +# Depuis le poste perso ou storage-01 +curl -s -X POST http://192.168.10.1:9093/webhook \ + -H "Content-Type: application/json" \ + -d '{ + "alerts": [{ + "status": "firing", + "labels": { + "alertname": "TestAlert", + "severity": "warning", + "instance": "storage-01:9100" + }, + "annotations": { + "summary": "Test alerte manuelle", + "description": "Vérification du pipeline webhook → Hermes" + } + }] + }' +# Réponse attendue : ok + +# Suivre la réponse Hermes +ssh storage-01 "sudo journalctl -u alertmanager-webhook -f" +# Attendre ~30-60s → la réponse ask-agent apparaît +``` + +### Vérifier la connexion AlertManager → webhook + +```bash +# Alertes actives dans AlertManager +curl -s http://alertmanager.lab.local/api/v2/alerts | python3 -m json.tool + +# Récepteurs configurés dans AlertManager +curl -s http://alertmanager.lab.local/api/v2/receivers | python3 -m json.tool + +# Statut du webhook dans AlertManager (UI) +# http://alertmanager.lab.local/#/status → section "receivers" +``` + +### Déclencher une vraie alerte + +```bash +# Forcer une alerte Watchdog à passer par hermes-monitor (test de bout en bout) +# En modifiant temporairement la route dans values.yaml (à faire sur branche test) + +# Ou créer un fichier flag d'alerte manuellement dans Prometheus +# (déconseillé en prod) +``` + +--- + +## Configuration AlertManager + +Le webhook est enregistré dans `k8s/infra/monitoring/values.yaml` comme receiver AlertManager : + +```yaml +alertmanager: + config: + route: + receiver: 'null' # receiver par défaut (Watchdog) + routes: + - matchers: + - alertname = "Watchdog" + receiver: 'null' + - receiver: 'hermes-monitor' # toutes les autres alertes → Hermes + + receivers: + - name: 'null' + - name: 'hermes-monitor' + webhook_configs: + - url: 'http://192.168.10.1:9093/webhook' + send_resolved: true # envoie aussi les alertes résolues +``` + +`send_resolved: true` — quand une alerte passe de `firing` à `resolved`, AlertManager +envoie un nouveau payload avec `"status": "resolved"` — Hermes est informé de la résolution. + +--- + +## Déploiement et mise à jour + +### Fichiers + +| Fichier | Emplacement | Rôle | +|---|---|---| +| `webhook.py` | `ansible/roles/alertmanager_webhook/files/webhook.py` | Code source (versionné) | +| `webhook.py` (déployé) | `/opt/alertmanager-webhook/webhook.py` (storage-01) | Instance active | +| Rôle Ansible | `ansible/roles/alertmanager_webhook/` | Déploiement, service systemd | + +### Redéployer après modification + +```bash +# Depuis le poste perso +scp ansible/roles/alertmanager_webhook/files/webhook.py \ + storage-01:/tmp/webhook.py + +ssh storage-01 "sudo cp /tmp/webhook.py /opt/alertmanager-webhook/webhook.py \ + && sudo systemctl restart alertmanager-webhook \ + && sudo systemctl status alertmanager-webhook --no-pager" +``` + +Ou via Ansible (une fois le rôle `hermes_user` en place) : + +```bash +cd /mnt/nvme0n1/nvme0n1p3/Projets/lab +.venv/bin/ansible-playbook -i ansible/inventory.yml ansible/playbooks/site.yml \ + --tags alertmanager_webhook +``` + +--- + +## Modifier le message envoyé à Hermes + +Le prompt est construit dans `webhook.py`, fonction `do_POST` : + +```python +message = "\n".join(lines) + "\n\nAnalyse ces alertes : sont-elles critiques ? Quelles actions recommandes-tu ?" +``` + +Pour personnaliser l'instruction donnée à Hermes, modifier cette ligne et redéployer. + +--- + +## Ajouter un filtre d'alertes + +Pour que certaines alertes n'arrivent pas à Hermes (trop bruyantes), +ajouter une route spécifique avant `hermes-monitor` dans `values.yaml` : + +```yaml +routes: + - matchers: + - alertname = "Watchdog" + receiver: 'null' + - matchers: + - severity = "info" # silencer les alertes info + receiver: 'null' + - receiver: 'hermes-monitor' # tout le reste → Hermes +``` + +`git push` → ArgoCD applique en ~3 minutes. + +--- + +## Dépannage + +### BrokenPipeError dans les logs + +**Symptôme** : `BrokenPipeError: [Errno 32] Broken pipe` dans `journalctl`. + +**Cause** : ancienne version du webhook qui envoyait le HTTP 200 *après* `ask-agent` +(bloquant ~54s → AlertManager fermait la connexion avant). + +**Solution** : vérifier que le webhook.py déployé correspond bien à la version actuelle +(200 envoyé immédiatement, `ask-agent` en thread). + +```bash +ssh storage-01 "grep -n 'send_response\|threading' /opt/alertmanager-webhook/webhook.py" +# Doit afficher send_response AVANT threading.Thread +``` + +### Codes ANSI dans les logs (output illisible) + +**Symptôme** : `ask-agent response: 790G'*%G+>,<...` (caractères parasites). + +**Cause** : `ask-agent` utilise des couleurs terminal capturées brutes par subprocess. + +**Solution** : vérifier que `NO_COLOR=1` est passé à subprocess : + +```bash +ssh storage-01 "grep 'NO_COLOR' /opt/alertmanager-webhook/webhook.py" +# Doit afficher : "NO_COLOR": "1" +``` + +### Pas de réponse Hermes dans les logs (timeout) + +`ask-agent monitor` a expiré après 120s. Causes possibles : + +```bash +# LiteLLM répond ? +ssh storage-01 "curl -s http://localhost:4000/health \ + -H 'Authorization: Bearer lm-studio'" + +# llama-server-monitor tourne ? +ssh gpu-01 "systemctl status llama-server-monitor --no-pager" + +# Logs ask-agent (stderr capturé) +ssh storage-01 "sudo journalctl -u alertmanager-webhook -n 30 --no-pager" \ + | grep -E "stderr|error|timeout" +``` + +### Le webhook ne reçoit pas les alertes AlertManager + +```bash +# 1. Le service écoute sur le bon port ? +ssh storage-01 "ss -tlnp | grep 9093" + +# 2. nftables autorise le pod CIDR ? +ssh storage-01 "sudo nft list ruleset | grep 9093" +# Doit contenir : 10.42.0.0/16 + +# 3. AlertManager atteint storage-01 ? +kubectl logs -n monitoring \ + alertmanager-kube-prometheus-stack-alertmanager-0 \ + | grep -i webhook | tail -20 +``` + +### Hermes répond mais l'analyse est mauvaise + +Le profil `monitor` utilise Qwen3-1.7B (petit modèle CPU). Pour des analyses +plus poussées, modifier `ask-agent` pour utiliser `brain` (Claude Sonnet) sur les +alertes critiques — ou ajuster le SOUL.md du profil monitor. + +```bash +# SOUL.md du profil monitor +ssh storage-01 "cat /srv/data/hermes/profiles/monitor/SOUL.md" +``` + +--- + +## Points d'attention + +| Sujet | Détail | +|---|---| +| Réponse asynchrone | Hermes répond dans les logs 20-60s après l'alerte — pas de retour vers AlertManager | +| Alertes résolues | `send_resolved: true` — Hermes reçoit aussi les résolutions, utile pour confirmer le retour à la normale | +| Port 9093 | nftables doit autoriser `10.42.0.0/16` (pod CIDR) ET `192.168.10.0/24` sur ce port | +| Modèle monitor | Qwen3-1.7B CPU (~21 tok/s) — rapide mais limité. Convient pour des verdicts courts (OK/WARNING/ERROR) | +| Pas de stockage | Les analyses Hermes ne sont pas persistées — elles apparaissent uniquement dans `journalctl` | +| Watchdog | L'alerte Watchdog (heartbeat interne Prometheus) est routée vers `null` — ne pas la router vers Hermes (spam toutes les minutes) | diff --git a/admin/k8s/monitoring.md b/admin/k8s/monitoring.md index b13f418..ebb6f19 100644 --- a/admin/k8s/monitoring.md +++ b/admin/k8s/monitoring.md @@ -233,8 +233,10 @@ curl -s http://192.168.10.20:1237/metrics | grep llamacpp ## AlertManager → Hermes monitor AlertManager envoie les alertes en POST vers `http://192.168.10.1:9093/webhook`. -Le service `alertmanager-webhook` (Python, storage-01) reçoit les alertes et appelle `ask-agent monitor`. -Hermes (profil `monitor`) analyse et répond dans ses logs. +Le service `alertmanager-webhook` (Python, storage-01) reçoit les alertes, répond 200 immédiatement, +et lance `ask-agent monitor` en arrière-plan. Hermes (profil `monitor`) analyse et répond dans ses logs. + +**Documentation complète** : [admin/ia/alertmanager-webhook.md](../ia/alertmanager-webhook.md) ```bash # Vérifier le webhook diff --git a/ansible/roles/alertmanager_webhook/files/webhook.py b/ansible/roles/alertmanager_webhook/files/webhook.py index 9ea010d..9312d4e 100644 --- a/ansible/roles/alertmanager_webhook/files/webhook.py +++ b/ansible/roles/alertmanager_webhook/files/webhook.py @@ -7,6 +7,7 @@ import json import os import subprocess import logging +import threading from http.server import HTTPServer, BaseHTTPRequestHandler from datetime import datetime @@ -63,21 +64,27 @@ class WebhookHandler(BaseHTTPRequestHandler): log.info(f"Forwarding {len(firing)} firing + {len(resolved)} resolved alerts to ask-agent monitor") - try: - result = subprocess.run( - ["/usr/local/bin/ask-agent", "monitor", message], - capture_output=True, text=True, timeout=120 - ) - log.info(f"ask-agent response: {result.stdout[:200]}") - except subprocess.TimeoutExpired: - log.error("ask-agent timed out") - except Exception as e: - log.error(f"ask-agent error: {e}") - self.send_response(200) self.end_headers() self.wfile.write(b"ok") + def run_agent(): + try: + env = {**os.environ, "NO_COLOR": "1", "TERM": "dumb"} + result = subprocess.run( + ["/usr/local/bin/ask-agent", "monitor", message], + capture_output=True, text=True, timeout=120, env=env + ) + log.info(f"ask-agent response: {result.stdout[:200]}") + if result.returncode != 0: + log.error(f"ask-agent stderr: {result.stderr[:200]}") + except subprocess.TimeoutExpired: + log.error("ask-agent timed out") + except Exception as e: + log.error(f"ask-agent error: {e}") + + threading.Thread(target=run_agent, daemon=True).start() + def log_message(self, format, *args): pass # silence HTTP access logs