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:
alkatrazz 2026-05-14 21:58:39 +02:00
parent c97e895b87
commit 3a013834aa
3 changed files with 384 additions and 13 deletions

View 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) |