diff --git a/CLAUDE.md b/CLAUDE.md index a9d0128..bc95a7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ Journal de progression : `PROGRESS.md` → `progress/YYYY-MM-DD.md` **Points clés d'architecture :** - `storage-01` et `gpu-01` sont **hors du cluster k8s** — consommés via `ExternalName` services ou `Endpoints` manuels - Single control-plane assumé : si compute-01 tombe, `kubectl apply` ne fonctionne plus mais les workloads continuent -- Modèles LLM stockés sur NFS RAID5 de storage-01 (`/srv/data/models`), montés sur gpu-01 (`/mnt/models`) +- Modèles LLM sur storage-01 (`/srv/data/models`, NVMe depuis 2026-05-29), montés sur gpu-01 via NFS (`/mnt/models`) --- @@ -138,7 +138,37 @@ git commit -m "open-webui: bump to v0.4.0" git push ``` -ArgoCD utilise le repo `git@github.com:Alkatrazz24/Funk-lab.git` branche `main`. Les deux namespaces gérés actuellement : `infra` (Traefik, MetalLB, NFS-provisioner) et `monitoring` (kube-prometheus-stack 85.0.2). +ArgoCD utilise le repo `git@github.com:Alkatrazz24/Funk-lab.git` branche `main`. + +### Structure k8s/ + +``` +k8s/ +├── apps-of-apps/ +│ ├── root.yaml # Application ArgoCD racine (pointe vers apps-of-apps/apps/) +│ └── apps/ # Un fichier Application par workload — ArgoCD les crée automatiquement +│ ├── n8n.yaml +│ ├── open-webui.yaml +│ ├── monitoring.yaml +│ └── nfs-provisioner.yaml +├── apps/ # Manifests raw k8s par application +│ ├── n8n/ # namespace: ai +│ ├── open-webui/ # namespace: ai +│ └── {external-services}/ # ExternalName/Endpoints pointant vers storage-01 ou gpu-01 +└── infra/ # Helm releases (HelmRelease + values.yaml) + ├── monitoring/ # kube-prometheus-stack 85.0.2 + ├── nfs-provisioner/ + ├── traefik/ + └── metallb/ +``` + +### Ajouter un nouveau workload k8s + +1. Créer `k8s/apps//` avec les manifests (Deployment, Service, Ingress, PVC…) +2. Créer `k8s/apps-of-apps/apps/.yaml` (ArgoCD Application CRD) — copier `n8n.yaml` comme modèle +3. `git commit` + `git push` → ArgoCD prend en charge automatiquement + +Namespaces actifs : `argocd`, `infra` (Traefik, MetalLB, NFS-provisioner), `monitoring` (Prometheus/Grafana), `ai` (n8n, open-webui). --- @@ -179,7 +209,7 @@ ask-agent monitor "logs litellm : — erreurs ?" ### RAG — Documentation Funk -Qdrant collection `funk-docs` indexée depuis `admin/` (284 chunks, score min 0.60). +Qdrant collection `funk-docs` indexée depuis `admin/` (339 chunks, score min 0.60). ```bash rag-query "comment relancer dnsmasq ?" @@ -278,6 +308,6 @@ talosctl etcd snapshot /path/to/backup.db --nodes compute-01 - **Git** : branches `main` (prod) / `feature/*` — conventional commits (`feat:`, `fix:`, `chore:`, `docs:`) - **Secrets** : Ansible Vault (Ansible), SOPS+age (Talos), Sealed Secrets (k8s) -- **Namespaces k8s** : `argocd`, `infra`, `monitoring`, `external` -- **Hermes data** : `~hermes/` doit résider sur le RAID5 `/srv/data`, pas sur le SSD OS +- **Namespaces k8s** : `argocd`, `infra`, `monitoring`, `ai`, `external` +- **Hermes data** : profils/données dans `/srv/data/hermes` (NVMe, bind-mount `/home/data`), code dans `/opt/hermes/` (SSD OS) - **RAM compute-02/03** : toujours définir `resources.requests` + `resources.limits` (8 GB RAM, OOM-killer actif) diff --git a/README.md b/README.md index 5e08990..fb22dae 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ L'ensemble est géré en Infrastructure-as-Code : **Ansible** pour les hôtes Al ┌────────────────┐ │ storage-01 │ AlmaLinux 9.7 │ 192.168.10.1 │ Gateway + NAT + DNS - │ │ NFS RAID5 · PostgreSQL + │ │ NFS · PostgreSQL │ │ Qdrant · LiteLLM · Hermes └───────┬────────┘ │ LAN cluster (192.168.10.0/24) @@ -95,13 +95,14 @@ L'ensemble est géré en Infrastructure-as-Code : **Ansible** pour les hôtes Al |---|---|---|---|---| | storage-01 | 192.168.10.1 | AlmaLinux 9.7 | 32 GB | Gateway · NFS · Services IA | | gpu-01 | 192.168.10.20 | AlmaLinux 9.7 | 32 GB | Inférence LLM (RX 6700XT) | -| compute-01 | 192.168.10.11 | Talos v1.10+ | 16 GB | k8s control-plane | -| compute-02 | 192.168.10.12 | Talos v1.10+ | 8 GB | k8s worker | -| compute-03 | 192.168.10.13 | Talos v1.10+ | 8 GB | k8s worker | +| compute-01 | 192.168.10.11 | Talos v1.13 | 16 GB | k8s control-plane | +| compute-02 | 192.168.10.12 | Talos v1.13 | 8 GB | k8s worker | +| compute-03 | 192.168.10.13 | Talos v1.13 | 8 GB | k8s worker | ### Stockage -- RAID5 logiciel (`md0`) — 4 × 1 TB SATA → **2,7 TB** utiles sur storage-01 +- NVMe système (`/home/data`, ~845 GB XFS) — bind-monté sur `/srv/data` depuis 2026-05-29 +- RAID5 logiciel (`md0`) — 4 × 1 TB SATA, actif mais plus monté (sdb ATA errors + sdd retiré) - Point de montage `/srv/data` — héberge modèles LLM, bases de données, données Hermes --- @@ -127,6 +128,11 @@ L'ensemble est géré en Infrastructure-as-Code : **Ansible** pour les hôtes Al │ ├── litellm # Proxy LLM + script ask-agent │ ├── hermes_agent # Agent IA autonome │ ├── hermes_user # User hermes + SSH partagé entre hosts +│ ├── rag # RAG : scripts rag-ingest/rag-query + skill Hermes +│ ├── alertmanager_webhook # Pont AlertManager → ask-agent +│ ├── postfix_relay # SMTP sortant via Gmail +│ ├── node_exporter # Métriques Prometheus +│ ├── minio # Stockage objet S3 │ ├── postgresql # Base de données │ └── qdrant # Base vectorielle ├── hermes-skills/ # Skills Hermes versionnés @@ -195,7 +201,7 @@ ansible-vault encrypt ansible/group_vars/all/vault.yml ansible-vault edit ansible/group_vars/all/vault.yml ``` -Variables stockées dans le vault : `vault_anthropic_api_key`, `vault_pg_hermes_password`, `vault_pg_litellm_password`. +Variables stockées dans le vault : `vault_anthropic_api_key`, `vault_pg_hermes_password`, `vault_pg_litellm_password`, `vault_pg_n8n_password`, `vault_pg_openwebui_password`, `vault_postfix_relay_password`. --- @@ -213,14 +219,18 @@ Elle est gérée automatiquement par le rôle Ansible `llama_server`. ## Roadmap -- [x] Ansible — storage-01 (gateway, NFS, PostgreSQL, Qdrant, LiteLLM, Hermes) +- [x] Ansible — storage-01 (gateway, NFS, PostgreSQL, Qdrant, LiteLLM, Hermes, postfix, AlertManager webhook) - [x] Ansible — gpu-01 (ROCm, llama-server ×3 — GPU + 2× CPU) - [x] Stack IA opérationnelle (Hermes multi-profils + Qwen3 + Claude API) - [x] Délégation inter-agents (`ask-agent` + skill `agent-delegation`) +- [x] RAG opérationnel — Qdrant 339 chunks, `rag-query` / `rag-ingest` - [x] User `hermes` avec SSH partagé entre storage-01 et gpu-01 -- [x] Cluster Kubernetes opérationnel (Talos + talhelper) +- [x] Cluster Kubernetes opérationnel (Talos v1.13 + talhelper) - [x] MetalLB + Traefik + wildcard DNS `lab.local` +- [x] Monitoring complet (kube-prometheus-stack + AlertManager → Hermes + n8n) +- [x] Open WebUI (`openwebui.lab.local`, namespace `ai`) +- [x] n8n (`n8n.lab.local`, namespace `ai`, 2 workflows actifs) +- [x] Email sortant — Postfix relay → Gmail SMTP - [ ] WOL compute nodes (configuration BIOS) - [ ] Route statique Freebox -- [ ] ArgoCD bootstrap + workloads IA (Open WebUI, n8n, monitoring) -- [ ] Rôle MinIO +- [ ] Modèle d'embedding dédié (nomic-embed-text ou bge-m3) pour améliorer le RAG diff --git a/admin/ia/hermes.md b/admin/ia/hermes.md index 4af171f..cf05da6 100644 --- a/admin/ia/hermes.md +++ b/admin/ia/hermes.md @@ -5,7 +5,7 @@ Hermes Agent tourne en deux services systemd sur **storage-01** : - `hermes-dashboard` — interface web (port 9119) Il passe par **LiteLLM** (`:4000`) pour toute inférence — Qwen local ou Claude API. -Les données persistantes sont sur le RAID5 : `/srv/data/hermes`. +Les données persistantes sont dans `/srv/data/hermes` (NVMe bind-mount depuis 2026-05-29, plus RAID5). --- @@ -330,7 +330,7 @@ ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm ## Arborescence -Depuis l'incident 2026-05-13, le code est sur le SSD OS et les données sur le RAID5. +Depuis l'incident 2026-05-13, le code est sur le SSD OS. Depuis l'incident 2026-05-29, les données sont sur NVMe (`/home/data` bind-monté sur `/srv/data`), plus sur le RAID5. ``` /opt/hermes/ → home utilisateur hermes (SSD OS) @@ -340,7 +340,7 @@ Depuis l'incident 2026-05-13, le code est sur le SSD OS et les données sur le R ├── hermes_cli/ → source CLI └── web/ → source frontend dashboard -/srv/data/hermes/ → HERMES_HOME (RAID5) — données persistantes +/srv/data/hermes/ → HERMES_HOME (NVMe bind-mount) — données persistantes ├── config.yaml → configuration profil par défaut (géré Ansible) ├── .env → LM_BASE_URL, LM_API_KEY (géré Ansible) ├── memories/ → mémoire persistante profil par défaut @@ -413,7 +413,7 @@ Fermer le TUI et en ouvrir un nouveau après chaque modification. | Sujet | Détail | |---|---| -| Code vs données | Code + venv : `/opt/hermes/.hermes/hermes-agent/` (SSD OS) — Profils/données : `/srv/data/hermes/` (RAID5) | +| Code vs données | Code + venv : `/opt/hermes/.hermes/hermes-agent/` (SSD OS) — Profils/données : `/srv/data/hermes/` (NVMe) | | `HERMES_HOME` | Toujours préfixer les commandes hermes avec `HERMES_HOME=/srv/data/hermes` | | `LM_API_KEY` | Doit valoir `lm-studio` exactement | | `context_length` | Hermes exige 64k minimum — override via `model.context_length: 65536` dans config.yaml | diff --git a/admin/ia/litellm.md b/admin/ia/litellm.md index 9f453fb..3958ef4 100644 --- a/admin/ia/litellm.md +++ b/admin/ia/litellm.md @@ -56,7 +56,7 @@ Script déployé sur storage-01 : ```bash sudo hermes-switch status # voir le modèle actuel de hermes-default -sudo hermes-switch qwen # Qwen2.5-14B local (gratuit, ~35 tok/s) +sudo hermes-switch qwen # Qwen3-8B local GPU (gratuit, ~60 tok/s) sudo hermes-switch claude # Claude Sonnet 4.6 (payant, ~1-2$ par session debug) ``` @@ -84,7 +84,7 @@ curl http://127.0.0.1:4000/v1/models \ curl http://127.0.0.1:4000/v1/chat/completions \ -H "Authorization: Bearer lm-studio" \ -H "Content-Type: application/json" \ - -d '{"model": "qwen2.5-14b-instruct", "messages": [{"role": "user", "content": "Dis bonjour"}], "max_tokens": 30}' + -d '{"model": "qwen3-8b", "messages": [{"role": "user", "content": "Dis bonjour"}], "max_tokens": 30}' # Test inférence Claude curl http://127.0.0.1:4000/v1/chat/completions \ @@ -106,6 +106,22 @@ elle vient du vault Ansible (`vault_anthropic_api_key`). Jamais en clair dans co --- +## ⚠️ Gap IaC — modèles CPU absents du template Ansible + +`ansible/roles/litellm/templates/config.yaml.j2` n'inclut que 4 modèles : +`hermes-default`, `qwen3-8b`, `claude-sonnet-4-6`, `claude-opus-4-7`. + +Les entrées `qwen3-1.7b-system` (:1236) et `qwen3-1.7b-monitor` (:1237) ont été ajoutées +**manuellement** dans `/etc/litellm/config.yaml` sur storage-01. + +Un `make apply-storage` sur un hôte vierge ne déploierait PAS les routes CPU nécessaires +à `ask-agent system` et `ask-agent monitor`. + +**Axe d'amélioration** : ajouter les 2 entrées CPU dans `config.yaml.j2` avec des variables +dans `defaults/main.yml` (`litellm_cpu_system_url`, `litellm_cpu_monitor_url`). + +--- + ## Ajouter un modèle 1. Modifier `roles/litellm/templates/config.yaml.j2` : diff --git a/admin/ia/llama_server.md b/admin/ia/llama_server.md index 6efb7cb..2875ce9 100644 --- a/admin/ia/llama_server.md +++ b/admin/ia/llama_server.md @@ -177,6 +177,17 @@ sudo systemctl restart llama-server --- +## ⚠️ Gap IaC — instances CPU non couvertes par Ansible + +Le rôle `ansible/roles/llama_server/` ne déploie que l'instance **GPU** (`llama-server.service` sur :1234). +Les instances CPU (`llama-server-system` :1236 et `llama-server-monitor` :1237) ont été créées **manuellement** et ne sont pas encore dans le code Ansible. + +Conséquence : un `make apply-gpu` complet sur un hôte vierge ne déploierait PAS les instances CPU. + +**Axe d'amélioration** : étendre le rôle `llama_server` pour déployer les 3 instances via une boucle (`with_items` ou `loop` Ansible), avec des variables dédiées dans `host_vars/gpu-01/vars.yml`. + +--- + ## Points d'attention | Sujet | Détail | @@ -189,3 +200,4 @@ sudo systemctl restart llama-server | Gemma 4 interdit | Crash ROCm sur prompts >800 tokens | | Modèles sur NFS | `/mnt/models` monté depuis storage-01 (NFS) — si NFS down, les 3 services échouent au démarrage | | 3 services indépendants | Chaque instance a son propre service systemd — redémarrer l'un n'affecte pas les autres | +| Instances CPU hors Ansible | llama-server-system et llama-server-monitor créés manuellement — voir section Gap IaC ci-dessus | diff --git a/admin/ia/rag.md b/admin/ia/rag.md index 5dfac47..88fb446 100644 --- a/admin/ia/rag.md +++ b/admin/ia/rag.md @@ -34,8 +34,8 @@ Hermes formule sa réponse en citant les sources | `rag-ingest` | `/usr/local/bin/rag-ingest` | Indexe les docs dans Qdrant | | `rag-query` | `/usr/local/bin/rag-query` | Interroge Qdrant depuis la ligne de commande | | Skill `rag-docs` | Profil `funk-ai` Hermes | Enseigne à Hermes comment utiliser `rag-query` | -| Docs indexées | `/srv/data/rag/docs/` (RAID5) | Copie locale du dossier `admin/` | -| Collection Qdrant | `funk-docs` | 284 chunks — dimension 4096 (Cosine) | +| Docs indexées | `/srv/data/rag/docs/` (NVMe) | Copie locale du dossier `admin/` | +| Collection Qdrant | `funk-docs` | 339 chunks — dimension 4096 (Cosine) | --- @@ -132,7 +132,7 @@ rsync -av --delete admin/ storage-01:/srv/data/rag/docs/ ssh storage-01 "rag-ingest /srv/data/rag/docs/" ``` -L'ingestion prend ~5-10 minutes pour 284 chunks (embeddings via GPU). +L'ingestion prend ~5-10 minutes pour 339 chunks (embeddings via GPU). Elle est idempotente — re-lancer ne crée pas de doublons (IDs stables basés sur MD5 du fichier+section). --- @@ -175,7 +175,7 @@ ansible/roles/rag/ hermes-skills/funk/rag-docs/ └── SKILL.md # Source versionnée du skill (copié dans files/) -/srv/data/rag/ (RAID5 storage-01) +/srv/data/rag/ (NVMe storage-01 — bind-mount /home/data) ├── docs/ # Copie de admin/ — source des chunks └── (Qdrant stocke dans /srv/data/qdrant/) ``` @@ -190,5 +190,5 @@ hermes-skills/funk/rag-docs/ | Modèle dédié | `nomic-embed-text` ou `bge-m3` diviseraient les faux positifs par ~3 | | Contention GPU | `rag-ingest` sollicite le GPU (port 1234) pendant ~5-10 min — éviter pendant une session d'inférence active | | Re-indexation | Obligatoire après modification de `admin/` — pas de sync automatique | -| Qdrant persistance | Données dans `/srv/data/qdrant/` sur RAID5 — survivent aux redémarrages | +| Qdrant persistance | Données dans `/srv/data/qdrant/` sur NVMe — survivent aux redémarrages | | Score minimum | `MIN_SCORE = 0.60` dans `rag-query` — tous les résultats sont au-dessus avec ce modèle | diff --git a/admin/index_knowledge.py b/admin/index_knowledge.py index 4a14ea0..4200824 100644 --- a/admin/index_knowledge.py +++ b/admin/index_knowledge.py @@ -32,12 +32,12 @@ KNOWLEDGE = [ { "category": "litellm", "tags": ["litellm", "proxy", "modèle", "switch"], - "text": "LiteLLM tourne sur storage-01 port 4000. master_key doit valoir exactement lm-studio. Hermes utilise l'alias hermes-default. Pour switcher: sudo hermes-switch qwen (Qwen2.5-7B local gratuit) ou sudo hermes-switch claude (Claude Sonnet 4.6 ~1-2$ par session). Le script modifie /etc/litellm/config.yaml et redémarre le service.", + "text": "LiteLLM tourne sur storage-01 port 4000. master_key doit valoir exactement lm-studio. Hermes utilise l'alias hermes-default. Pour switcher: sudo hermes-switch qwen (Qwen3-8B local gratuit) ou sudo hermes-switch claude (Claude Sonnet 4.6 ~1-2$ par session). Le script modifie /etc/litellm/config.yaml et redémarre le service.", }, { "category": "litellm", "tags": ["litellm", "config", "modèles", "anthropic"], - "text": "LiteLLM config /etc/litellm/config.yaml expose: hermes-default (alias switchable), qwen2.5-7b-instruct (gpu-01:1234), claude-sonnet-4-6 (API Anthropic), claude-opus-4-7 (API Anthropic). ANTHROPIC_API_KEY dans l'environnement systemd via vault Ansible.", + "text": "LiteLLM config /etc/litellm/config.yaml expose: hermes-default (alias switchable), qwen3-8b (gpu-01:1234 GPU), qwen3-1.7b-system (gpu-01:1236 CPU), qwen3-1.7b-monitor (gpu-01:1237 CPU), claude-sonnet-4-6 (API Anthropic), claude-opus-4-7 (API Anthropic). ANTHROPIC_API_KEY dans l'environnement systemd via vault Ansible.", }, { "category": "hermes", @@ -52,7 +52,7 @@ KNOWLEDGE = [ { "category": "hermes", "tags": ["hermes", "cli", "path", "git"], - "text": "Toujours lancer hermes -z depuis /srv/data/hermes pour éviter l'erreur PermissionError sur .git. Hermes remonte l'arborescence à la recherche d'un .git et échoue s'il tombe sur /home/ansible/.git sans permission. HERMES_HOME=/srv/data/hermes, données sur RAID5.", + "text": "Toujours lancer hermes -z depuis /srv/data/hermes pour éviter l'erreur PermissionError sur .git. Hermes remonte l'arborescence à la recherche d'un .git et échoue s'il tombe sur /home/ansible/.git sans permission. HERMES_HOME=/srv/data/hermes, données sur NVMe (bind-mount /home/data depuis 2026-05-29).", }, { "category": "hermes", @@ -72,17 +72,17 @@ KNOWLEDGE = [ { "category": "gpu", "tags": ["llama-server", "embeddings", "pooling", "vecteurs"], - "text": "llama-server doit être démarré avec --embeddings --pooling mean pour exposer /v1/embeddings compatible OpenAI. Sans --pooling mean: erreur 'Pooling type none is not OAI compatible'. Qwen2.5-7B produit des vecteurs de dimension 3584. Endpoint: http://192.168.10.20:1234/v1/embeddings.", + "text": "llama-server doit être démarré avec --embeddings --pooling mean pour exposer /v1/embeddings compatible OpenAI. Sans --pooling mean: erreur 'Pooling type none is not OAI compatible'. Qwen3-8B produit des vecteurs de dimension 3584. Endpoint: http://192.168.10.20:1234/v1/embeddings.", }, { "category": "gpu", "tags": ["modèles", "qwen", "gemma", "performance"], - "text": "Modèle actif gpu-01: Qwen2.5-14B-Instruct-Q4_K_M (~35 tok/s génération, ~200 tok/s prefill, ROCm natif). Contexte limité à 16384 tokens (VRAM: 8GB modèle + 3GB KV cache = 11GB sur 12GB). Alias API: qwen2.5-14b-instruct. Ne pas utiliser Gemma 4: crash ROCm >800 tokens. Dimension embeddings: 5120.", + "text": "Modèle actif gpu-01: Qwen3-8B Q4_K_M (~60 tok/s génération, ~300 tok/s prefill, ROCm 7.x natif). Contexte 32768 tokens (VRAM: ~5GB modèle + ~4GB KV cache = ~9GB sur 12GB). Alias API: qwen3-8b. Instances CPU: qwen3-1.7b-system sur :1236 et qwen3-1.7b-monitor sur :1237 (~21 tok/s CPU). Ne pas utiliser Gemma 4: crash ROCm >800 tokens. Dimension embeddings: 3584.", }, { "category": "postgresql", "tags": ["postgresql", "pgdg", "PGDATA", "RAID5"], - "text": "PostgreSQL 16 installé depuis repo officiel pgdg sur storage-01. PGDATA sur RAID5: /srv/data/postgres. Override systemd dans /etc/systemd/system/postgresql-16.service.d/pgdata.conf. Auth: peer en local, scram-sha-256 pour connexions réseau. Bases: hermes et litellm avec utilisateurs dédiés.", + "text": "PostgreSQL 16 installé depuis repo officiel pgdg sur storage-01. PGDATA sur NVMe: /srv/data/postgres (bind-mount /home/data depuis 2026-05-29). Override systemd dans /etc/systemd/system/postgresql-16.service.d/pgdata.conf. Auth: peer en local, scram-sha-256 pour connexions réseau. Bases: hermes, litellm, n8n, openwebui avec utilisateurs dédiés.", }, { "category": "qdrant", @@ -97,7 +97,7 @@ KNOWLEDGE = [ { "category": "ansible", "tags": ["ansible", "vault", "secrets", "variables"], - "text": "Secrets Ansible dans group_vars/all/vault.yml: vault_anthropic_api_key, vault_pg_hermes_password, vault_pg_litellm_password. Rôles idempotents: common, gateway, dnsmasq, nfs_server, postgresql, qdrant, minio, litellm, hermes_agent, llama_server, rocm. Playbooks: storage-01.yml et gpu-01.yml.", + "text": "Secrets Ansible dans group_vars/all/vault.yml: vault_anthropic_api_key, vault_pg_hermes_password, vault_pg_litellm_password, vault_pg_n8n_password, vault_pg_openwebui_password, vault_postfix_relay_password. Rôles idempotents: common, gateway, dnsmasq, nfs_server, postgresql, qdrant, minio, litellm, hermes_agent, rag, alertmanager_webhook, postfix_relay, node_exporter, llama_server, rocm. Playbooks: storage-01.yml et gpu-01.yml.", }, { "category": "ansible",