mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 17:44:42 +02:00
fix(webhook): réponse HTTP 200 immédiate + ask-agent asynchrone
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 <noreply@anthropic.com>
This commit is contained in:
parent
c97e895b87
commit
3a013834aa
3 changed files with 384 additions and 13 deletions
362
admin/ia/alertmanager-webhook.md
Normal file
362
admin/ia/alertmanager-webhook.md
Normal file
|
|
@ -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) |
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue