mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 17:54:41 +02:00
Ajout d'une section '## État vérifié' à chaque doc admin/ basée sur l'inspection read-only du cluster (systemctl, kubectl, nft, etc.). Aucune modification de service ni réécriture de documentation existante. Co-Authored-By: Hermes <hermes@funk.lab>
740 lines
No EOL
23 KiB
Markdown
740 lines
No EOL
23 KiB
Markdown
# Installation storage-01
|
|
|
|
Guide d'installation complet de storage-01 — de l'ISO AlmaLinux à la stack IA
|
|
complète (Hermes + LiteLLM + Qdrant + PostgreSQL). Écrit après installation réelle.
|
|
|
|
---
|
|
|
|
## Sommaire
|
|
|
|
1. [Rôle et architecture](#1-rôle-et-architecture)
|
|
2. [Installation AlmaLinux 9.7 (base OS)](#2-installation-almalinux-97-base-os)
|
|
3. [Configuration réseau initiale (manuelle)](#3-configuration-réseau-initiale-manuelle)
|
|
4. [Préparer le poste perso pour Ansible](#4-préparer-le-poste-perso-pour-ansible)
|
|
5. [Cloner le repo et initialiser Ansible](#5-cloner-le-repo-et-initialiser-ansible)
|
|
6. [Configurer le vault Ansible (secrets)](#6-configurer-le-vault-ansible-secrets)
|
|
7. [Jouer le playbook — phase par phase](#7-jouer-le-playbook--phase-par-phase)
|
|
8. [Vérifications post-install](#8-vérifications-post-install)
|
|
9. [Pièges et incidents rencontrés](#9-pièges-et-incidents-rencontrés)
|
|
|
|
---
|
|
|
|
## 1. Rôle et architecture
|
|
|
|
storage-01 est la machine centrale du lab. Elle n'est **pas dans le cluster Kubernetes**
|
|
— elle est la fondation sur laquelle tout le reste repose.
|
|
|
|
### Ce qu'elle fait
|
|
|
|
| Fonction | Détail |
|
|
|---|---|
|
|
| Passerelle NAT | Forward LAN cluster → internet, LAN domestique → LAN cluster |
|
|
| DHCP + DNS | dnsmasq pour le LAN cluster (`192.168.10.0/24`) et le wildcard `*.lab.local` |
|
|
| Stockage RAID5 | 4 disques SATA 1 TB → 2,7 TB utiles, montés sur `/srv/data` |
|
|
| NFS | Exports vers gpu-01 et le cluster k8s (`/srv/data/nfs/k8s`, `/srv/data/models`) |
|
|
| Hermes Agent | Agent IA autonome avec mémoire vectorielle |
|
|
| LiteLLM Proxy | Routage LLM : Qwen local (gpu-01) ↔ Claude API (Anthropic) |
|
|
| Qdrant | Base vectorielle pour la mémoire de Hermes (`hermes_memory`) |
|
|
| PostgreSQL 16 | Bases pour Hermes et LiteLLM |
|
|
|
|
### Interfaces réseau
|
|
|
|
| Interface | IP | Rôle |
|
|
|---|---|---|
|
|
| `enp4s0` | `192.168.1.200` | WAN — vers Freebox (LAN domestique `192.168.1.0/24`) |
|
|
| `enp6s0f3u2c2` | `192.168.10.1` | LAN cluster (`192.168.10.0/24`) — carte USB-Ethernet |
|
|
|
|
> `enp6s0f3u2c2` est une carte USB-Ethernet branchée en permanence — les noms d'interface sont contre-intuitifs.
|
|
|
|
### RAID5
|
|
|
|
4 disques SATA 1 TB (Seagate/WD) assemblés en RAID5 software Linux (`md0` ou `md127`).
|
|
- Capacité utile : ~2,7 TB
|
|
- Tolérance : 1 disque mort
|
|
- Point de montage : `/srv/data`
|
|
- ⚠️ `sdb` a généré des erreurs I/O après une coupure de courant — surveiller avec `smartctl`
|
|
|
|
---
|
|
|
|
## 2. Installation AlmaLinux 9.7 (base OS)
|
|
|
|
### Télécharger l'ISO
|
|
|
|
AlmaLinux 9.7 minimal (pas de desktop) :
|
|
`https://repo.almalinux.org/almalinux/9/isos/x86_64/`
|
|
|
|
Flasher sur clé USB :
|
|
```bash
|
|
sudo dd if=AlmaLinux-9.7-x86_64-minimal.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
|
```
|
|
|
|
### Paramètres d'installation (Anaconda)
|
|
|
|
Dans l'installeur graphique Anaconda :
|
|
|
|
**Langue / clavier** : Français ou English — peu importe, Ansible configure le reste.
|
|
|
|
**Disque système** : sélectionner le NVMe interne uniquement.
|
|
- Ne pas sélectionner les disques SATA (RAID5) — ils sont gérés manuellement.
|
|
- Partitionnement automatique convient (LVM sur NVMe).
|
|
|
|
**Réseau** : configurer uniquement l'interface WAN (`enp4s0`) pendant l'install :
|
|
- IP statique : `192.168.1.200/24`
|
|
- Gateway : `192.168.1.254` (Freebox)
|
|
- DNS : `1.1.1.1`
|
|
- Hostname : `storage-01`
|
|
|
|
L'interface LAN (`enp6s0f3u2c2`) sera configurée par Ansible.
|
|
|
|
**Utilisateurs** :
|
|
- Root : définir un mot de passe temporaire (sera désactivé par Ansible)
|
|
- Créer un premier utilisateur `ansible` (ou laisser Ansible le créer — dans ce cas se connecter en root pour la première exécution)
|
|
|
|
### Premier boot
|
|
|
|
Après l'installation, vérifier que la machine est joignable depuis le poste perso :
|
|
```bash
|
|
ping 192.168.1.200
|
|
ssh root@192.168.1.200
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Configuration réseau initiale (manuelle)
|
|
|
|
Avant de lancer Ansible, il faut que storage-01 soit SSH-accessible et que les deux
|
|
interfaces réseau soient opérationnelles. Ces opérations sont faites une seule fois
|
|
depuis la console ou root SSH.
|
|
|
|
### Configurer l'interface LAN (USB-Ethernet)
|
|
|
|
```bash
|
|
# Se connecter en root
|
|
ssh root@192.168.1.200
|
|
|
|
# Identifier la carte USB-Ethernet (généralement enp6s0f3u2c2 ou similaire)
|
|
nmcli device status
|
|
ip link show
|
|
|
|
# Créer la connexion NetworkManager
|
|
nmcli connection add type ethernet \
|
|
ifname enp6s0f3u2c2 \
|
|
con-name lan-cluster \
|
|
ipv4.method manual \
|
|
ipv4.addresses 192.168.10.1/24 \
|
|
ipv4.never-default yes \
|
|
connection.autoconnect yes
|
|
|
|
nmcli connection up lan-cluster
|
|
```
|
|
|
|
Vérifier :
|
|
```bash
|
|
ip addr show enp6s0f3u2c2
|
|
# → inet 192.168.10.1/24
|
|
```
|
|
|
|
### Activer l'IP forwarding
|
|
|
|
```bash
|
|
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-forwarding.conf
|
|
sysctl -p /etc/sysctl.d/99-forwarding.conf
|
|
```
|
|
|
|
> Ansible gère aussi l'IP forwarding, mais l'activer maintenant permet aux nœuds
|
|
> Talos de sortir sur internet dès que dnsmasq et nftables seront en place.
|
|
|
|
### Ajouter gpu-01 dans /etc/hosts (si gpu-01 sera installé avant dnsmasq)
|
|
|
|
```bash
|
|
echo '192.168.10.20 gpu-01' >> /etc/hosts
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Préparer le poste perso pour Ansible
|
|
|
|
Tout Ansible se lance depuis le **poste perso** (ou storage-01 une fois qu'il est
|
|
opérationnel — voir section suivante).
|
|
|
|
### Installer Python et créer le venv
|
|
|
|
```bash
|
|
# Python 3 doit être disponible
|
|
python3 --version
|
|
|
|
# Créer le venv à la racine du repo
|
|
cd /mnt/nvme0n1/nvme0n1p3/Projets/lab
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
|
|
# Installer ansible-core
|
|
pip install -r requirements.txt
|
|
# ansible-core>=2.17,<2.18
|
|
```
|
|
|
|
### Installer les collections Ansible
|
|
|
|
```bash
|
|
.venv/bin/ansible-galaxy collection install -r ansible/requirements.yml
|
|
```
|
|
|
|
### Générer une clé SSH pour Ansible
|
|
|
|
```bash
|
|
# Si pas encore de clé ed25519
|
|
ssh-keygen -t ed25519 -C "ansible@poste-perso" -f ~/.ssh/id_ed25519
|
|
|
|
# Copier la clé publique vers storage-01
|
|
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@192.168.1.200
|
|
```
|
|
|
|
Ansible utilisera le compte `ansible` (créé par le rôle `common`) — mais pour la
|
|
première exécution, il se connecte en root.
|
|
|
|
### Configurer ~/.ssh/config
|
|
|
|
```
|
|
Host s01
|
|
HostName 192.168.1.200
|
|
User ansible
|
|
IdentityFile ~/.ssh/id_ed25519
|
|
|
|
Host g01
|
|
HostName 192.168.10.20
|
|
User ansible
|
|
ProxyJump s01
|
|
IdentityFile ~/.ssh/id_ed25519
|
|
StrictHostKeyChecking no
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Cloner le repo et initialiser Ansible
|
|
|
|
```bash
|
|
git clone git@github.com:Alkatrazz24/Funk-lab.git
|
|
cd Funk-lab
|
|
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install -r requirements.txt
|
|
.venv/bin/ansible-galaxy collection install -r ansible/requirements.yml
|
|
```
|
|
|
|
### Structure Ansible du repo
|
|
|
|
```
|
|
ansible/
|
|
├── inventory.yml # hosts + groupes
|
|
├── playbooks/
|
|
│ ├── site.yml # import storage-01.yml + gpu-01.yml
|
|
│ ├── storage-01.yml # playbook storage-01
|
|
│ └── gpu-01.yml # playbook gpu-01
|
|
├── group_vars/
|
|
│ ├── all/
|
|
│ │ ├── vars.yml # variables globales (dns_domain, cluster_network...)
|
|
│ │ └── vault.yml # secrets chiffrés (Ansible Vault)
|
|
│ └── gateway/
|
|
│ └── vars.yml # interfaces réseau storage-01
|
|
├── host_vars/
|
|
│ ├── storage-01/
|
|
│ │ └── vars.yml # RAID uuid, NFS exports, chemins services
|
|
│ └── gpu-01/
|
|
│ └── vars.yml # modèle llama-server, montages NFS
|
|
└── roles/
|
|
├── common/ # hostname, timezone, EPEL, user ansible, SSH hardenin
|
|
├── gateway/ # nftables + NAT + IP forwarding
|
|
├── dnsmasq/ # DNS lab.local + DHCP cluster
|
|
├── nfs_server/ # RAID5 fstab + exports NFS
|
|
├── postgresql/ # PostgreSQL 16 sur RAID5
|
|
├── qdrant/ # Qdrant vectordb sur RAID5
|
|
├── litellm/ # LiteLLM proxy + hermes-switch
|
|
├── hermes_agent/ # Hermes Agent + Dashboard
|
|
├── rocm/ # ROCm AMD pour gpu-01
|
|
├── llama_server/ # llama-server sur gpu-01
|
|
└── nfs_client/ # montages NFS automatiques (gpu-01)
|
|
```
|
|
|
|
### Variables globales (`group_vars/all/vars.yml`)
|
|
|
|
```yaml
|
|
dns_domain: lab.local
|
|
ntp_servers:
|
|
- 0.fr.pool.ntp.org
|
|
- 1.fr.pool.ntp.org
|
|
cluster_network: 192.168.10.0/24
|
|
wan_gateway: 192.168.1.254
|
|
```
|
|
|
|
### Variables gateway (`group_vars/gateway/vars.yml`)
|
|
|
|
```yaml
|
|
wan_interface: enp4s0 # NIC PCIe — vers Freebox
|
|
lan_interface: enp6s0f3u2c2 # NIC USB — vers LAN cluster
|
|
wan_ip: 192.168.1.200
|
|
lan_ip: 192.168.10.1
|
|
```
|
|
|
|
### Variables storage-01 (`host_vars/storage-01/vars.yml`)
|
|
|
|
```yaml
|
|
ansible_host: 192.168.1.200
|
|
raid_uuid: "3add8360-fa01-47eb-8aa1-0e84bacbbc15"
|
|
raid_mount: /srv/data
|
|
nfs_exports:
|
|
- path: /srv/data/nfs/k8s
|
|
network: "{{ cluster_network }}"
|
|
options: "rw,sync,no_subtree_check,no_root_squash"
|
|
- path: /srv/data/models
|
|
network: "{{ cluster_network }}"
|
|
options: "rw,sync,no_subtree_check,no_root_squash"
|
|
hermes_data_dir: /srv/data/hermes
|
|
postgres_data_dir: /srv/data/postgres
|
|
qdrant_data_dir: /srv/data/qdrant
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Configurer le vault Ansible (secrets)
|
|
|
|
Le vault contient les mots de passe et l'API key Anthropic. Il est chiffré dans git.
|
|
|
|
### Fichier mot de passe vault
|
|
|
|
```bash
|
|
# Créer le fichier de mot de passe (ne jamais le committer — dans .gitignore)
|
|
echo "ton-mot-de-passe-vault" > .vault_pass
|
|
chmod 600 .vault_pass
|
|
```
|
|
|
|
`ansible.cfg` pointe automatiquement dessus :
|
|
```ini
|
|
[defaults]
|
|
vault_password_file = .vault_pass
|
|
```
|
|
|
|
### Créer le vault avec les secrets
|
|
|
|
```bash
|
|
cd ansible/
|
|
../.venv/bin/ansible-vault create group_vars/all/vault.yml
|
|
```
|
|
|
|
Contenu du vault :
|
|
```yaml
|
|
vault_anthropic_api_key: "sk-ant-..."
|
|
vault_pg_hermes_password: "mot-de-passe-fort"
|
|
vault_pg_litellm_password: "mot-de-passe-fort"
|
|
```
|
|
|
|
Pour modifier plus tard :
|
|
```bash
|
|
make vault-edit
|
|
# ou
|
|
cd ansible && ../.venv/bin/ansible-vault edit group_vars/all/vault.yml
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Jouer le playbook — phase par phase
|
|
|
|
Il est recommandé d'appliquer les rôles dans l'ordre ci-dessous, par petits
|
|
blocs, pour valider chaque couche avant de passer à la suivante.
|
|
|
|
### Test de connectivité
|
|
|
|
```bash
|
|
make ping
|
|
# ou
|
|
cd ansible && ../.venv/bin/ansible all -m ping
|
|
```
|
|
|
|
Sortie attendue :
|
|
```
|
|
storage-01 | SUCCESS => {"ping": "pong"}
|
|
```
|
|
|
|
> gpu-01 échouera si pas encore installé — normal.
|
|
|
|
### Phase 1 — Base OS
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags common
|
|
```
|
|
|
|
Le rôle `common` fait :
|
|
- Hostname → `storage-01`
|
|
- Timezone → Europe/Paris
|
|
- EPEL activé
|
|
- `dnf update -y` (tous les packages à jour)
|
|
- Packages de base : `vim`, `curl`, `wget`, `git`, `htop`, `tmux`, `rsync`, `lsof`, `net-tools`, `bind-utils`
|
|
- Création user `ansible` avec sudo NOPASSWD
|
|
- Clé SSH publique du poste perso déposée dans `~/.ssh/authorized_keys`
|
|
- SSH durci : `PermitRootLogin no`, `PasswordAuthentication no`, `X11Forwarding no`
|
|
|
|
Après cette étape, les connexions root ne fonctionnent plus — utiliser `ssh s01`.
|
|
|
|
### Phase 2 — Passerelle (nftables + NAT)
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags gateway
|
|
```
|
|
|
|
Le rôle `gateway` fait :
|
|
- Déploie `/etc/nftables.conf` depuis le template (flush + rebuild complet)
|
|
- Active IP forwarding (`sysctl net.ipv4.ip_forward=1`)
|
|
- Active et redémarre `nftables`
|
|
|
|
Vérifier que le NAT fonctionne depuis le LAN cluster :
|
|
```bash
|
|
# Depuis une machine du cluster (si déjà installée)
|
|
ssh 192.168.10.11 "curl -s https://ifconfig.me"
|
|
```
|
|
|
|
### Phase 3 — DNS + DHCP (dnsmasq)
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags dnsmasq
|
|
```
|
|
|
|
Le rôle `dnsmasq` fait :
|
|
- Installe `dnsmasq`
|
|
- Déploie la config depuis template : DNS `lab.local`, DHCP `192.168.10.50-99`, baux statiques MACs
|
|
- Wildcard `*.lab.local → 192.168.10.200` (Traefik)
|
|
- Override systemd : dnsmasq attend que l'interface USB-Ethernet soit prête avant de démarrer
|
|
|
|
Tester depuis le poste perso :
|
|
```bash
|
|
dig @192.168.10.1 storage-01.lab.local +short
|
|
# → 192.168.10.1
|
|
|
|
dig @192.168.10.1 test.lab.local +short
|
|
# → 192.168.10.200 (wildcard)
|
|
```
|
|
|
|
> Sans l'override systemd, dnsmasq démarre avant que `enp6s0f3u2c2` (USB-Ethernet)
|
|
> soit disponible → `bind(): Address not available` → service en échec au boot.
|
|
|
|
### Phase 4 — NFS + RAID5
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags nfs_server
|
|
```
|
|
|
|
Le rôle `nfs_server` fait :
|
|
- Installe `nfs-utils` et `mdadm`
|
|
- Persiste la config RAID dans `/etc/mdadm.conf` (pour survie aux reboots)
|
|
- Ajoute `/srv/data` dans `/etc/fstab` avec `_netdev 0 0`
|
|
- Crée les répertoires d'export (`/srv/data/nfs/k8s`, `/srv/data/models`)
|
|
- Déploie `/etc/exports` et recharge `nfs-server`
|
|
|
|
Vérifier :
|
|
```bash
|
|
ssh s01 "cat /proc/mdstat" # RAID doit être [4/4] [UUUU]
|
|
ssh s01 "sudo exportfs -v" # doit lister les deux exports
|
|
ssh s01 "df -h /srv/data" # doit afficher ~2.7 TB
|
|
```
|
|
|
|
> **UUID du RAID** : récupérer avec `sudo blkid | grep md` sur storage-01 et
|
|
> mettre à jour `host_vars/storage-01/vars.yml` avant de jouer ce rôle.
|
|
|
|
### Phase 5 — PostgreSQL 16
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags postgresql
|
|
```
|
|
|
|
Le rôle `postgresql` fait :
|
|
- Installe le repo officiel PGDG (EL9)
|
|
- Désactive le module AppStream par défaut (`dnf module disable postgresql`)
|
|
- Installe `postgresql16-server` et `python3-psycopg2`
|
|
- Crée `/srv/data/postgres` sur le RAID5 et le configure comme `PGDATA`
|
|
- Override systemd pour pointer sur le bon `PGDATA`
|
|
- `initdb` si la base n'existe pas encore
|
|
- Crée les bases `hermes` et `litellm` et leurs users avec les mots de passe du vault
|
|
|
|
Vérifier :
|
|
```bash
|
|
ssh s01 "sudo -u postgres psql -c '\l'"
|
|
# doit lister : hermes, litellm, postgres, template0, template1
|
|
```
|
|
|
|
### Phase 6 — Qdrant
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags qdrant
|
|
```
|
|
|
|
Le rôle `qdrant` fait :
|
|
- Télécharge le binaire Qdrant (release GitHub)
|
|
- Crée un user système `qdrant`
|
|
- Configure le stockage dans `/srv/data/qdrant`
|
|
- Déploie `qdrant.service` (bind `0.0.0.0:6333`, gRPC `6334`)
|
|
- Active et démarre le service
|
|
|
|
Vérifier :
|
|
```bash
|
|
ssh s01 "curl -s http://localhost:6333/healthz"
|
|
# → {"result":"ok","status":"ok","time":0.00...}
|
|
```
|
|
|
|
### Phase 7 — LiteLLM Proxy
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags litellm
|
|
```
|
|
|
|
Le rôle `litellm` fait :
|
|
- Installe `litellm` via pip dans un venv dédié
|
|
- Déploie `/etc/litellm/config.yaml` avec les modèles (Qwen local + Claude)
|
|
- Configure `master_key: lm-studio` (aligné avec `LM_API_KEY=lm-studio` dans Hermes)
|
|
- Déploie `litellm.service` (bind `127.0.0.1:4000` — jamais exposé sur le réseau)
|
|
- Déploie le script `hermes-switch` dans `/usr/local/bin/`
|
|
|
|
Config LiteLLM (résumé) :
|
|
```yaml
|
|
model_list:
|
|
- model_name: hermes-default
|
|
litellm_params:
|
|
model: openai/qwen3-8b
|
|
api_base: http://192.168.10.20:1234/v1
|
|
api_key: lm-studio
|
|
|
|
- model_name: claude-sonnet-4-6
|
|
litellm_params:
|
|
model: anthropic/claude-sonnet-4-6
|
|
api_key: "{{ vault_anthropic_api_key }}"
|
|
|
|
general_settings:
|
|
master_key: lm-studio
|
|
```
|
|
|
|
Vérifier :
|
|
```bash
|
|
ssh s01 "curl -s http://localhost:4000/health -H 'Authorization: Bearer lm-studio'"
|
|
```
|
|
|
|
### Phase 8 — Hermes Agent
|
|
|
|
```bash
|
|
cd ansible && ../.venv/bin/ansible-playbook playbooks/storage-01.yml --tags hermes_agent
|
|
```
|
|
|
|
Le rôle `hermes_agent` fait :
|
|
- Crée un user système `hermes` avec home sur le RAID5 (`/srv/data/hermes`)
|
|
- Installe Hermes via son installer officiel dans `/opt/hermes`
|
|
- Crée le venv Python (`/srv/data/hermes/hermes-agent/venv/`) avec `qdrant-client`
|
|
- Déploie `~hermes/.config/hermes/config.yaml` :
|
|
- `model.provider: lmstudio`
|
|
- `model.base_url: http://127.0.0.1:4000/v1`
|
|
- `model.name: hermes-default`
|
|
- Déploie `hermes-agent.service` et `hermes-dashboard.service`
|
|
- Déploie `/usr/local/bin/hermes-tui` (wrapper pour TUI interactif)
|
|
- Permissions `/opt/hermes` → `711` (traversable, non listable)
|
|
|
|
Vérifier :
|
|
```bash
|
|
ssh s01 "sudo systemctl status hermes-agent hermes-dashboard"
|
|
```
|
|
|
|
Dashboard accessible depuis le poste perso (restreint à `192.168.1.10`) :
|
|
```
|
|
http://192.168.1.200:9119
|
|
```
|
|
|
|
TUI interactif (doit être lancé sous le compte hermes) :
|
|
```bash
|
|
ssh storage-01
|
|
sudo -i -u hermes
|
|
cd /srv/data/hermes
|
|
hermes --tui
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Vérifications post-install
|
|
|
|
### Checklist complète
|
|
|
|
```bash
|
|
# 1. Réseau
|
|
ping 192.168.10.20 # gpu-01 joignable
|
|
ssh g01 "curl -s https://ifconfig.me" # NAT fonctionnel
|
|
dig @192.168.10.1 storage-01.lab.local +short # DNS lab.local
|
|
|
|
# 2. RAID
|
|
ssh s01 "cat /proc/mdstat" # [4/4] [UUUU]
|
|
ssh s01 "df -h /srv/data" # ~2.7 TB monté
|
|
|
|
# 3. NFS
|
|
ssh s01 "sudo exportfs -v" # exports actifs
|
|
|
|
# 4. PostgreSQL
|
|
ssh s01 "sudo -u postgres psql -c '\l'" # bases hermes + litellm présentes
|
|
|
|
# 5. Qdrant
|
|
ssh s01 "curl -s http://localhost:6333/healthz" | python3 -m json.tool
|
|
|
|
# 6. LiteLLM
|
|
ssh s01 "curl -s http://localhost:4000/health -H 'Authorization: Bearer lm-studio'"
|
|
|
|
# 7. Hermes
|
|
ssh s01 "sudo systemctl is-active hermes-agent hermes-dashboard"
|
|
|
|
# 8. funk-cluster (script de gestion global)
|
|
ssh s01 "sudo /usr/local/bin/funk-cluster status"
|
|
```
|
|
|
|
### Services actifs attendus
|
|
|
|
```bash
|
|
ssh s01 "sudo systemctl list-units --type=service --state=running | grep -E 'dnsmasq|nfs|postgres|qdrant|litellm|hermes|nftables'"
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Pièges et incidents rencontrés
|
|
|
|
---
|
|
|
|
### ❌ dnsmasq ne démarre pas au boot — "Address not available"
|
|
|
|
**Symptôme** : `systemctl status dnsmasq` → `bind(): Address not available`.
|
|
Storage-01 rebooté → dnsmasq en `failed`, plus de DHCP pour le cluster.
|
|
|
|
**Cause** : dnsmasq tentait de binder sur `192.168.10.1` (`enp6s0f3u2c2`, USB-Ethernet)
|
|
avant que cette interface soit disponible. L'USB-Ethernet est détectée plus tard que
|
|
les NIC PCIe dans la séquence de boot.
|
|
|
|
**Fix** : Un override systemd dans `/etc/systemd/system/dnsmasq.service.d/wait-lan.conf` :
|
|
```ini
|
|
[Unit]
|
|
After=network-online.target sys-subsystem-net-devices-enp6s0f3u2c2.device
|
|
Wants=network-online.target sys-subsystem-net-devices-enp6s0f3u2c2.device
|
|
```
|
|
|
|
Géré par le rôle Ansible `dnsmasq` — ne pas modifier manuellement.
|
|
|
|
---
|
|
|
|
### ❌ Boucle fsck au boot après coupure de courant
|
|
|
|
**Symptôme** : Boot bloqué sur `emergency mode`, impossible de saisir le mot de passe root
|
|
(messages d'erreur défilants), le RAID `/dev/md0` ne monte plus.
|
|
|
|
**Cause** : Coupure de courant → corruption du filesystem XFS sur `almalinux-root` (NVMe).
|
|
systemd tentait de monter `/srv/data` (RAID5) avant que le RAID soit assemblé, créant
|
|
une boucle bloquante.
|
|
|
|
**Résolution** (procédure complète dans `admin/incidents.md`) :
|
|
1. Shell via GRUB (`init=/bin/bash` dans la ligne kernel)
|
|
2. `lvm vgchange -ay` pour activer le LVM
|
|
3. `xfs_repair /dev/mapper/almalinux-root` (pas `fsck` — XFS a son propre outil)
|
|
4. Commenter `/srv/data` dans `/etc/fstab` temporairement pour booter
|
|
5. Après boot : corriger fstab → ajouter `_netdev 0 0` pour `/srv/data`
|
|
|
|
**Fix permanent dans fstab** :
|
|
```
|
|
UUID=3add8360-... /srv/data ext4 defaults,noatime,nodiratime,_netdev 0 0
|
|
```
|
|
- `_netdev` : attend que les périphériques soient prêts (RAID assemblé) avant de monter
|
|
- `0 0` : désactive le `fsck` automatique au boot (le RAID a sa propre intégrité)
|
|
|
|
Géré par le rôle Ansible `nfs_server`. Vérifier que ces options sont bien présentes.
|
|
|
|
---
|
|
|
|
### ❌ hermes --tui "gateway error" si lancé sous le mauvais compte
|
|
|
|
**Symptôme** : `hermes --tui` lancé depuis le compte `ansible` retourne des "gateway error"
|
|
en boucle. TUI inutilisable.
|
|
|
|
**Cause** : Hermes charge sa configuration depuis `$HERMES_HOME`, qui doit pointer vers
|
|
`/srv/data/hermes`. Lancé sous `ansible`, les variables d'environnement ne sont pas
|
|
correctes et `hermes` cherche sa config dans `~ansible/.config/hermes/` qui n'existe pas.
|
|
|
|
**Fix** : Toujours lancer le TUI sous le compte `hermes` :
|
|
```bash
|
|
ssh storage-01
|
|
sudo -i -u hermes
|
|
cd /srv/data/hermes
|
|
hermes --tui
|
|
```
|
|
|
|
Ou via le wrapper `/usr/local/bin/hermes-tui` qui gère automatiquement l'environnement.
|
|
|
|
---
|
|
|
|
### ❌ qdrant-client : méthode search() supprimée en v1.14+
|
|
|
|
**Symptôme** : Scripts Python Hermes qui appellent Qdrant échouent avec
|
|
`AttributeError: 'AsyncQdrantClient' object has no attribute 'search'`.
|
|
|
|
**Cause** : `qdrant-client` v1.14+ a supprimé la méthode `search()`.
|
|
Elle est remplacée par `query_points()`.
|
|
|
|
**Fix** : Mettre à jour les scripts pour utiliser `query_points()`.
|
|
Le rôle Ansible `hermes_agent` installe la version compatible — ne pas faire
|
|
`pip install qdrant-client` manuellement sans vérifier la version.
|
|
|
|
---
|
|
|
|
### ❌ LM_API_KEY absent → Hermes ne peut pas appeler LiteLLM
|
|
|
|
**Symptôme** : Hermes démarre mais toutes les requêtes vers LiteLLM échouent avec 401.
|
|
|
|
**Cause** : Hermes utilise `LM_API_KEY` comme Bearer token. LiteLLM attend `master_key`.
|
|
Les deux doivent avoir la même valeur. Si `LM_API_KEY` n'est pas défini dans
|
|
l'environnement du service systemd, Hermes envoie un token vide ou incorrect.
|
|
|
|
**Fix** : Le service `hermes-agent.service` doit avoir dans sa section `[Service]` :
|
|
```ini
|
|
Environment=LM_API_KEY=lm-studio
|
|
```
|
|
|
|
La valeur `lm-studio` est une valeur magique alignée avec `master_key: lm-studio`
|
|
dans la config LiteLLM. Géré par le rôle Ansible.
|
|
|
|
---
|
|
|
|
### ❌ hermes -z ne fonctionne pas depuis n'importe où
|
|
|
|
**Symptôme** : `hermes -z "question"` depuis `/home/ansible` → erreur de chemin,
|
|
Hermes ne trouve pas son `.git`.
|
|
|
|
**Cause** : Hermes remonte l'arborescence à la recherche d'un `.git` pour localiser
|
|
sa config de projet. Si lancé depuis un répertoire sans `.git` en amont, il échoue.
|
|
|
|
**Fix** : Toujours lancer depuis `/srv/data/hermes` :
|
|
```bash
|
|
cd /srv/data/hermes && hermes -z "ta question"
|
|
```
|
|
|
|
---
|
|
|
|
### Résumé des points d'attention
|
|
|
|
| Sujet | Règle |
|
|
|---|---|
|
|
| dnsmasq au boot | Override systemd obligatoire pour USB-Ethernet |
|
|
| fstab `/srv/data` | `_netdev 0 0` — jamais `0 2` |
|
|
| TUI Hermes | Toujours sous le compte `hermes`, depuis `/srv/data/hermes` |
|
|
| `hermes -z` | Toujours depuis `/srv/data/hermes` |
|
|
| LM_API_KEY | Doit valoir `lm-studio` (= `master_key` LiteLLM) |
|
|
| qdrant-client | Utiliser `query_points()` pas `search()` (supprimé en v1.14+) |
|
|
| RAID sdb | Surveiller régulièrement avec `smartctl -a /dev/sdb` |
|
|
| UUID RAID | Vérifier `host_vars/storage-01/vars.yml` avant de jouer `nfs_server` |
|
|
| Vault | `.vault_pass` ne doit jamais être commité — dans `.gitignore` |
|
|
|
|
|
|
---
|
|
|
|
## État vérifié — 2026-06-05
|
|
|
|
- Services : tous actifs (aucun unité échoué)
|
|
- OS : AlmaLinux 9.7, kernel 5.14.0-687.5.3.el9_8.x86_64
|
|
- Stockage : /srv/data (2.7T) monté, /home (845G), /boot (960M)
|
|
- Mémoire : 29Gi totale, 2.5Gi utilisée
|
|
- Divergences : Aucune divergence notée entre la documentation et l'état réel
|
|
- Éléments opérationnels : Aucun pod crashé rapporté, export NFS non vérifié dans les commandes actuelles |