diff --git a/CLAUDE.md b/CLAUDE.md index 4eb3804..c55643c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,17 +6,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **Funk** — homelab IA composé d'un cluster Kubernetes (Talos) pour les services applicatifs, d'un hôte d'inférence LLM dédié (gpu-01 / llama-server ROCm), et d'un agent autonome (Hermes Agent) sur storage-01. Stack IaC complète : Ansible + talhelper + ArgoCD. -**État actuel (2026-05-25)** : +**État actuel (2026-05-29)** : - ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain) - ✅ RAG opérationnel — Qdrant 284 chunks, `rag-ingest` / `rag-query`, skill funk-ai -- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes +- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n - ✅ storage-01 installé — `192.168.10.1` (LAN cluster), `192.168.1.200` (WAN) - ✅ gpu-01 installé — llama-server GPU (:1234) + CPU (:1236/:1237) opérationnels -- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent +- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute `0.0.0.0:4000`) - ✅ Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local) -- ✅ postfix_relay — SMTP sortant via Brevo (aliyesilkaya93@gmail.com) +- ✅ postfix_relay — SMTP sortant via Gmail SMTP + App Password (aliyesilkaya93@gmail.com) +- ✅ Open WebUI opérationnel — `openwebui.lab.local`, namespace `ai`, backend LiteLLM +- ✅ n8n opérationnel — `n8n.lab.local`, namespace `ai`, 2 workflows actifs - ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis -- ⏳ ArgoCD bootstrap + workloads IA (Open WebUI, n8n) Journal de progression : `PROGRESS.md` → `progress/YYYY-MM-DD.md` diff --git a/admin/k8s/n8n.md b/admin/k8s/n8n.md new file mode 100644 index 0000000..45d450f --- /dev/null +++ b/admin/k8s/n8n.md @@ -0,0 +1,248 @@ +# 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 | + +--- + +## 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) + +```bash +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** : +```bash +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` | + +--- + +## 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 `KubePodCrashLooping` → `kubectl 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 + +```bash +# 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: " | python3 -m json.tool + +# Activer/désactiver un workflow +curl -s -X POST http://n8n.lab.local/api/v1/workflows//activate \ + -H "X-N8N-API-KEY: " +curl -s -X POST http://n8n.lab.local/api/v1/workflows//deactivate \ + -H "X-N8N-API-KEY: " + +# Dernières exécutions +curl -s "http://n8n.lab.local/api/v1/executions?limit=5" \ + -H "X-N8N-API-KEY: " | 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 '{...}'` | + +--- + +## 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` diff --git a/admin/k8s/open-webui.md b/admin/k8s/open-webui.md new file mode 100644 index 0000000..7a16605 --- /dev/null +++ b/admin/k8s/open-webui.md @@ -0,0 +1,124 @@ +# Open WebUI — Interface de chat IA + +Open WebUI est l'interface web de chat connectée à LiteLLM. Elle permet d'accéder à tous les modèles du lab depuis un navigateur, avec historique persistant et gestion multi-utilisateurs. + +--- + +## Accès + +| URL | `http://openwebui.lab.local` | +|---|---| +| Namespace k8s | `ai` | +| Port interne | `8080` | +| Backend LLM | LiteLLM `http://192.168.10.1:4000/v1` | + +**Modèles disponibles** (via LiteLLM) : +- `hermes-default` — Qwen3-8B sur gpu-01, profil principal (~35 tok/s) +- `qwen3-8b` — alias direct gpu-01 +- `claude-sonnet-4-6` — Claude API Anthropic (facturé) +- `claude-opus-4-7` — Claude API Anthropic (facturé) + +--- + +## Architecture + +``` +Navigateur + │ + ▼ +Traefik (openwebui.lab.local) + │ + ▼ +Open WebUI pod (namespace ai) + │ OPENAI_API_BASE_URL=http://192.168.10.1:4000/v1 + ▼ +LiteLLM (storage-01 :4000) + │ + ├── hermes-default → llama-server gpu-01 :1234 + └── claude-* → Anthropic API +``` + +**Base de données** : PostgreSQL sur storage-01 (`openwebui` DB). Les conversations, utilisateurs et paramètres sont persistés. + +--- + +## Déploiement (ArgoCD) + +Manifests dans `k8s/apps/open-webui/` : +- `deployment.yaml` — pod Open WebUI +- `service.yaml` — ClusterIP :8080 +- `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/open-webui.yaml` + +### Créer le secret (première installation) + +```bash +kubectl create secret generic open-webui-secret -n ai \ + --from-literal=litellm-api-key="lm-studio" \ + --from-literal=database-url="postgresql://openwebui:PASSWORD@192.168.10.1:5432/openwebui" \ + --from-literal=webui-secret-key="$(openssl rand -hex 32)" +``` + +Le mot de passe PostgreSQL est dans le vault Ansible (`vault_pg_openwebui_password`). + +--- + +## Premier démarrage + +1. Naviguer sur `http://openwebui.lab.local` +2. Créer un compte administrateur (premier compte = admin automatiquement) +3. Sélectionner un modèle dans le dropdown en haut +4. Commencer à discuter + +**Note** : au premier démarrage, Open WebUI télécharge des modèles sentence-transformer (~500 MB) pour le RAG local. Le pod peut sembler lent pendant 2-3 minutes — c'est normal. + +--- + +## Différence avec Hermes TUI + +| | Open WebUI | Hermes TUI | +|---|---|---| +| Accès | Navigateur (réseau local) | SSH + `hermes chat` | +| Contexte Funk | ✗ LLM générique | ✅ Skills RAG, monitoring, agent | +| Historique | ✅ Persistant (PostgreSQL) | ✗ Session uniquement | +| Multi-utilisateurs | ✅ | ✗ | +| Upload fichiers | ✅ | ✗ | +| Usage | Chat général, documents | Admin lab, debug infra | + +--- + +## Variables d'environnement clés + +| Variable | Valeur | Rôle | +|---|---|---| +| `OPENAI_API_BASE_URL` | `http://192.168.10.1:4000/v1` | Backend LiteLLM | +| `OPENAI_API_KEY` | `lm-studio` | Clé LiteLLM master key | +| `DATABASE_URL` | `postgresql://...` | Persistance PostgreSQL | +| `ENABLE_OLLAMA_API` | `false` | Désactiver Ollama | +| `WEBUI_NAME` | `Funk AI` | Nom affiché | + +--- + +## Dépannage + +| Symptôme | Cause | Action | +|---|---|---| +| "Aucun modèle disponible" | LiteLLM inaccessible | Vérifier `curl http://192.168.10.1:4000/v1/models -H 'Authorization: Bearer lm-studio'` depuis un pod | +| Pod OOMKilled | RAM insuffisante au 1er boot | Attendre le rechargement ou augmenter la limit mémoire | +| Connexion refusée | Pod pas encore prêt | `kubectl get pods -n ai` — attendre readiness probe | +| Erreur DB migrations | PostgreSQL inaccessible | `kubectl logs deploy/open-webui -n ai` | + +```bash +# Logs +kubectl logs deploy/open-webui -n ai -f + +# Redémarrer +kubectl rollout restart deploy/open-webui -n ai + +# Vérifier connectivité LiteLLM depuis le pod +kubectl exec -n ai deploy/open-webui -- curl -s http://192.168.10.1:4000/v1/models \ + -H 'Authorization: Bearer lm-studio' +```