Funk-lab/CLAUDE.md
alkatrazz 3cd374630d docs(incidents): 2026-05-29 — migration /srv/data RAID5 → NVMe (sdb ATA bus errors)
- Incident : sdb ATA bus errors sous charge I/O bloquaient le rsync en état D
- Résolution : sdb marqué (F) dans mdadm, rsync relancé (205 MB/s, 35s)
- Fix : version.info corrompu dans segment Qdrant RocksDB (0.D\0 → 0.6.0)
- Architecture : /srv/data bind-monté depuis /home/data (NVMe XFS, 801 GB libres)
- fstab mis à jour, tous les services opérationnels
- CLAUDE.md : venv bootstrap, make --tags invalide, incidents.md dans admin/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:59:13 +02:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Présentation du projet

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

  • 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
  • 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
  • Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local)
  • postfix_relay — SMTP sortant via Brevo (aliyesilkaya93@gmail.com)
  • WOL compute nodes non configuré (BIOS) — allumage manuel requis
  • ArgoCD bootstrap + workloads IA (Open WebUI, n8n)

Journal de progression : PROGRESS.mdprogress/YYYY-MM-DD.md


Architecture des machines

Machine IP OS Rôle
storage-01 192.168.10.1 / 192.168.1.200 AlmaLinux 9.7 Passerelle + bastion admin + données + Hermes Agent — hors cluster
compute-01 192.168.10.11 Talos v1.13 k8s control-plane
compute-02 192.168.10.12 Talos v1.13 k8s worker
compute-03 192.168.10.13 Talos v1.13 k8s worker
gpu-01 192.168.10.20 AlmaLinux 9.7 llama-server dédié (RX 6700XT, ROCm 7.x) — hors cluster

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)

Stack IaC — trois outils, trois cibles

Outil Cible Déclenchement
Ansible storage-01, gpu-01 (AlmaLinux) Push manuel : make apply-storage / make apply-gpu
talhelper compute-01/02/03 (Talos) Push manuel : talosctl apply-config
ArgoCD Workloads k8s Pull automatique depuis Git

Ansible

Commandes via Makefile (depuis la racine du repo)

python3 -m venv .venv  # créer le venv (une seule fois, avant make install)
make install        # pip install + ansible-galaxy (à faire une fois)
make ping           # vérifier la connectivité vers tous les hôtes
make check          # dry-run site.yml (storage-01 + gpu-01)
make apply          # appliquer site.yml complet
make apply-storage  # appliquer uniquement storage-01.yml
make apply-gpu      # appliquer uniquement gpu-01.yml
make vault-view     # lire le vault chiffré
make vault-edit     # éditer le vault (ouvre $EDITOR)
make vault-encrypt  # (ré)chiffrer vault.yml

Commandes directes (depuis ansible/)

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server

Les tags correspondent aux noms de rôles. Tous les rôles sont idempotents.

Inventaire et connectivité

  • storage-01 : accès direct SSH via 192.168.1.200 (WAN)
  • gpu-01 : accès via ProxyJump sur storage-01 (configuré dans inventory.yml)
  • Vault password dans .vault_pass (ignoré par git, chargé automatiquement via ansible.cfg)

Rôles — storage-01

hermes_user, common, gateway (nftables + NAT), dnsmasq, nfs_server, qdrant, postgresql, minio, litellm, hermes_agent, rag, node_exporter, alertmanager_webhook, postfix_relay

Rôles — gpu-01

hermes_user, common, rocm, nfs_client, llama_server, node_exporter

lm_studio est présent dans ansible/roles/ mais n'est plus utilisé dans aucun playbook.

Caveat premier déploiement — hermes_agent

Le rôle hermes_agent installe le binaire via curl | bash seulement si le binaire est absent. La configuration (.env, config.yaml) est ensuite conditionnelle à hermes_binary.stat.exists. Sur un hôte vierge, il faut deux runs consécutifs : le premier installe, le second configure.

Variables par hôte

ansible/host_vars/<hostname>/vars.yml contient les variables spécifiques à chaque machine :

  • storage-01 : UUID RAID5, exports NFS, chemins des services sous /srv/data
  • gpu-01 : chemin du modèle GGUF (llama_model_path), alias modèle, llama_ctx_size, montages NFS

Variables globales (non-secrètes) dans ansible/group_vars/all/vars.yml : admin_user, dns_domain, ntp_servers, cluster_network, wan_gateway.

Secrets Ansible Vault

Variables chiffrées dans ansible/group_vars/all/vault.yml :

  • vault_anthropic_api_key — clé Anthropic pour le profil brain
  • vault_pg_hermes_password, vault_pg_litellm_password
  • vault_postfix_relay_password — SMTP Brevo

talhelper (nœuds Talos)

cd talos/
talhelper genconfig                                        # régénérer configs nœuds
talhelper gencommand apply --node compute-02 | bash        # appliquer sur un nœud
talhelper gencommand bootstrap --node compute-01 | bash    # bootstrap (1ère fois seulement)
talhelper gencommand kubeconfig | bash                     # récupérer kubeconfig

talos/clusterconfig/ est généré — gitignored, ne pas committer. talos/talsecret.sops.yaml chiffré avec SOPS+age (clé dans .config/sops/, ignorée par git).


ArgoCD (workloads k8s)

Pattern "App of Apps" : modifier un manifest → git commit + git push → ArgoCD poll automatiquement (~3 min).

git add k8s/apps/open-webui/
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).


Stack IA — Hermes Agent

Profils

Profil Modèle Port Usage
funk-ai Qwen3-8B Q4_K_M GPU :1234 Agent principal (~35 tok/s)
system Qwen3-1.7B Q4_K_M CPU :1236 Calculs, formatage, résumés
monitor Qwen3-1.7B Q4_K_M CPU :1237 Analyse logs, métriques, état services
brain Claude Sonnet 4.6 API Anthropic Raisonnement complexe — facturé

Hermes écoute sur le port 8080 (gateway). Dashboard web sur le port 9119 (accessible uniquement depuis 192.168.1.10).

Switcher le modèle de Hermes à chaud

hermes-switch status    # modèle actuel de hermes-default
hermes-switch qwen      # → Qwen3-8B sur gpu-01 (local, gratuit)
hermes-switch claude    # → Claude Sonnet 4.6 via API Anthropic (facturé)

hermes-switch modifie /etc/litellm/config.yaml puis redémarre litellm. L'alias hermes-default dans LiteLLM est ce que Hermes consomme — changer cet alias ne nécessite pas de reconfigurer Hermes.

ask-agent (délégation inter-agents)

Binaire dans /usr/local/bin/ask-agent sur storage-01. Appel LiteLLM direct (:4000), réponse en ~2-5s.

Règle : ask-agent reçoit du texte, jamais des commandes shell. Toujours 2 étapes :

# Étape 1 — collecter les données
journalctl -u litellm -n 20 --no-pager --output=cat
# Étape 2 — passer le texte à ask-agent
ask-agent monitor "logs litellm : <résultat étape 1> — erreurs ?"

RAG — Documentation Funk

Qdrant collection funk-docs indexée depuis admin/ (284 chunks, score min 0.60).

rag-query "comment relancer dnsmasq ?"
rag-query "nftables port cluster configuration"
rag-query "hermes profils litellm modèles" --top 3

Scripts installés sur storage-01 : /usr/local/bin/rag-query, /usr/local/bin/rag-ingest

Après modification de admin/, re-déployer via la commande directe (voir section Ansible) :

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags rag

Le script admin/index_knowledge.py est l'ingesteur Python brut.

Hermes Skills — hermes-skills/

Versionnés dans ce repo, déployés par le rôle hermes_agent vers ~hermes/.

hermes-skills/
├── funk/
│   ├── agent-delegation/SKILL.md   # usage de ask-agent (2 étapes, agents dispo)
│   └── rag-docs/SKILL.md           # usage de rag-query
└── souls/                          # prompts système par profil
    ├── funk-ai.md    # profil principal (agent sur storage-01)
    ├── system.md     # profil calculs/formatage
    ├── monitor.md    # profil analyse logs
    └── brain.md      # profil Claude API

Base de connaissance — admin/

Documentation admin versionnée, indexée dans le RAG :

admin/
├── ia/           # hermes, litellm, llama_server, rocm, rag, alertmanager-webhook
├── infra/        # dnsmasq, nfs, réseau, ssh, email
├── install/      # procédures installation storage-01, gpu-01, k8s
├── k8s/          # argocd, talos, monitoring
├── ops/          # ansible, cluster, système
└── incidents.md  # historique des incidents matériel/service avec résolutions

Réseaux

  • CNI Flannel — Pods : 10.42.0.0/16
  • Services : 10.43.0.0/16
  • MetalLB pool : 192.168.10.200-230 (IP virtuelles Traefik/services)
  • DNS wildcard *.lab.local via dnsmasq → Traefik

nftables géré par le rôle gateway sur storage-01. Modifier ansible/roles/gateway/templates/nftables.conf.j2 puis :

ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags gateway

Contrainte GPU critique

La RX 6700XT (gfx1031) n'est pas officiellement supportée par ROCm. Variable obligatoire dans tous les services GPU :

HSA_OVERRIDE_GFX_VERSION=10.3.0

Gérée automatiquement par le rôle llama_server. ROCm tourne directement sur l'hôte AlmaLinux.


Gestion du cluster

Ces commandes sont des fonctions bash sur le poste perso (configurées dans ~/.bashrc) qui SSH sur storage-01 et invoquent /usr/local/bin/funk-cluster.

funk-status                 # état de tous les services (storage-01 + gpu-01 + k8s)
funk-start                  # WOL gpu-01 + démarrage services storage-01
funk-start --k8s            # idem + nœuds k8s (WOL → drain-aware boot)
funk-stop                   # arrêt propre gpu-01 + services
funk-stop --k8s             # idem + drain k8s → NFS check → talosctl shutdown

etcd backup (single control-plane → critique) :

talosctl etcd snapshot /path/to/backup.db --nodes compute-01

Conventions

  • 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
  • RAM compute-02/03 : toujours définir resources.requests + resources.limits (8 GB RAM, OOM-killer actif)