Funk-lab/admin/k8s/n8n.md
Alkatrazz e61f1c76fc docs(n8n): fix parsing webhook alertes (0 alerte/unknown) + filtre anti-bruit
Le noeud Format Alertes lisait body.alerts/body.status au lieu de body.body.* ->
tous les mails affichaient 0 alerte/unknown meme en cas de vraie alerte.
Correctif applique au workflow (live) + filtre Watchdog/InfoInhibitor/severity none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:37:13 +02:00

12 KiB

n8n — Automatisation de workflows

n8n est le moteur d'automatisation du lab. Il orchestre les tâches récurrentes, connecte les services entre eux et envoie des rapports/alertes par email via LiteLLM + Postfix.


Accès

URL http://n8n.lab.local
Namespace k8s ai
Port interne 5678
API key générée dans Settings → n8n API — stockée dans le vault Ansible (vault_n8n_api_key)

Architecture

Déclencheurs                 Pipeline                        Sortie
────────────                 ────────                        ──────
AlertManager webhook    →    Formatage JSON                  Email Gmail
Cron (toutes les heures) →  Requêtes Prometheus        →    (via Postfix :25)
Webhook manuel          →    LiteLLM hermes-default
                             (résumé/analyse)

SMTP : n8n envoie via Postfix sur 192.168.10.1:25 (pas d'auth, réseau interne). Postfix relaie vers smtp.gmail.com:587 avec App Password.

LLM : n8n appelle LiteLLM sur http://192.168.10.1:4000/v1/chat/completions avec model: hermes-default.


Déploiement (ArgoCD)

Manifests dans k8s/apps/n8n/ :

  • deployment.yaml — pod n8n
  • service.yaml — ClusterIP :5678
  • ingress.yaml — IngressRoute Traefik
  • pvc.yaml — données applicatives (NFS)
  • secret.yaml — secrets (non commité, à créer manuellement)

Application ArgoCD : k8s/apps-of-apps/apps/n8n.yaml

Créer le secret (première installation)

kubectl create secret generic n8n-secret -n ai \
  --from-literal=db-password="PASSWORD" \
  --from-literal=encryption-key="$(openssl rand -hex 32)"

Le mot de passe est dans le vault Ansible (vault_pg_n8n_password).


Credentials configurées

Nom Type Détail
Postfix local SMTP 192.168.10.1:25, sans auth, STARTTLS désactivé

Pour créer une credential OpenAI/LiteLLM manuellement : Settings → Credentials → New → OpenAI → Base URL: http://192.168.10.1:4000/v1, API Key: lm-studio


Workflows actifs

1. Alertes Prometheus → Résumé LLM → Gmail

ID : 4lfZ0sHZqQ5HYEph
Déclencheur : Webhook POST sur http://n8n.lab.local/webhook/alerts
Configuré dans : k8s/infra/monitoring/values.yaml (receiver AlertManager)

Pipeline :

AlertManager POST
    │
    ├── Respond to Webhook (répond OK immédiatement)
    │
    ▼
Format Alertes (Code)
  → extrait alertname, instance, severity, summary, description
    │
    ▼
Résumé LLM (HTTP Request → LiteLLM)
  → system: "assistant ops, résumé concis en français"
    │
    ▼
Envoyer Email (SMTP)
  → Subject: [Funk] N alerte(s) — firing/resolved
  → Body: résumé LLM

Test manuel :

curl -X POST http://n8n.lab.local/webhook/alerts \
  -H "Content-Type: application/json" \
  -d '{
    "version":"4","status":"firing","receiver":"n8n-webhook",
    "alerts":[{
      "status":"firing",
      "labels":{"alertname":"HighMemory","instance":"192.168.10.12:9100","severity":"warning"},
      "annotations":{"summary":"RAM élevée","description":"RAM > 85%"}
    }]
  }'

2. Rapport Horaire Système — Funk

ID : i2KAGxBFDps7IjIP
Déclencheur : Cron toutes les heures (:00)

Pipeline :

Schedule (toutes les heures)
    │
    ├── CPU (Prometheus)          avg 1h par nœud
    ├── Mémoire (Prometheus)      avg_over_time 1h
    ├── Mémoire Max (Prometheus)  max_over_time 1h
    ├── Disque (Prometheus)       snapshot actuel
    ├── Services (Prometheus)     up/down
    └── Services Changes          changements d'état sur 1h
    │
    ▼
Format Rapport (Code)
  → tableau métriques par nœud (avg/max mémoire, CPU moyen 1h)
  → liste services stables/instables
    │
    ▼
Analyse LLM (HTTP Request → LiteLLM)
  → system: "résumé état général, points d'attention, recommandation"
    │
    ▼
Préparer Email (Code)
  → assemble : résumé LLM + séparateur + tableau métriques
    │
    ▼
Envoyer Rapport (SMTP)
  → Subject: [Funk] Rapport systeme - DD/MM/YYYY HH:MM:SS
  → Body HTML (pre tag) : résumé + métriques

Queries Prometheus utilisées :

Nœud Query
CPU 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[1h])) * 100)
Mémoire avg_over_time((1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)[1h:5m]) * 100
Mémoire Max max_over_time((1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)[1h:5m]) * 100
Disque (filesystem_size - filesystem_free) / filesystem_size * 100 (mountpoint=/)
Services up
Services Changes changes(up[1h]) > 0
GPU Power 1h avg_over_time(rocm_gpu_power_watts{instance="gpu-01"}[1h:5m]) → Wh cette heure
GPU Power 24h avg_over_time(rocm_gpu_power_watts{instance="gpu-01"}[24h:5m]) → kWh 24h glissants

Section énergie GPU : affichée dans chaque rapport entre l'analyse Hermes et les métriques brutes. À minuit (exécution 00:00), un encart BILAN JOURNALIER met en avant la consommation totale en kWh des 24h écoulées.


3. Hermes Doc Worker — Funk-lab

ID : CX72UUOempE1YtNL
Déclencheurs : Schedule quotidien 22h00 · webhook manuel POST /webhook/hermes-doc-run · (un ancien Schedule 30min existe mais est désactivé)

Pipeline :

Schedule 22h (ou webhook manuel)
    │
    ▼
Déclencher run (HTTP POST storage-01:9095/trigger/all)
  → lance hermes-auto-improve --all (28 docs, ~60-90 min)
    │
    ▼
┌── Attendre run (5 min) ◄────────────┐
│       │                             │
│       ▼                             │
│   Status run (GET :9095/status)     │
│       │                             │
│       ▼                             │
│   Run terminé ? (IF running=false) ─┘ false → reboucle
│       │ true
└───────▼
Format Run → LLM Run (résumé) → Email Run → Envoyer Run
    │
    ▼
Est 22h ? (IF heure courante = 22)
    │ true
    ▼
Déclencher PR (POST :9095/trigger/daily-pr)
  → crée ou met à jour la PR hermes/daily-work → main (squash-only)
    │
    ▼
Attendre PR (5 min) → Status PR → Format PR → LLM PR → Email PR → Envoyer PR

Backend : trigger server hermes-auto-improve sur storage-01 (:9095), déployé par le rôle Ansible hermes_auto_improve. Voir admin/ia/hermes-auto-improve.md.

Gestion via API : la clé API n8n est dans le vault Ansible (vault_n8n_api_key) :

make vault-view | grep n8n_api_key
curl -s -H "X-N8N-API-KEY: $KEY" http://n8n.lab.local/api/v1/workflows

Workflows à ajouter

Court terme

Alerte disque > 80%
Cron toutes les 30 min → query Prometheus disk > 80 → si résultat non vide → email d'alerte.
Utile car AlertManager ne couvre pas toujours les seuils fins par nœud.

Redémarrage automatique pod crashé
Webhook AlertManager sur KubePodCrashLoopingkubectl rollout restart via HTTP Request vers l'API k8s (ou script SSH sur storage-01) → notification email.

Rapport hebdomadaire étendu
Cron dimanche 09h → même pipeline que le rapport horaire mais avec fenêtre 7 jours ([7d]) + statistiques min/max de la semaine.

Moyen terme

Budget Anthropic API
Cron quotidien → requête API Anthropic usage → si > seuil configuré → email d'alerte.
Évite les surprises de facturation Claude.

Notification nouveaux commits Funk-lab
GitHub webhook → n8n → email/notification résumé du commit.
Utile pour tracker les changements sur d'autres postes.

Monitor llama-server performance
Cron horaire → query Prometheus llamacpp_tokens_per_second → si tokens/s < seuil (GPU dégradé) → alerte email.

Rotation/nettoyage logs
Cron quotidien → SSH storage-01 → journalctl --vacuum-time=30d → rapport taille libérée.

Long terme

Auto-scaling modèle Hermes
Monitor charge LiteLLM → si charge élevée, switcher automatiquement vers claude-sonnet-4-6 via hermes-switch claude → revenir à qwen après N minutes.

RAG re-indexation automatique
Webhook Git (push sur admin/) → déclencher rag-ingest via SSH sur storage-01 → email confirmation nombre de chunks indexés.

Résumé quotidien Hermes
Cron 08h00 → interroger Hermes sur l'état du lab → recevoir un briefing matinal par email avec état services, alertes de la nuit, métriques clés.


Opérations courantes

# Logs n8n
kubectl logs deploy/n8n -n ai -f

# Redémarrer n8n
kubectl rollout restart deploy/n8n -n ai

# Lister les workflows via API
curl -s http://n8n.lab.local/api/v1/workflows \
  -H "X-N8N-API-KEY: <clé>" | python3 -m json.tool

# Activer/désactiver un workflow
curl -s -X POST http://n8n.lab.local/api/v1/workflows/<id>/activate \
  -H "X-N8N-API-KEY: <clé>"
curl -s -X POST http://n8n.lab.local/api/v1/workflows/<id>/deactivate \
  -H "X-N8N-API-KEY: <clé>"

# Dernières exécutions
curl -s "http://n8n.lab.local/api/v1/executions?limit=5" \
  -H "X-N8N-API-KEY: <clé>" | python3 -c \
  "import sys,json; [print(e['id'], e['status'], e.get('stoppedAt','')) for e in json.load(sys.stdin)['data']]"

Dépannage

Symptôme Cause Action
Email vide Expression $json.xxx évaluée à vide dans emailSend Utiliser un nœud Code intermédiaire qui assemble le corps, puis emailType: "html" + html: "={{ $json.htmlBody }}"
SMTP ETIMEDOUT Port 25 non ouvert pour les pods Vérifier nftables : tcp dport 25 ip saddr { 192.168.10.0/24, 10.42.0.0/16 }
LLM timeout hermes-default trop lent (> 2 min) Passer en mode async : ajouter "Respond to Webhook" en branche parallèle avant le LLM
Prometheus query vide Service DNS interne non résolu Utiliser l'URL service k8s : http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090
Workflow ne se déclenche pas Webhook AlertManager mal configuré Tester : curl -X POST http://n8n.lab.local/webhook/alerts -d '{...}'

Workflow « Alertes Prometheus → Résumé LLM → Gmail » — parsing du webhook

⚠️ Piège corrigé le 2026-06-15. Le nœud Webhook n8n imbrique le payload HTTP sous .body ($input.first().json = { headers, params, query, body }). Le nœud « Format Alertes » lisait body.alerts / body.status au mauvais niveau → toujours [] et 'unknown'. Résultat : tous les mails affichaient « 0 alerte(s) — unknown » sans détail, même lors d'une vraie alerte (pipeline d'alerte aveugle).

Correctif appliqué dans le code du nœud « Format Alertes » :

const root = $input.first().json;
const payload = root.body || root;          // <-- lit le payload imbriqué (repli si format plat)
const groupStatus = payload.status || 'unknown';
const allAlerts = payload.alerts || [];
// Anti-bruit : exclure Watchdog / InfoInhibitor et severity none/info
const alerts = allAlerts.filter(a => {
  const sev = (a.labels?.severity || '').toLowerCase();
  const name = a.labels?.alertname || '';
  return !['none','info',''].includes(sev) && !['Watchdog','InfoInhibitor'].includes(name);
});
if (alerts.length === 0) { return []; }     // aucune vraie alerte -> pas d'email (stoppe le workflow)
  • AlertManager reçoit toujours son HTTP 200 (nœud « Respond 200 » en branche parallèle, indépendant).
  • Test sans email : curl -XPOST http://n8n.lab.local/webhook/alerts -d '{"status":"firing","alerts":[{"labels":{"alertname":"Watchdog","severity":"none"}}]}' → doit produire 0 item au nœud « Format Alertes », pas d'email.
  • Workflow non versionné (vit dans la base n8n) ; édition via l'API : PUT /api/v1/workflows/<id> avec {name,nodes,connections,settings}.

Configuration Ansible / k8s

  • Secrets k8s : kubectl get secret n8n-secret -n ai
  • Base de données : PostgreSQL n8n sur storage-01 (vault_pg_n8n_password)
  • PVC data : NFS, 5 Gi, claimName n8n-data
  • nftables : port 25 ouvert 10.42.0.0/16 (pods) dans le rôle gateway
  • AlertManager : receiver hermes-monitor dans k8s/infra/monitoring/values.yaml