# 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  ⚠️ backend retiré le 2026-06-20
  ▼
(ex-llama-server-monitor gpu-01:1237 — SUPPRIMÉ)
  │  Répond dans les logs du service
  ▼
journalctl -u alertmanager-webhook
  "ask-agent response: SERVICE SAIN — aucune action requise"
```

> ⚠️ **2026-06-20** : le serveur CPU `monitor` (:1237) a été supprimé. Le résumé d'alerte par
> `ask-agent monitor` ne fonctionne donc plus tel quel — pour le réactiver, ajouter dans LiteLLM
> un alias `qwen3-1.7b-monitor` (ou changer l'agent du webhook) pointant sur `qwen3-8b` (GPU).

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