post: Remplacer Ollama par Claude API dans l'alert-agent
Some checks failed
Hugo Build & Deploy / build-deploy (push) Failing after 1m2s
Some checks failed
Hugo Build & Deploy / build-deploy (push) Failing after 1m2s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
130
content/posts/alert-agent-claude-api-fr.md
Normal file
130
content/posts/alert-agent-claude-api-fr.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: "Remplacer Ollama par Claude API dans un Alert-Agent Docker Swarm"
|
||||
date: 2026-07-21T00:00:00+00:00
|
||||
draft: false
|
||||
tags: ["cybersecurity", "infosec", "homelab", "docker-swarm", "docker", "devops", "selfhosted", "opensource", "build-in-public", "french-tech", "apprendre-la-cyber", "threat-intelligence"]
|
||||
summary: "Mon alert-agent appelait Ollama/Mistral pour prendre des décisions de remédiation. Problème : 40+ secondes de latence, timeouts, et une sévérité figée à 'warning'. Voilà comment j'ai branché Claude Haiku à la place — et pourquoi ça change vraiment quelque chose."
|
||||
description: "Intégration de Claude API (Anthropic) dans un alert-agent Prometheus/Docker Swarm pour remplacer Ollama. Sévérité dynamique, latence réduite de 40s à 2s, architecture multi-backend."
|
||||
author: "Bojemoi"
|
||||
ShowToc: true
|
||||
ShowReadingTime: true
|
||||
---
|
||||
|
||||
Mon homelab tourne sur Docker Swarm avec un pipeline de sécurité continu. Quand Prometheus déclenche une alerte — swap élevé, service crashé, disk plein — un composant que j'appelle l'**alert-agent** reçoit le webhook, enrichit le contexte avec des infos Docker, consulte un LLM pour décider quoi faire, et envoie le résultat sur Telegram.
|
||||
|
||||
Jusqu'ici, ce LLM c'était Ollama avec Mistral:latest, tournant en local sur meta-68.
|
||||
|
||||
Le problème : ça ne fonctionnait plus vraiment.
|
||||
|
||||
## Le Problème avec Ollama
|
||||
|
||||
Ollama était là depuis le début, mais au fil des mois, meta-68 est devenu de plus en plus chargé : MSF teamserver (6 GB RAM), ZAP qui scanne en continu, 15 replicas bm12, 3 replicas uzi... Le modèle Mistral avait du mal à répondre dans les temps.
|
||||
|
||||
En mesurant directement :
|
||||
|
||||
```
|
||||
status: 200 time: 42.9 s
|
||||
{"action":"assistant_response","message":"Understood. I have received a test message..."}
|
||||
```
|
||||
|
||||
**43 secondes.** Et même là, Mistral ignorait le format JSON demandé et répondait en texte libre.
|
||||
|
||||
Avec un timeout à 30s dans le code, chaque alerte finissait en `notify_only` par défaut, sans décision LLM réelle. Le fallback prenait le relais — ce qui était mieux que rien, mais l'agent ne servait plus à grand chose.
|
||||
|
||||
## L'autre Problème : la Sévérité Figée
|
||||
|
||||
Toutes mes alertes arrivaient comme `⚠️ Severity: warning`. Pourquoi ? Parce que la sévérité affichée dans Telegram venait directement du label Prometheus, qui est défini statiquement dans les règles d'alerting.
|
||||
|
||||
`HighSeverityFindingsFound` → `warning`. `NodeHighSwapUsage` → `warning`. `DiskAlmostFull` → `warning`.
|
||||
|
||||
Tout est `warning`. C'est inutile — un swap à 85% sur le manager n'a pas le même impact qu'un disk à 99% sur le worker qui héberge la base MSF.
|
||||
|
||||
## La Solution : Claude API avec Évaluation Dynamique
|
||||
|
||||
J'ai intégré Claude Haiku (Anthropic) comme backend LLM, avec deux changements clés.
|
||||
|
||||
### 1. Architecture multi-backend
|
||||
|
||||
Le backend LLM est maintenant configurable via une variable d'environnement `LLM_BACKEND` :
|
||||
|
||||
```python
|
||||
async def _call_claude(messages: list) -> dict:
|
||||
client = anthropic.AsyncAnthropic(api_key=settings.claude_api_key)
|
||||
system = next((m["content"] for m in messages if m["role"] == "system"), "")
|
||||
user_messages = [m for m in messages if m["role"] != "system"]
|
||||
response = await client.messages.create(
|
||||
model=settings.claude_model,
|
||||
max_tokens=256,
|
||||
system=system,
|
||||
messages=user_messages,
|
||||
)
|
||||
return _extract_json(response.content[0].text)
|
||||
```
|
||||
|
||||
`LLM_BACKEND=claude` active Claude. `LLM_BACKEND=ollama` revient à Mistral local. `LLM_BACKEND=kimi` appellerait Kimi K3 via Moonshot AI. Pas de rebuild nécessaire pour switcher.
|
||||
|
||||
### 2. Sévérité évaluée par le LLM
|
||||
|
||||
J'ai modifié le system prompt pour demander au LLM d'évaluer la sévérité réelle de l'alerte, indépendamment du label Prometheus :
|
||||
|
||||
```
|
||||
Severity levels (assess based on actual risk, ignore Prometheus label):
|
||||
- critical: immediate risk of data loss, service outage, or security breach
|
||||
- high: significant degradation, likely to escalate without action
|
||||
- medium: noticeable issue, should be addressed soon
|
||||
- low: minor issue, informational
|
||||
- info: no real impact
|
||||
|
||||
Respond with ONLY a JSON object:
|
||||
{"action": "<action>", "severity": "<severity>", "reason": "<brief reason>", "params": {}}
|
||||
```
|
||||
|
||||
Le JSON de réponse inclut maintenant un champ `severity` que le LLM choisit lui-même. Ce champ remplace le label Prometheus dans le message Telegram.
|
||||
|
||||
### 3. Emojis adaptatifs
|
||||
|
||||
```python
|
||||
severity_emoji = {
|
||||
"critical": "🔴",
|
||||
"high": "🟠",
|
||||
"medium": "🟡",
|
||||
"low": "🔵",
|
||||
"info": "⚪",
|
||||
}.get(severity or "", "⚠️")
|
||||
```
|
||||
|
||||
## Résultat
|
||||
|
||||
Avant :
|
||||
```
|
||||
🤖 Alert-Agent
|
||||
🔔 Alert: NodeHighSwapUsage
|
||||
⚠️ Severity: warning
|
||||
🧠 LLM decision: notify_only
|
||||
💬 Reason: LLM unavailable: ReadTimeout
|
||||
✅ Action: notify_only
|
||||
```
|
||||
|
||||
Après :
|
||||
```
|
||||
🤖 Alert-Agent
|
||||
🔔 Alert: NodeHighSwapUsage
|
||||
🟠 Severity: high
|
||||
🧠 LLM decision: notify_only
|
||||
💬 Reason: Swap at 85% on the Swarm manager — no Docker service to restart, but warrants monitoring. Escalate if it reaches 95%.
|
||||
✅ Action: notify_only
|
||||
```
|
||||
|
||||
Latence : **2.4 secondes** au lieu de 40+. La sévérité est maintenant contextuelle — le même `NodeHighSwapUsage` peut être `medium` à 60% et `high` à 85%.
|
||||
|
||||
## Ce que j'ai Gardé d'Ollama
|
||||
|
||||
Le stack `51-service-ollama.yml` est conservé avec `replicas: 0`. Si un jour Claude API est indisponible ou trop coûteux, un `docker service scale ollama_ollama=1` + `LLM_BACKEND=ollama` suffit à revenir en arrière.
|
||||
|
||||
C'est l'avantage de l'architecture multi-backend : pas de couplage fort avec un provider.
|
||||
|
||||
## Leçon
|
||||
|
||||
Un LLM local gratuit peut sembler idéal pour ce genre d'usage. Mais quand le node est déjà à 90% de sa capacité RAM/CPU, l'inference locale devient le goulot d'étranglement. À 0.25$ / million de tokens d'input pour Haiku, et avec des alertes qui se déclenchent quelques dizaines de fois par jour au max, le coût mensuel sera probablement inférieur à 1$.
|
||||
|
||||
Parfois, l'API cloud est la solution pragmatique.
|
||||
Reference in New Issue
Block a user