# Incidents — Historique et résolutions

## Index

| Date | Composant | Résumé | Sévérité |
|---|---|---|---|
| 2026-05-29 | RAID5 / sdb + storage-01 | sdb ATA bus errors sous I/O — migration /srv/data du RAID vers NVMe | Important |
| 2026-05-27 | RAID5 / sdd | Disque sdd défaillant (44 secteurs morts) — corruptions multiples en cascade | Critique |
| 2026-05-14 | Grafana | OOMKilled — limite 256Mi trop serrée pour Grafana 13 | Warning |
| 2026-05-13 | AlertManager | Receiver "null" non défini — ReconciliationFailed | Warning |
| 2026-05-13 | llama-server | `/metrics` retourne 501 Not Implemented — flag `--metrics` absent | Warning |
| 2026-05-13 | nftables storage-01 | Règle monitoring ajoutée après le drop — paquets bloqués | Warning |
| 2026-05-13 | storage-01 | Corruption EXT4 + réinstallation hermes-agent | Critique |
| 2026-05-11 | storage-01 | Boucle fsck après coupure de courant — XFS corrompu | Critique |

---

## 2026-05-29 — Migration /srv/data : RAID5 → NVMe (sdb ATA bus errors)

### Contexte

Suite à l'incident du 2026-05-27 (sdd retiré), le RAID5 tournait déjà en dégradé `[4/3] [UUU_]`. Décision prise de migrer `/srv/data` du RAID vers le LV NVMe (`almalinux-home`, 845 GB XFS) pour sortir les données d'un RAID en mauvais état.

### Symptômes déclencheurs

Lors du lancement du rsync de migration, le processus se bloque en état `D` (uninterruptible sleep) :

```
dmesg:
[ 1341.231068] sd 1:0:0:0: [sdb] FAILED Result: hostbyte=DID_OK
[ 1341.231082] I/O error, dev sdb, sector 582656 op 0x0:(READ)
[ 1358.639065] ata2.00: res 40/00:ff:ff:00:00/... Emask 0x50 (ATA bus error)
[ 1451.541751] md/raid:md0: Disk failure on sdb2, disabling device.
```

`sdb` génère des ATA bus errors sous charge I/O soutenue (câble SATA ou port instable). SMART passe (`PASSED`, aucun secteur défaillant) mais `Raw_Read_Error_Rate = 124014439` — problème d'interface, pas de plateau.

### Résolution — migration complète

**Étape 1 — Stopper les services**
```bash
sudo systemctl stop hermes-agent hermes-dashboard litellm qdrant postgresql-16 alertmanager-webhook
```

**Étape 2 — Préparer la destination NVMe**
```bash
sudo mkdir -p /home/data
sudo mount --bind /home/data /srv/data-new   # vérification préalable
```

**Étape 3 — Gérer sdb (blocage rsync)**

Le premier rsync (avec sdb actif) se bloque en état `D`. Solution : marquer sdb comme défaillant pour que le RAID lise depuis sda/sdc/sdd uniquement.

```bash
sudo kill <pid_rsync_bloqué>
sudo mdadm --manage /dev/md0 --fail /dev/sdb2
# → [4/3] [U_UU] — sdb hors circuit
```

**Étape 4 — Rsync (RAID → NVMe)**
```bash
sudo nohup rsync -aHX --info=progress2 /srv/data/ /home/data/ > /tmp/rsync-migration.log 2>&1 &
# Durée : ~35s — 205 MB/s — 5106 fichiers — exit code 23 (xattr SELinux non critiques)
```

**Étape 5 — Corriger la corruption Qdrant**

Un `version.info` de segment RocksDB copié depuis le RAID avec sdb défaillant était corrompu :
```bash
sudo cat /srv/data/qdrant/storage/collections/funk-docs/0/segments/784eab54-.../version.info
# → 0.D\0  (corrompu — devrait être 0.6.0)
printf '0.6.0' | sudo tee /srv/data/qdrant/storage/collections/funk-docs/0/segments/784eab54-.../version.info
```

**Étape 6 — Basculer /srv/data vers NVMe**
```bash
sudo umount /srv/data          # démonte le RAID
sudo umount /srv/data-new      # démonte le mount intermédiaire
sudo mount --bind /home/data /srv/data
```

**Étape 7 — Mettre à jour fstab**
```bash
# Remplacer l'entrée RAID par le bind mount
# Avant : UUID=3add8360-... /srv/data ext4 defaults,noatime,nodiratime 0 0
# Après :
echo '/home/data /srv/data none bind 0 0' | sudo tee -a /etc/fstab
# (commenter l'ancienne ligne UUID=3add8360)
```

**Étape 8 — Redémarrer les services**
```bash
sudo systemctl start postgresql-16 qdrant litellm hermes-agent hermes-dashboard alertmanager-webhook
```

### État post-migration

| Composant | Avant | Après |
|---|---|---|
| `/srv/data` | RAID5 ext4 `/dev/md0` (dégradé) | NVMe XFS bind-mount `/home/data` |
| Espace disponible | 2.6 TB RAID (dégradé, risqué) | 801 GB NVMe (sûr) |
| RAID5 md0 | `[4/3] [UUU_]` actif | `[4/3] [U_UU]` — sdb `(F)`, **plus monté** |
| Tous les services | Stoppés | ✅ Actifs |

### Actions restantes

- **Retirer sdb physiquement** (ou le supprimer du RAID) :
  ```bash
  sudo mdadm --manage /dev/md0 --remove /dev/sdb2
  ```
- **Décider du sort du RAID** : les données sont sur NVMe. Le RAID peut être démonté définitivement ou conservé pour une future migration vers un NAS.
- Vérifier câble SATA de sdb (ATA bus errors = câble ou port, pas plateau).

---

## 2026-05-27 — RAID5 : défaillance sdd — corruptions multiples en cascade

### Symptômes

Au démarrage du cluster (`funk-start --k8s`) :

- Qdrant en `CrashLoopBackOff` : `RocksDB open error: Corruption: no log_file_number ... MANIFEST-000033 may be corrupted`
- Grafana en `CrashLoopBackOff` : `pq: relation mapping file "base/24737/pg_filenode.map" contains invalid data`
- LiteLLM DB : `ERROR: invalid page in block 0 of relation base/16385/2838`
- Prometheus en `CrashLoopBackOff` : `corrupted block 01KSGFC9WP7STGTXWDNECY2FS0: read symbols: invalid checksum`
- Docker registry retournant 500 sur toutes les requêtes : `filesystem: stat ... data: input/output error`
- `sacrifice-web` en `ImagePullBackOff` (registry inaccessible)

Pattern observé : corruptions récurrentes tous les 3-4 démarrages depuis ~7 semaines.

### Cause racine

Disque `sdd` (WDC WD10EALX-009BA0, S/N WD-WCATR9001223) en défaillance progressive :

```
Current_Pending_Sector  :  44   ← secteurs illisibles, non réalloués
Offline_Uncorrectable   :  44   ← secteurs confirmés irrécupérables
Power_On_Hours          : 52816  ← 6 ans d'utilisation (MTBF constructeur = 5 ans)
```

Historique des self-tests SMART (tous en échec) :
```
#1  Short   LBA 1555355176  read failure  (nouveau secteur mort)
#2  Short   LBA 1413976229  read failure
#4  Extended LBA 1413976229  read failure  (premier secteur mort, découvert il y a ~49 jours)
```

**Mécanisme de corruption silencieuse :**

Le RAID dit `[4/4] [UUUU]` et `State: clean` car sdd ne retourne pas d'erreur au moment de l'écriture — son write-back cache (activé) acquitte l'écriture avant de la commettre sur le plateau. La donnée qui tombait sur les secteurs défaillants était silencieusement perdue, la parité RAID5 devenait incohérente avec les données réelles. Le RAID n'en savait rien.

À chaque session d'utilisation (~10-12h), les services NFS/k8s écrivaient sur `/srv/data`. Une fraction des writes tombait sur les zones corrompues de sdd et accumulait de la corruption dans les métadonnées ext4 (directory blocks, btree). Au bout de 3-4 sessions, assez de structures critiques étaient corrompues pour faire crasher les services.

**Problèmes secondaires identifiés :**
- `sda` : `UDMA_CRC_Error_Count = 6` → câble SATA défaillant ou port SATA instable
- `sdb` : `Start_Stop_Count = 17780` pour 8847h → spin-down APM trop agressif (~49 cycles/jour)
- Tous les disques : write-back cache activé sans onduleur → risque de corruption sur coupure de courant

### Résolution — corruptions de données

**Qdrant :** collection `funk-docs` supprimée et réingérée.
```bash
sudo systemctl stop qdrant
sudo rm -rf /srv/data/qdrant/storage/collections/funk-docs
sudo systemctl start qdrant
rag-ingest   # → 308 chunks réindexés
```

**PostgreSQL — bases grafana et litellm :**
```bash
sudo systemctl stop litellm
sudo -u postgres psql -c "DROP DATABASE litellm;"
sudo -u postgres psql -c "CREATE DATABASE litellm OWNER litellm;"
sudo -u postgres psql -c "DROP DATABASE grafana;"
sudo -u postgres psql -c "CREATE DATABASE grafana OWNER grafana;"
sudo systemctl start litellm
kubectl rollout restart deployment/kube-prometheus-stack-grafana -n monitoring
# → Grafana rejoue 709 migrations, recrée le schéma
```

**Prometheus TSDB :** deux blocs corrompus supprimés du NFS.
```bash
PROM_NFS="/srv/data/nfs/k8s/monitoring-prometheus-...-pvc-aa1a187f-.../prometheus-db"
sudo rm -rf "$PROM_NFS/01KSGF0DXNXJ4HC5CEZX9THT5B"
sudo rm -rf "$PROM_NFS/01KSGFC9WP7STGTXWDNECY2FS0"
kubectl delete pod prometheus-kube-prometheus-stack-prometheus-0 -n monitoring
```

**Docker registry :** PVC corrompu recréé (NFS provisioner génère un répertoire propre).
```bash
kubectl scale deployment registry -n infra --replicas=0
kubectl delete pvc registry-pvc -n infra
kubectl delete pv pvc-c5e1e86e-da56-4b31-bf52-07d5d619c9ea
# Recréer la PVC — le NFS provisioner provisionne un nouveau répertoire
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry-pvc
  namespace: infra
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: nfs
  resources:
    requests:
      storage: 10Gi
EOF
kubectl scale deployment registry -n infra --replicas=1
```

**sacrifice-web :** image rebuildée et pushée via tunnel SSH.
```bash
# Tunnel SSH pour atteindre le registry depuis le poste perso
ssh -f -N -L 31500:192.168.10.11:31500 storage-01
cd /path/to/Sacrifice/sacrifice-web
podman build -t localhost:31500/sacrifice-web:latest .
podman push localhost:31500/sacrifice-web:latest --tls-verify=false
kubectl rollout restart deployment/sacrifice-web -n sacrifice
```

### Résolution — cause racine (sdd retiré du RAID)

```bash
# Marquer sdd comme défaillant et le retirer
sudo mdadm --manage /dev/md0 --fail /dev/sdd1
sudo mdadm --manage /dev/md0 --remove /dev/sdd1
# → State: clean, degraded  [4/3] [UUU_]

# Persister la configuration
sudo mdadm --detail --scan | sudo tee /etc/mdadm.conf
sudo dracut --force   # régénérer l'initramfs
```

### État post-incident

| Service | État |
|---|---|
| RAID5 md0 | `[4/3] [UUU_]` — dégradé, **zéro redondance** jusqu'au remplacement de sdd |
| Qdrant | ✅ 308 chunks réindexés |
| PostgreSQL grafana | ✅ Schéma recréé, dashboards reprovisionnés |
| PostgreSQL litellm | ✅ Schéma recréé, config modèles intacte (perte de l'historique spend) |
| Prometheus | ✅ Running — perte des ~2 derniers blocs TSDB (quelques heures de métriques) |
| Docker registry | ✅ PVC propre, image sacrifice-web repushée |
| sacrifice-web | ✅ Running, HTTP 200 |

### Actions requises (non résolues)

- **Remplacer sdd** (1TB+, ex. WD Red ou Seagate IronWolf) et relancer la reconstruction :
  ```bash
  sudo mdadm --manage /dev/md0 --add /dev/sdNEW1
  # Reconstruction automatique ~4h, visible dans /proc/mdstat
  ```
- **Câble SATA sda** : remplacer le câble (UDMA_CRC_Error_Count=6)
- **APM sdb** : désactiver le spin-down (`sudo hdparm -B 254 /dev/sdb`) et persister via udev
- **Onduleur (UPS)** : indispensable — write-back cache activé sur tous les disques
- **e2fsck** : planifier une fenêtre de maintenance pour vérifier les métadonnées ext4 restantes :
  ```bash
  sudo systemctl stop nfs-server qdrant postgresql-16 litellm hermes-agent
  sudo umount /srv/data
  sudo e2fsck -f /dev/md0
  sudo mount /srv/data && sudo systemctl start nfs-server qdrant postgresql-16 litellm hermes-agent
  ```

---

## 2026-05-14 — Grafana : OOMKilled (limite mémoire trop serrée)

### Symptôme

Erreurs "No available server" intermittentes sur `grafana.lab.local`. Pod en CrashLoop.

```bash
kubectl describe pod -n monitoring -l app.kubernetes.io/name=grafana
# → Last State: Terminated, Reason: OOMKilled, Exit Code: 137
# → RESTARTS: 5
```

### Cause

Limite mémoire à 256Mi dans `k8s/infra/monitoring/values.yaml`. Grafana 13 consomme plus que ses prédécesseurs — OOM systématique. Traefik perd le backend pendant chaque redémarrage.

### Résolution

Augmenter les limites dans `k8s/infra/monitoring/values.yaml` :

```yaml
grafana:
  resources:
    requests:
      memory: 256Mi
    limits:
      memory: 1Gi   # était 256Mi
```

`git push` → ArgoCD applique → pod redémarre proprement.

---

## 2026-05-13 — AlertManager : ReconciliationFailed — receiver "null" non défini

### Symptôme

AlertManager en erreur dans ArgoCD. Logs :
```
level=error msg="loading configuration" err="undefined receiver \"null\" used in route"
```

### Cause

kube-prometheus-stack génère en interne une sous-route référençant le receiver `"null"` pour le heartbeat Watchdog. Une config AlertManager custom qui ne définissait pas ce receiver cassait la validation.

### Résolution

Ajouter explicitement le receiver et la route dans `values.yaml` :

```yaml
alertmanager:
  config:
    route:
      routes:
        - matchers:
            - alertname = "Watchdog"
          receiver: "null"
          repeat_interval: 24h
    receivers:
      - name: "null"   # obligatoire même si vide
```

---

## 2026-05-13 — llama-server : /metrics retourne 501 Not Implemented

### Symptôme

Prometheus ne scrappe rien sur les jobs `llama-server-*`. Targets en erreur avec `501 Not Implemented`.

### Cause

Le flag `--metrics` doit être passé explicitement à llama-server pour activer l'endpoint `/metrics`. Sans lui, la route existe mais répond 501.

### Résolution

Ajouter `--metrics` dans le service systemd (template Ansible `llama-server.service.j2`) :

```ini
ExecStart=/opt/llama.cpp/build/bin/llama-server \
    ...
    --metrics \
    --log-disable
```

Fix live sur les 3 instances + `daemon-reload` + restart :
```bash
# Sur gpu-01
for svc in llama-server llama-server-system llama-server-monitor; do
  sudo sed -i 's/--log-disable/--metrics \\\n    --log-disable/' /etc/systemd/system/$svc.service
done
sudo systemctl daemon-reload
sudo systemctl restart llama-server llama-server-system llama-server-monitor
```

---

## 2026-05-13 — nftables storage-01 : règle monitoring bloquée par le drop

### Symptôme

Prometheus ne pouvait pas scrapper `storage-01:9100` (node_exporter) ni recevoir les webhooks AlertManager sur `:9093`. Targets en `connection refused` dans Prometheus.

### Cause

`nft add rule` ajoute la règle **en fin de chaîne**, après la règle terminale `log prefix "nft-drop:" drop`. Les paquets étaient droppés avant d'atteindre la nouvelle règle.

```bash
# Vérifier l'ordre
sudo nft -n -a list chain inet filter input
# → handle 22 : log drop  (terminal)
# → handle 27 : tcp dport { 9093, 9100 } accept  (ajouté APRÈS → jamais atteint)
```

### Résolution

Supprimer la règle mal placée et la réinsérer **avant** le drop avec `insert rule position` :

```bash
sudo nft delete rule inet filter input handle 27
sudo nft insert rule inet filter input position 22 \
  tcp dport { 9093, 9100 } ip saddr 192.168.10.0/24 accept
```

Fix permanent dans le template Ansible `nftables.conf.j2` : placer la règle avant la règle drop.

---

## 2026-05-13 — storage-01 : corruption EXT4 après arrêt brutal + reinstall hermes-agent

### Symptômes

- `hermes-agent` en boucle de crash : `SyntaxError: source code string cannot contain null bytes`
- Erreurs EXT4 au démarrage des services : `Directory block failed checksum`, `deleted inode referenced`
- `dmesg` : `EXT4-fs (md0): error count since last fsck: 655`
- `sdb` : erreurs ATA bus (`SError: { RecovComm HostInt PHYRdyChg PHYInt }`) et I/O error sector 13405184

### Cause racine

Arrêt brutal du cluster la veille (`funk-stop --k8s`) → journal ext4 non rejoué correctement → 655 erreurs de corruption sur `/dev/md0`. `sdb` avait des erreurs transitoires mais SMART PASSED (pas de défaillance matérielle).

### Résolution étape par étape

**1. Vérifier SMART sdb**

```bash
smartctl -H /dev/sdb
# SMART overall-health self-assessment test result: PASSED → purement logiciel
```

**2. Arrêter les services et démonter**

```bash
sudo systemctl stop hermes-agent litellm qdrant postgresql-16 nfs-server
sudo umount /srv/data
```

**3. Réparer le filesystem**

```bash
sudo e2fsck -f -y /dev/md0
# 655 erreurs corrigées : HTREE invalides, blocs multiplement revendiqués,
# checksums de répertoires incorrects, compteurs d'inodes wronge
```

**4. Remonter et redémarrer les services**

```bash
sudo mount /srv/data
sudo systemctl start postgresql-16 qdrant litellm hermes-agent nfs-server
```

**5. Hermes-agent toujours en erreur après fsck**

Le e2fsck a zéroïsé des blocs appartenant à plusieurs inodes simultanément → des fichiers du venv Python et du code source `hermes_cli/` ont été corrompus (null bytes).

Tentatives de réparation du venv échouées (`pip install -e .` crash, `uv.lock` corrompu).

**6. Réinstallation complète de hermes-agent**

```bash
# Supprimer le code source corrompu (les profils dans /srv/data/hermes/profiles/ ne sont PAS touchés)
sudo rm -rf /srv/data/hermes/hermes-agent
sudo rm /opt/hermes/.local/bin/hermes

# Réinstaller depuis le script officiel (depuis un répertoire valide)
cd /srv/data/hermes
sudo -u hermes bash -c 'cd /srv/data/hermes && HOME=/opt/hermes HERMES_HOME=/srv/data/hermes curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash'
```

> Lors du `sudo rm -rf`, ne pas rester dans le répertoire supprimé — le shell perd le CWD et le script d'installation échoue avec `getcwd: cannot access parent directories`.

**Résultat** : hermes-agent réinstallé dans `/opt/hermes/.hermes/hermes-agent/` (SSD OS, plus de RAID5). Tous les profils (`funk-ai`, `brain`, `system`, `monitor`) intacts dans `/srv/data/hermes/profiles/`.

### Changement d'architecture post-incident

| Avant | Après |
|---|---|
| Code hermes-agent : `/srv/data/hermes/hermes-agent/` (RAID5) | Code hermes-agent : `/opt/hermes/.hermes/hermes-agent/` (SSD OS) |
| Venv : `/srv/data/hermes/hermes-agent/venv/` | Venv : `/opt/hermes/.hermes/hermes-agent/venv/` |
| Profils/données : `/srv/data/hermes/` | Profils/données : `/srv/data/hermes/` (inchangé) |

Le code étant désormais sur le SSD, une corruption future du RAID5 n'impactera plus l'installation — seulement les profils et données.

### Surveillance post-incident

```bash
# Santé RAID et disques
cat /proc/mdstat
smartctl -H /dev/sdb    # à surveiller

# Vérifier que e2fsck n'a pas schedulé un check au prochain boot
sudo tune2fs -l /dev/md0 | grep -E "check|count|error"
```

---

## 2026-05-11 — storage-01 : boucle fsck après coupure de courant

### Symptômes

- Boucle infinie de `fsck` au démarrage sur `/dev/md0`
- `You are in emergency mode. Give root password for maintenance` — mot de passe impossible à saisir (messages défilants)
- Après résolution : `hermes-agent` en boucle d'erreurs (cause : `/srv/data` non monté)

### Architecture concernée

| Composant | Détail |
|---|---|
| Disque système | NVMe `nvme0n1` — LVM (`almalinux-root` XFS 70G, swap 14.7G, home 845G) |
| RAID données | RAID5 software `md0` — 4 disques SATA 931.5G (`sda1`, `sdb2`, `sdc1`, `sdd1`) |
| Point de montage | `/srv/data` (ext4) |

### Cause racine

La coupure de courant a corrompu le système de fichiers **XFS** sur `almalinux-root`. Au redémarrage, systemd tentait de vérifier `/srv/data` avant que le RAID `md0` soit assemblé → boucle bloquante.

### Résolution étape par étape

**1. Shell de secours via GRUB**

Au démarrage, appuyer sur `Shift` pour afficher le menu GRUB. Appuyer sur `e` pour éditer la ligne `linux` :
- Remplacer `ro` par `rw`
- Ajouter `init=/bin/bash` en fin de ligne

Démarrer avec `Ctrl+X`.

**2. Activer le LVM**

```bash
lvm vgchange -ay
# 3 logical volume(s) in volume group "almalinux" now active
```

**3. Réparer le XFS (pas fsck)**

XFS nécessite `xfs_repair`, pas `fsck` :

```bash
umount /dev/mapper/almalinux-root
xfs_repair /dev/mapper/almalinux-root
# Phases 1 à 7 — succès
```

**4. Désactiver temporairement `/srv/data` dans fstab**

```bash
vi /etc/fstab
# Commenter la ligne /srv/data pour permettre le boot sans RAID
```

**5. Reboot** → système normal, RAID intact `[4/4] [UUUU]`.

### Fix permanent — fstab

Modifier la ligne `/srv/data` dans `/etc/fstab` :

```
# Avant
UUID=3add8360-fa01-47eb-0aa1-0e84bacbbc15 /srv/data ext4 defaults,noatime,nodiratime 0 2

# Après
UUID=3add8360-fa01-47eb-0aa1-0e84bacbbc15 /srv/data ext4 defaults,noatime,nodiratime,_netdev 0 0
```

| Changement | Effet |
|---|---|
| `0 2` → `0 0` | Désactive la vérification fsck automatique au boot |
| `_netdev` | Force systemd à attendre que tous les périphériques (RAID) soient prêts avant de monter |

### Surveillance post-incident

```bash
# État du RAID
cat /proc/mdstat

# Santé des disques (sdb a généré des erreurs I/O — à surveiller)
smartctl -a /dev/sda
smartctl -a /dev/sdb
smartctl -a /dev/sdc
smartctl -a /dev/sdd

# Monter /srv/data manuellement si besoin
mount /srv/data

# Logs de boot
journalctl -xb | grep -i "error\|fail"
```

### Points d'attention

- `sdb` a généré des erreurs I/O pendant l'incident — il est revenu opérationnel mais doit être surveillé régulièrement avec `smartctl`.
- Envisager un **onduleur (UPS)** pour prévenir ce type d'incident sur coupure de courant.
- Le fix fstab (`_netdev`, `0 0`) est permanent — ne pas revenir aux valeurs précédentes.


---

## État vérifié — 2026-06-05  
Tous les services critiques (LiteLLM, Qdrant, PostgreSQL, llama-server) sont actifs et sans incident. Aucune divergence avec la documentation. Aucun pod crashé détecté dans le cluster Kubernetes. Export NFS sur /srv/data fonctionnel. Aucune configuration inattendue observée.