mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-09 17:14:41 +02:00
docs: réorganisation complète de admin/ en 4 domaines thématiques
Structure avant : 20 fichiers à plat dans admin/ — difficile à naviguer.
Structure après : 4 sous-répertoires thématiques + index clair.
Réorganisation :
admin/ops/ → cluster.md, ansible.md, systeme.md
admin/infra/ → reseau.md, nfs.md, dnsmasq.md, ssh.md
admin/k8s/ → talos.md, argocd.md, monitoring.md
admin/ia/ → llama_server.md, rocm.md, litellm.md, hermes.md
Suppressions :
- ask-agent.md : contenu fusionné dans ia/hermes.md (section ask-agent)
- lm_studio.md : obsolète (LM Studio remplacé par llama-server)
Mises à jour contenu :
- ia/hermes.md : fusion complète avec ask-agent.md (profils, skills,
SOUL.md, ask-agent CLI, dépannage) — doc unifiée sans redondance
- ops/cluster.md : section GitOps réduite à 2 lignes + lien argocd.md
- incidents.md : tableau de résumé en tête + 4 nouveaux incidents
(Grafana OOMKilled, AlertManager null receiver, llama-server 501,
nftables règle après drop)
- README.md : réécrit — navigation rapide + index par domaine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d552abd2ef
commit
5bcf95b82e
19 changed files with 633 additions and 677 deletions
250
admin/k8s/argocd.md
Normal file
250
admin/k8s/argocd.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# ArgoCD — GitOps cluster Funk
|
||||
|
||||
ArgoCD est l'opérateur GitOps du cluster. Il surveille le repo `Funk-lab` et
|
||||
applique automatiquement tout ce qui se trouve dans `k8s/` vers le cluster Kubernetes.
|
||||
|
||||
---
|
||||
|
||||
## Accès
|
||||
|
||||
| Interface | URL | Login | Mot de passe |
|
||||
|---|---|---|---|
|
||||
| UI web | http://argocd.lab.local | `admin` | voir ci-dessous |
|
||||
| CLI | `argocd` | — | — |
|
||||
|
||||
**Mot de passe initial :**
|
||||
```bash
|
||||
kubectl -n argocd get secret argocd-initial-admin-secret \
|
||||
-o jsonpath="{.data.password}" | base64 -d && echo
|
||||
```
|
||||
|
||||
> Supprimer ce secret après avoir changé le mot de passe :
|
||||
> `kubectl -n argocd delete secret argocd-initial-admin-secret`
|
||||
|
||||
---
|
||||
|
||||
## Principe GitOps
|
||||
|
||||
```
|
||||
Modifier un fichier dans k8s/ → git commit + git push → ArgoCD détecte (~3 min) → kubectl apply automatique
|
||||
```
|
||||
|
||||
**Tu ne fais plus jamais `helm install` ou `kubectl apply` manuellement pour les workloads.**
|
||||
Tout passe par Git. Le repo est la source de vérité.
|
||||
|
||||
---
|
||||
|
||||
## Installation (bootstrap — fait une seule fois)
|
||||
|
||||
ArgoCD est bootstrappé manuellement via Helm, puis se gère ensuite lui-même via GitOps.
|
||||
|
||||
```bash
|
||||
# Ajouter le repo Helm ArgoCD
|
||||
helm repo add argo https://argoproj.github.io/argo-helm
|
||||
helm repo update
|
||||
|
||||
# Installer ArgoCD
|
||||
kubectl create namespace argocd
|
||||
helm install argocd argo/argo-cd \
|
||||
--namespace argocd \
|
||||
--version 9.5.14 \
|
||||
-f k8s/argocd-bootstrap/values.yaml \
|
||||
--wait --timeout 5m
|
||||
|
||||
# Donner accès au repo Git (SSH)
|
||||
kubectl create secret generic argocd-repo-funk-lab \
|
||||
--namespace argocd \
|
||||
--from-literal=type=git \
|
||||
--from-literal=url=git@github.com:Alkatrazz24/Funk-lab.git \
|
||||
--from-file=sshPrivateKey=~/.ssh/id_ed25519
|
||||
kubectl label secret argocd-repo-funk-lab \
|
||||
-n argocd "argocd.argoproj.io/secret-type=repository"
|
||||
|
||||
# Appliquer l'App of Apps racine
|
||||
kubectl apply -f k8s/apps-of-apps/root.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structure GitOps
|
||||
|
||||
```
|
||||
k8s/
|
||||
├── argocd-bootstrap/ ← values.yaml Helm (bootstrap manuel uniquement)
|
||||
├── apps-of-apps/
|
||||
│ ├── root.yaml ← Application racine (appliquée une seule fois)
|
||||
│ └── apps/
|
||||
│ └── monitoring.yaml ← Application monitoring (créée par root)
|
||||
└── infra/
|
||||
└── monitoring/ ← Manifests déployés par l'app monitoring
|
||||
├── namespace.yaml
|
||||
├── helmrelease.yaml ← Application kube-prometheus-stack
|
||||
└── values.yaml
|
||||
```
|
||||
|
||||
**Pattern App of Apps :**
|
||||
- `root` surveille `k8s/apps-of-apps/apps/` → crée les Applications enfants
|
||||
- Chaque Application enfant surveille son propre répertoire dans `k8s/infra/` ou `k8s/apps/`
|
||||
|
||||
---
|
||||
|
||||
## Déployer une nouvelle application
|
||||
|
||||
### 1. Créer les manifests
|
||||
|
||||
```bash
|
||||
mkdir -p k8s/apps/<nom-app>
|
||||
vim k8s/apps/<nom-app>/deployment.yaml
|
||||
# ...
|
||||
```
|
||||
|
||||
### 2. Créer l'Application ArgoCD
|
||||
|
||||
```bash
|
||||
cat > k8s/apps-of-apps/apps/<nom-app>.yaml << 'EOF'
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: <nom-app>
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:Alkatrazz24/Funk-lab.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/<nom-app>
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: <namespace>
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
EOF
|
||||
```
|
||||
|
||||
### 3. Pousser et attendre
|
||||
|
||||
```bash
|
||||
git add k8s/
|
||||
git commit -m "feat: <nom-app>"
|
||||
git push
|
||||
# ArgoCD déploie automatiquement en ~3 min
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Déployer un chart Helm via ArgoCD (multi-source)
|
||||
|
||||
Pattern pour un chart Helm avec values dans le repo :
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mon-chart
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: https://charts.exemple.com
|
||||
chart: mon-chart
|
||||
targetRevision: 1.2.3
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/mon-chart/values.yaml
|
||||
- repoURL: git@github.com:Alkatrazz24/Funk-lab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: mon-namespace
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
```
|
||||
|
||||
> **Important :** utiliser `sources` (pluriel) — pas `source` + `sources` en même temps.
|
||||
> Le champ est `valueFiles` (pas `valuesFiles`).
|
||||
|
||||
---
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```bash
|
||||
# État de toutes les applications
|
||||
kubectl get applications -n argocd
|
||||
|
||||
# Forcer un refresh (re-lire le repo Git)
|
||||
kubectl -n argocd annotate application <nom> argocd.argoproj.io/refresh=hard --overwrite
|
||||
|
||||
# Logs ArgoCD
|
||||
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server --tail=50
|
||||
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller --tail=50
|
||||
|
||||
# Redémarrer ArgoCD
|
||||
kubectl rollout restart deployment -n argocd
|
||||
|
||||
# Mettre à jour ArgoCD
|
||||
helm upgrade argocd argo/argo-cd \
|
||||
--namespace argocd \
|
||||
--version <nouvelle-version> \
|
||||
-f k8s/argocd-bootstrap/values.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Application bloquée en OutOfSync
|
||||
|
||||
```bash
|
||||
kubectl describe application <nom> -n argocd | grep -A5 "Message"
|
||||
# Forcer un hard refresh
|
||||
kubectl -n argocd annotate application <nom> argocd.argoproj.io/refresh=hard --overwrite
|
||||
```
|
||||
|
||||
### Erreur "Repository not found"
|
||||
|
||||
La clé SSH n'est pas configurée ou le secret a été supprimé :
|
||||
```bash
|
||||
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=repository
|
||||
# Recréer si absent (voir section Installation)
|
||||
```
|
||||
|
||||
### Erreur "field not declared in schema" sur une Application
|
||||
|
||||
Symptôme : ArgoCD essaie de patcher une ressource Application avec un champ invalide.
|
||||
Fix : supprimer la ressource ArgoCD et laisser root la recréer depuis le Git corrigé.
|
||||
```bash
|
||||
kubectl delete application <nom> -n argocd
|
||||
kubectl -n argocd annotate application root argocd.argoproj.io/refresh=hard --overwrite
|
||||
```
|
||||
|
||||
### StatefulSet bloqué par PVC après changement de storageSpec
|
||||
|
||||
Les StatefulSets n'autorisent pas la modification de `volumeClaimTemplates`.
|
||||
```bash
|
||||
# Patcher le CRD pour supprimer le storage
|
||||
kubectl patch <crd> <nom> -n <ns> --type=merge -p '{"spec":{"storage":null}}'
|
||||
# Supprimer StatefulSet + PVC
|
||||
kubectl delete statefulset <nom> -n <ns>
|
||||
kubectl delete pvc -n <ns> --all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Helm (k8s/argocd-bootstrap/values.yaml)
|
||||
|
||||
- Ingress Traefik sur `argocd.lab.local`
|
||||
- `server.insecure: true` — TLS terminé par Traefik
|
||||
- `dex.enabled: false` — pas de SSO pour l'instant
|
||||
- Resources limitées (128-256 Mi RAM par composant)
|
||||
416
admin/k8s/monitoring.md
Normal file
416
admin/k8s/monitoring.md
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
# Monitoring — Grafana + Prometheus + AlertManager
|
||||
|
||||
Stack déployée via ArgoCD dans le namespace `monitoring`.
|
||||
Chart : `kube-prometheus-stack` v85.0.2 (Prometheus Operator).
|
||||
Dashboards et alertes custom versionnés dans `k8s/infra/monitoring/`.
|
||||
|
||||
---
|
||||
|
||||
## Accès et comptes
|
||||
|
||||
| Service | URL | Login | Mot de passe |
|
||||
|---|---|---|---|
|
||||
| **Grafana** | http://grafana.lab.local | `admin` | `funk-grafana` |
|
||||
| **Prometheus** | http://prometheus.lab.local | — | — |
|
||||
| **AlertManager** | http://alertmanager.lab.local | — | — |
|
||||
|
||||
> DNS `*.lab.local` ne résout que depuis le LAN cluster (192.168.10.0/24).
|
||||
> Depuis le poste perso (192.168.1.x), ajouter dans `/etc/hosts` :
|
||||
> ```
|
||||
> 192.168.10.200 grafana.lab.local argocd.lab.local prometheus.lab.local alertmanager.lab.local
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Prometheus (k8s, namespace monitoring)
|
||||
├── kube-state-metrics → ressources k8s (pods, deployments, nodes)
|
||||
├── node-exporter (DaemonSet) → métriques système compute-01/02/03
|
||||
├── scrape storage-01:9100 → métriques système storage-01 (job=storage-01)
|
||||
├── scrape gpu-01:9100 → métriques système + ROCm gpu-01 (job=gpu-01-node)
|
||||
│ └── textfile_collector → rocm_gpu_temperature/utilization/vram (rocm_scraper.timer)
|
||||
├── scrape gpu-01:1234 → llama-server GPU Qwen3-8B (job=llama-server-gpu)
|
||||
├── scrape gpu-01:1236 → llama-server CPU system (job=llama-server-system)
|
||||
└── scrape gpu-01:1237 → llama-server CPU monitor (job=llama-server-monitor)
|
||||
↓
|
||||
AlertManager
|
||||
├── receiver "null" → alerte Watchdog (heartbeat interne — silencé)
|
||||
└── receiver "hermes-monitor" → webhook storage-01:9093 → ask-agent monitor → Hermes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dashboards Grafana
|
||||
|
||||
Trois dashboards custom auto-importés via ConfigMaps (label `grafana_dashboard: "1"`).
|
||||
Versionnés dans `k8s/infra/monitoring/dashboards/`.
|
||||
|
||||
| Dashboard | UID | Contenu |
|
||||
|---|---|---|
|
||||
| **Funk — Kubernetes** | `funk-k8s1` | Cluster overview, CPU/RAM par nœud, pod restarts, PVCs |
|
||||
| **Funk — Infrastructure** | `funk-inf1` | Hardware compute/storage-01/gpu-01 — CPU, RAM, disque, réseau, GPU ROCm |
|
||||
| **Funk — IA / Hermes** | `funk-ai01` | Tokens/s llama-server (GPU+CPU), requêtes, VRAM, température GPU |
|
||||
|
||||
URLs directes :
|
||||
- http://grafana.lab.local/d/funk-k8s1
|
||||
- http://grafana.lab.local/d/funk-inf1
|
||||
- http://grafana.lab.local/d/funk-ai01
|
||||
|
||||
### Ajouter un dashboard custom (GitOps)
|
||||
|
||||
1. Créer `k8s/infra/monitoring/dashboards/mon-dashboard.yaml` :
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-mon-dashboard
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
mon-dashboard.json: |
|
||||
{ ... JSON exporté depuis Grafana ... }
|
||||
```
|
||||
|
||||
2. `git add` + `git push` → ArgoCD applique → sidecar Grafana importe automatiquement.
|
||||
|
||||
Pour exporter un dashboard depuis Grafana : **Dashboard → Share → Export → Save to file**.
|
||||
|
||||
---
|
||||
|
||||
## Règles d'alerte
|
||||
|
||||
Versionnées dans `k8s/infra/monitoring/alerts/` comme `PrometheusRule`.
|
||||
Déclenchent AlertManager → webhook storage-01:9093 → profil `monitor` Hermes.
|
||||
|
||||
### Nœuds (`alerts-node.yaml`)
|
||||
|
||||
| Alerte | Seuil | Sévérité |
|
||||
|---|---|---|
|
||||
| `NodeHighCPU` | CPU > 90% pendant 5 min | warning |
|
||||
| `NodeCriticalCPU` | CPU > 98% pendant 2 min | critical |
|
||||
| `NodeHighMemory` | RAM > 90% pendant 5 min | warning |
|
||||
| `NodeCriticalMemory` | RAM > 97% pendant 2 min | critical |
|
||||
| `NodeDiskSpaceLow` | Disque > 80% pendant 10 min | warning |
|
||||
| `NodeDiskSpaceCritical` | Disque > 93% pendant 5 min | critical |
|
||||
| `Storage01Down` | `up{job="storage-01"} == 0` pendant 2 min | critical |
|
||||
| `Gpu01Down` | `up{job="gpu-01-node"} == 0` pendant 2 min | critical |
|
||||
| `NodeHighLoad` | Load5 > 8 pendant 10 min | warning |
|
||||
|
||||
### Kubernetes (`alerts-k8s.yaml`)
|
||||
|
||||
| Alerte | Seuil | Sévérité |
|
||||
|---|---|---|
|
||||
| `KubeNodeNotReady` | Nœud NotReady pendant 5 min | critical |
|
||||
| `PodCrashLooping` | > 3 restarts en 15 min | warning |
|
||||
| `PodFailedLong` | Pod Failed/Unknown pendant 10 min | warning |
|
||||
| `PodPendingLong` | Pod Pending pendant 15 min | warning |
|
||||
| `DeploymentUnavailable` | Réplicas < spec pendant 5 min | warning |
|
||||
| `PVCNotBound` | PVC non Bound pendant 10 min | warning |
|
||||
| `ArgoCDAppOutOfSync` | App ArgoCD OutOfSync pendant 30 min | warning |
|
||||
|
||||
### IA / LLM (`alerts-ai.yaml`)
|
||||
|
||||
| Alerte | Seuil | Sévérité |
|
||||
|---|---|---|
|
||||
| `LlamaServerGPUDown` | `up{job="llama-server-gpu"} == 0` pendant 2 min | critical |
|
||||
| `LlamaServerSystemDown` | `up{job="llama-server-system"} == 0` pendant 2 min | warning |
|
||||
| `LlamaServerMonitorDown` | `up{job="llama-server-monitor"} == 0` pendant 2 min | warning |
|
||||
| `GPUTemperatureHigh` | Temp GPU > 80°C pendant 5 min | warning |
|
||||
| `GPUTemperatureCritical` | Temp GPU > 90°C pendant 2 min | critical |
|
||||
| `GPUVRAMAlmostFull` | VRAM > 95% pendant 5 min | warning |
|
||||
| `LlamaServerHighQueueGPU` | Requêtes deferred > 5 pendant 2 min | warning |
|
||||
|
||||
### Ajouter une règle d'alerte
|
||||
|
||||
```yaml
|
||||
# k8s/infra/monitoring/alerts/ma-regle.yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: funk-ma-regle
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: kube-prometheus-stack # obligatoire — sélecteur du Prometheus operator
|
||||
spec:
|
||||
groups:
|
||||
- name: funk.custom
|
||||
rules:
|
||||
- alert: MonAlerte
|
||||
expr: ma_metrique > seuil
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Description courte"
|
||||
description: "Détail avec {{ $value }}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sources de métriques
|
||||
|
||||
### Nœuds hors cluster (storage-01, gpu-01)
|
||||
|
||||
`node_exporter` v1.9.1 déployé via Ansible (rôle `node_exporter`).
|
||||
Prérequis firewall : port 9100 ouvert depuis 192.168.10.0/24 (nftables storage-01, firewalld gpu-01).
|
||||
|
||||
```bash
|
||||
# État node_exporter
|
||||
ssh storage-01 "systemctl status node_exporter"
|
||||
ssh gpu-01 "systemctl status node_exporter"
|
||||
|
||||
# Vérifier les métriques directement
|
||||
curl -s http://192.168.10.1:9100/metrics | grep node_memory_MemAvailable
|
||||
curl -s http://192.168.10.20:9100/metrics | grep node_memory_MemAvailable
|
||||
|
||||
# Vérifier le statut des targets dans Prometheus
|
||||
curl -s http://prometheus.lab.local/api/v1/targets | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
for t in d['data']['activeTargets']:
|
||||
job = t['labels'].get('job','?')
|
||||
print(t['health'], job, t.get('lastError','')[:60])
|
||||
" | grep -E "storage-01|gpu-01|llama"
|
||||
```
|
||||
|
||||
### Métriques GPU AMD (gpu-01) — ROCm via textfile_collector
|
||||
|
||||
Collecteur sysfs `rocm_scraper.timer` (toutes les 30s) écrit dans `/var/lib/node_exporter/textfile_collector/rocm.prom`.
|
||||
Servi par node_exporter sur port 9100 — **pas de port séparé**.
|
||||
Lit `/sys/class/drm/card0/device/` directement (rocm-smi retiré dans ROCm 7.x).
|
||||
|
||||
```bash
|
||||
# Vérifier les métriques ROCm
|
||||
ssh gpu-01 "cat /var/lib/node_exporter/textfile_collector/rocm.prom"
|
||||
|
||||
# Forcer une collecte manuelle
|
||||
ssh gpu-01 "sudo systemctl start rocm_scraper"
|
||||
|
||||
# Vérifier le timer
|
||||
ssh gpu-01 "systemctl status rocm_scraper.timer"
|
||||
```
|
||||
|
||||
Métriques disponibles (labels : `gpu="0"`, `model="gfx1031"`) :
|
||||
|
||||
| Métrique | Description | Valeur typique |
|
||||
|---|---|---|
|
||||
| `rocm_gpu_temperature_celsius` | Température (°C) | 40–85 |
|
||||
| `rocm_gpu_utilization_percent` | Utilisation GPU (%) | 0–100 |
|
||||
| `rocm_vram_used_bytes` | VRAM utilisée | ~10 GB (modèle chargé) |
|
||||
| `rocm_vram_total_bytes` | VRAM totale | 12.9 GB (RX 6700XT) |
|
||||
|
||||
### llama-server /metrics
|
||||
|
||||
llama.cpp expose ses métriques sur `/metrics` — activé via `--metrics` dans le service systemd.
|
||||
|
||||
| Métrique | Description |
|
||||
|---|---|
|
||||
| `llamacpp:prompt_tokens_seconds` | Débit prompt moyen (tokens/s) |
|
||||
| `llamacpp:predicted_tokens_seconds` | Débit génération moyen (tokens/s) |
|
||||
| `llamacpp:prompt_tokens_total` | Tokens prompt totaux (compteur) |
|
||||
| `llamacpp:tokens_predicted_total` | Tokens générés totaux (compteur) |
|
||||
| `llamacpp:requests_processing` | Requêtes en cours de traitement |
|
||||
| `llamacpp:requests_deferred` | Requêtes en file d'attente |
|
||||
| `llamacpp:n_decode_total` | Nombre total d'appels llama_decode() |
|
||||
|
||||
```bash
|
||||
# Métriques GPU (Qwen3-8B)
|
||||
curl -s http://192.168.10.20:1234/metrics | grep llamacpp
|
||||
|
||||
# Métriques CPU system
|
||||
curl -s http://192.168.10.20:1236/metrics | grep llamacpp
|
||||
|
||||
# Métriques CPU monitor
|
||||
curl -s http://192.168.10.20:1237/metrics | grep llamacpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AlertManager → Hermes monitor
|
||||
|
||||
AlertManager envoie les alertes en POST vers `http://192.168.10.1:9093/webhook`.
|
||||
Le service `alertmanager-webhook` (Python, storage-01) reçoit les alertes et appelle `ask-agent monitor`.
|
||||
Hermes (profil `monitor`) analyse et répond dans ses logs.
|
||||
|
||||
```bash
|
||||
# Vérifier le webhook
|
||||
ssh storage-01 "systemctl status alertmanager-webhook"
|
||||
ssh storage-01 "journalctl -u alertmanager-webhook -n 20 --no-pager --output=cat"
|
||||
|
||||
# Tester manuellement
|
||||
curl -s -X POST http://192.168.10.1:9093/webhook \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"alerts":[{"status":"firing","labels":{"alertname":"TestAlert","severity":"warning","instance":"storage-01"},"annotations":{"summary":"Test alerte","description":"Vérification du webhook"}}]}'
|
||||
|
||||
# Voir les alertes actives dans AlertManager
|
||||
curl -s http://alertmanager.lab.local/api/v2/alerts | python3 -m json.tool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Persistance des données
|
||||
|
||||
Prometheus et Grafana stockent leurs données sur le RAID5 de storage-01 via NFS.
|
||||
Provisioner : `nfs-subdir-external-provisioner`, StorageClass `nfs`.
|
||||
|
||||
| Service | PVC | Taille | Rétention |
|
||||
|---|---|---|---|
|
||||
| Prometheus | `prometheus-...-db-prometheus-...-0` | 20 Gi | 15 jours / 8 GB |
|
||||
| Grafana | `kube-prometheus-stack-grafana` | 2 Gi | — |
|
||||
|
||||
```bash
|
||||
# Espace consommé sur le RAID5
|
||||
ssh storage-01 "du -sh /srv/data/nfs/k8s/*"
|
||||
|
||||
# État des PVCs
|
||||
kubectl get pvc -n monitoring
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Administration Grafana
|
||||
|
||||
### Changer le mot de passe admin
|
||||
|
||||
Via API :
|
||||
```bash
|
||||
curl -X PUT http://admin:funk-grafana@grafana.lab.local/api/user/password \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"oldPassword":"funk-grafana","newPassword":"<nouveau>","confirmNew":"<nouveau>"}'
|
||||
```
|
||||
|
||||
Ou modifier `k8s/infra/monitoring/values.yaml` → `grafana.adminPassword` → `git push`.
|
||||
|
||||
### Datasources
|
||||
|
||||
| Nom | Type | URL (interne cluster) |
|
||||
|---|---|---|
|
||||
| Prometheus | prometheus | `http://kube-prometheus-stack-prometheus:9090` |
|
||||
| Alertmanager | alertmanager | `http://kube-prometheus-stack-alertmanager:9093` |
|
||||
|
||||
Configurées automatiquement au démarrage — ne pas modifier depuis l'UI (écrasé à chaque redémarrage).
|
||||
|
||||
### Lister les dashboards disponibles
|
||||
|
||||
```bash
|
||||
curl -s http://admin:funk-grafana@grafana.lab.local/api/search?type=dash-db \
|
||||
| python3 -c "import sys,json; [print(d['title'],'—',d['uid']) for d in json.load(sys.stdin)]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Administration Prometheus
|
||||
|
||||
### Vérifier les targets de scrape
|
||||
|
||||
```bash
|
||||
# Depuis l'UI
|
||||
open http://prometheus.lab.local/targets
|
||||
|
||||
# Via API
|
||||
curl -s http://prometheus.lab.local/api/v1/targets | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
for t in d['data']['activeTargets']:
|
||||
print(t['health'], t['labels'].get('job','?'), t.get('lastError','')[:60])
|
||||
" | sort
|
||||
```
|
||||
|
||||
### Ajouter un scrape config
|
||||
|
||||
Modifier `k8s/infra/monitoring/values.yaml` → section `additionalScrapeConfigs` → `git push`.
|
||||
|
||||
```yaml
|
||||
prometheus:
|
||||
prometheusSpec:
|
||||
additionalScrapeConfigs:
|
||||
- job_name: mon-service
|
||||
static_configs:
|
||||
- targets: ['192.168.10.x:9xxx']
|
||||
labels:
|
||||
instance: mon-instance
|
||||
```
|
||||
|
||||
### Vérifier les règles d'alerte
|
||||
|
||||
```bash
|
||||
# Depuis l'UI
|
||||
open http://prometheus.lab.local/alerts
|
||||
|
||||
# Via API — alertes actuellement firing
|
||||
curl -s http://prometheus.lab.local/api/v1/alerts | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
for a in d['data']['alerts']:
|
||||
if a['state'] == 'firing':
|
||||
print(a['labels']['alertname'], a['labels'].get('instance',''), a['state'])
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commandes d'administration
|
||||
|
||||
```bash
|
||||
# État de tous les pods monitoring
|
||||
kubectl get pods -n monitoring
|
||||
|
||||
# Logs Prometheus
|
||||
kubectl logs -n monitoring prometheus-kube-prometheus-stack-prometheus-0 -c prometheus --tail=50
|
||||
|
||||
# Logs Grafana
|
||||
kubectl logs -n monitoring -l app.kubernetes.io/name=grafana --tail=50
|
||||
|
||||
# Logs AlertManager
|
||||
kubectl logs -n monitoring alertmanager-kube-prometheus-stack-alertmanager-0 --tail=50
|
||||
|
||||
# Redémarrer Grafana (recharge les ConfigMaps dashboards)
|
||||
kubectl rollout restart deployment kube-prometheus-stack-grafana -n monitoring
|
||||
|
||||
# Forcer re-sync ArgoCD (dashboards, alertes, config)
|
||||
kubectl -n argocd annotate application monitoring argocd.argoproj.io/refresh=hard --overwrite
|
||||
kubectl -n argocd annotate application kube-prometheus-stack argocd.argoproj.io/refresh=hard --overwrite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structure des fichiers
|
||||
|
||||
```
|
||||
k8s/infra/monitoring/
|
||||
├── helmrelease.yaml # Application ArgoCD (multi-source Helm kube-prometheus-stack)
|
||||
├── namespace.yaml # Namespace monitoring (labels PodSecurity privileged)
|
||||
├── values.yaml # Valeurs Helm : ressources, ingress, scrape configs, alertmanager
|
||||
├── dashboards/
|
||||
│ ├── dashboard-kubernetes.yaml # ConfigMap — Funk Kubernetes (uid: funk-k8s1)
|
||||
│ ├── dashboard-infrastructure.yaml # ConfigMap — Funk Infrastructure (uid: funk-inf1)
|
||||
│ └── dashboard-ai.yaml # ConfigMap — Funk IA/Hermes (uid: funk-ai01)
|
||||
└── alerts/
|
||||
├── alerts-node.yaml # PrometheusRule — CPU, RAM, disque, nodes hors cluster
|
||||
├── alerts-k8s.yaml # PrometheusRule — k8s : pods, nodes, PVCs, ArgoCD
|
||||
└── alerts-ai.yaml # PrometheusRule — llama-server, GPU temp, VRAM
|
||||
```
|
||||
|
||||
> ArgoCD Application `monitoring` utilise `directory.recurse: true` pour scanner
|
||||
> récursivement le répertoire et ses sous-dossiers.
|
||||
|
||||
---
|
||||
|
||||
## Points d'attention
|
||||
|
||||
| Sujet | Détail |
|
||||
|---|---|
|
||||
| Persistance | **Active** — Prometheus 20 Gi + Grafana 2 Gi sur RAID5 NFS storage-01 |
|
||||
| Dashboards GitOps | ConfigMaps dans `dashboards/` → importés automatiquement par le sidecar Grafana |
|
||||
| Alertes GitOps | PrometheusRules dans `alerts/` → label `release: kube-prometheus-stack` obligatoire |
|
||||
| Receiver `null` | Doit exister dans la config AlertManager — requis par les sous-routes internes du chart (Watchdog) |
|
||||
| nftables storage-01 | Port 9100 doit être avant la règle `drop` dans la chaîne `input` (sinon context deadline) |
|
||||
| ROCm port | Métriques GPU via textfile_collector sur port **9100** — pas de port séparé 9101 |
|
||||
| llama-server `--metrics` | Flag requis dans le service systemd pour activer l'endpoint `/metrics` |
|
||||
| Talos — composants désactivés | `kubeEtcd`, `kubeScheduler`, `kubeControllerManager`, `kubeProxy` désactivés (non accessibles) |
|
||||
| PodSecurity privileged | Namespace `monitoring` doit être `privileged` pour node-exporter (hostNetwork/hostPID/hostPath) |
|
||||
| nfs-provisioner | StorageClass peut disparaître si ArgoCD prune l'app — forcer sync si PVC Pending |
|
||||
311
admin/k8s/talos.md
Normal file
311
admin/k8s/talos.md
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
# Talos Linux — Installation et administration du cluster
|
||||
|
||||
Cluster Kubernetes **funk** sur 3 ThinkCentre (AMD A10-9700E) via Talos Linux.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
| Nœud | IP | Rôle | Disque | RAM |
|
||||
|---|---|---|---|---|
|
||||
| compute-01 | 192.168.10.11 | control-plane | NVMe 256 GB (PNY/Samsung) | 16 GB |
|
||||
| compute-02 | 192.168.10.12 | worker | NVMe 256 GB (PNY) | 8 GB |
|
||||
| compute-03 | 192.168.10.13 | worker | NVMe 256 GB (PNY) | 8 GB |
|
||||
|
||||
- **OS** : Talos Linux v1.13.0
|
||||
- **Kubernetes** : v1.33.1
|
||||
- **CNI** : Flannel (pods `10.42.0.0/16`, services `10.43.0.0/16`)
|
||||
- **Single control-plane** : si compute-01 tombe, `kubectl` ne fonctionne plus mais les workloads continuent
|
||||
|
||||
---
|
||||
|
||||
## Outils requis (poste perso + storage-01)
|
||||
|
||||
```bash
|
||||
# talhelper
|
||||
curl -sL https://github.com/budimanjojo/talhelper/releases/latest/download/talhelper_linux_amd64.tar.gz | sudo tar xz -C /usr/local/bin talhelper
|
||||
|
||||
# age
|
||||
AGE_VERSION=$(curl -s https://api.github.com/repos/FiloSottile/age/releases/latest | grep tag_name | cut -d'"' -f4)
|
||||
curl -sL "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz" | sudo tar xz -C /tmp && sudo mv /tmp/age/age /usr/local/bin/ && sudo mv /tmp/age/age-keygen /usr/local/bin/
|
||||
|
||||
# sops
|
||||
sudo curl -sL "https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64" -o /usr/local/bin/sops && sudo chmod +x /usr/local/bin/sops
|
||||
|
||||
# talosctl
|
||||
curl -sL https://talos.dev/install | sh
|
||||
|
||||
# kubectl
|
||||
curl -sL "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -o /tmp/kubectl && sudo install /tmp/kubectl /usr/local/bin/kubectl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structure du repo
|
||||
|
||||
```
|
||||
talos/
|
||||
├── talconfig.yaml # config déclarative — source de vérité
|
||||
├── talsecret.sops.yaml # secrets chiffrés (SOPS/age) — commitable
|
||||
├── patches/
|
||||
│ ├── all.yaml # NTP + DNS → storage-01
|
||||
│ └── workers.yaml # kubelet reserved resources (8 GB RAM)
|
||||
└── clusterconfig/ # généré par talhelper — NE PAS committer
|
||||
├── funk-compute-01.yaml
|
||||
├── funk-compute-02.yaml
|
||||
├── funk-compute-03.yaml
|
||||
└── talosconfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Clé age (SOPS)
|
||||
|
||||
```bash
|
||||
# Générer une fois par poste admin
|
||||
mkdir -p ~/.config/sops/age
|
||||
age-keygen -o ~/.config/sops/age/keys.txt
|
||||
# → noter la clé publique et l'ajouter dans .sops.yaml
|
||||
```
|
||||
|
||||
> La clé privée (`~/.config/sops/age/keys.txt`) ne doit JAMAIS être committée.
|
||||
|
||||
---
|
||||
|
||||
## Procédure d'installation (premier déploiement)
|
||||
|
||||
### 1. Préparer la clé USB
|
||||
|
||||
```bash
|
||||
# Flasher l'ISO Talos directement (pas Ventoy)
|
||||
sudo dd if=/mnt/data/talos/1.13/metal-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
|
||||
> Utiliser le metal ISO directement (pas Ventoy) — Talos doit être le seul bootable sur la clé.
|
||||
|
||||
### 2. Générer les configs
|
||||
|
||||
```bash
|
||||
cd talos/
|
||||
|
||||
# Générer les secrets (une seule fois)
|
||||
talhelper gensecret | sops --filename-override talsecret.sops.yaml --encrypt /dev/stdin > talsecret.sops.yaml
|
||||
|
||||
# Générer les configs nœuds
|
||||
talhelper genconfig
|
||||
```
|
||||
|
||||
### 3. Booter chaque nœud en maintenance mode
|
||||
|
||||
1. Brancher la clé USB sur le nœud, allumer
|
||||
2. Le nœud prend une IP DHCP dans `192.168.10.50-99` (dnsmasq sur storage-01)
|
||||
3. Vérifier les disques **avant** d'appliquer la config :
|
||||
|
||||
```bash
|
||||
export TALOSCONFIG=talos/clusterconfig/talosconfig
|
||||
talosctl get disks --insecure --nodes <IP-DHCP>
|
||||
```
|
||||
|
||||
> ⚠️ Les ThinkCentre ont un NVMe (`nvme0n1`), pas un SATA. Vérifier que `installDisk: /dev/nvme0n1` dans `talconfig.yaml`.
|
||||
|
||||
### 4. Appliquer la config
|
||||
|
||||
```bash
|
||||
# compute-01
|
||||
talosctl apply-config --insecure --nodes <IP-DHCP> --file talos/clusterconfig/funk-compute-01.yaml
|
||||
|
||||
# compute-02
|
||||
talosctl apply-config --insecure --nodes <IP-DHCP> --file talos/clusterconfig/funk-compute-02.yaml
|
||||
|
||||
# compute-03
|
||||
talosctl apply-config --insecure --nodes <IP-DHCP> --file talos/clusterconfig/funk-compute-03.yaml
|
||||
```
|
||||
|
||||
Talos installe sur nvme0n1 et reboot automatiquement → nœud disponible sur son IP statique.
|
||||
|
||||
> Si le nœud reboot sur un ancien OS (EFI entries) : lancer `talosctl upgrade --image ghcr.io/siderolabs/installer:v1.13.0 --nodes <IP>` puis retirer la clé USB pendant le reboot.
|
||||
|
||||
### 5. Bootstrap etcd (une seule fois sur compute-01)
|
||||
|
||||
```bash
|
||||
talosctl bootstrap --nodes 192.168.10.11
|
||||
```
|
||||
|
||||
> ⚠️ Ne lancer qu'une seule fois — relancer sur un cluster existant casse etcd.
|
||||
|
||||
### 6. Récupérer le kubeconfig
|
||||
|
||||
```bash
|
||||
talosctl kubeconfig --nodes 192.168.10.11 ~/.kube/config
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
### 7. Flannel (CNI) — intégré à Talos
|
||||
|
||||
Talos v1.13 inclut Flannel nativement dans `kube-system` avec le bon CIDR `10.42.0.0/16`.
|
||||
|
||||
**Ne pas appliquer** le manifest Flannel externe — ça crée un conflit de deux DaemonSets qui mettent les pods en CrashLoopBackOff.
|
||||
|
||||
Si vous avez appliqué le manifest externe par erreur :
|
||||
```bash
|
||||
kubectl delete namespace kube-flannel
|
||||
# Corriger la ClusterRoleBinding pour pointer vers kube-system
|
||||
kubectl patch clusterrolebinding flannel --type='json' \
|
||||
-p='[{"op": "replace", "path": "/subjects/0/namespace", "value": "kube-system"}]'
|
||||
kubectl delete pod -n kube-system -l app=flannel
|
||||
```
|
||||
|
||||
Les nœuds passent `NotReady` → `Ready` en ~1 minute après le bootstrap.
|
||||
|
||||
### 8. Installer MetalLB
|
||||
|
||||
```bash
|
||||
helm repo add metallb https://metallb.github.io/metallb
|
||||
helm repo update
|
||||
kubectl create namespace metallb-system
|
||||
helm install metallb metallb/metallb --namespace metallb-system
|
||||
# Attendre que les pods soient Running
|
||||
kubectl get pods -n metallb-system -w
|
||||
```
|
||||
|
||||
Configurer le pool d'IPs :
|
||||
```bash
|
||||
cat <<'EOF' | kubectl apply -f -
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: funk-pool
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- 192.168.10.200-192.168.10.230
|
||||
---
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: funk-l2
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
ipAddressPools:
|
||||
- funk-pool
|
||||
EOF
|
||||
```
|
||||
|
||||
### 9. Installer Traefik
|
||||
|
||||
```bash
|
||||
helm repo add traefik https://traefik.github.io/charts
|
||||
helm repo update
|
||||
kubectl create namespace infra
|
||||
helm install traefik traefik/traefik \
|
||||
--namespace infra \
|
||||
--set service.type=LoadBalancer \
|
||||
--set service.loadBalancerIP=192.168.10.200 \
|
||||
--set ports.web.port=80 \
|
||||
--set ports.websecure.port=443 \
|
||||
--set ingressRoute.dashboard.enabled=true \
|
||||
--set logs.general.level=INFO
|
||||
```
|
||||
|
||||
Vérifier que l'IP `192.168.10.200` est assignée :
|
||||
```bash
|
||||
kubectl get svc -n infra traefik
|
||||
```
|
||||
|
||||
### 10. DNS wildcard *.lab.local
|
||||
|
||||
Le rôle dnsmasq Ansible configure automatiquement `address=/.lab.local/192.168.10.200`.
|
||||
Après `ansible-playbook --tags dnsmasq`, tout nom `<service>.lab.local` résout vers Traefik.
|
||||
|
||||
---
|
||||
|
||||
## Administration quotidienne
|
||||
|
||||
### État du cluster
|
||||
|
||||
```bash
|
||||
kubectl get nodes -o wide
|
||||
kubectl get pods -A
|
||||
talosctl --nodes 192.168.10.11,192.168.10.12,192.168.10.13 service
|
||||
```
|
||||
|
||||
### Logs et diagnostics
|
||||
|
||||
```bash
|
||||
# Logs d'un service Talos
|
||||
talosctl --nodes 192.168.10.11 dmesg | tail -30
|
||||
talosctl --nodes 192.168.10.11 service etcd
|
||||
|
||||
# Logs d'un pod k8s
|
||||
kubectl logs -n <namespace> <pod>
|
||||
|
||||
# Events k8s
|
||||
kubectl get events -A --sort-by='.lastTimestamp'
|
||||
```
|
||||
|
||||
### Reboot / shutdown d'un nœud
|
||||
|
||||
```bash
|
||||
# Reboot propre
|
||||
talosctl --nodes 192.168.10.12 reboot
|
||||
|
||||
# Shutdown
|
||||
talosctl --nodes 192.168.10.12 shutdown
|
||||
|
||||
# Tous les workers
|
||||
talosctl --nodes 192.168.10.12,192.168.10.13 reboot
|
||||
```
|
||||
|
||||
### Mise à jour de la config d'un nœud
|
||||
|
||||
```bash
|
||||
cd talos/
|
||||
# 1. Modifier talconfig.yaml
|
||||
# 2. Régénérer
|
||||
talhelper genconfig
|
||||
# 3. Appliquer (sans --insecure cette fois)
|
||||
talosctl --nodes 192.168.10.11 apply-config --file clusterconfig/funk-compute-01.yaml
|
||||
```
|
||||
|
||||
### Backup etcd (critique — single control-plane)
|
||||
|
||||
```bash
|
||||
talosctl --nodes 192.168.10.11 etcd snapshot /tmp/etcd-backup-$(date +%Y%m%d).db
|
||||
# Copier vers storage-01 RAID5
|
||||
scp /tmp/etcd-*.db storage-01:/srv/data/backups/etcd/
|
||||
```
|
||||
|
||||
> À planifier en cron hebdomadaire au minimum.
|
||||
|
||||
### Variables d'environnement utiles
|
||||
|
||||
```bash
|
||||
export TALOSCONFIG=~/Projets/lab/talos/clusterconfig/talosconfig
|
||||
export KUBECONFIG=~/.kube/config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MACs des nœuds (baux DHCP statiques)
|
||||
|
||||
| Nœud | MAC | IP fixe |
|
||||
|---|---|---|
|
||||
| compute-01 | `6c:4b:90:82:8e:47` | 192.168.10.11 |
|
||||
| compute-02 | `6c:4b:90:cf:7f:c5` | 192.168.10.12 |
|
||||
| compute-03 | `6c:4b:90:b6:49:20` | 192.168.10.13 |
|
||||
|
||||
---
|
||||
|
||||
## Points d'attention
|
||||
|
||||
| Sujet | Détail |
|
||||
|---|---|
|
||||
| installDisk | Toujours `nvme0n1` sur ces ThinkCentre — ne pas mettre `/dev/sda` (clé USB) |
|
||||
| USB ISO | Flasher le metal ISO directement avec `dd`, pas Ventoy |
|
||||
| Bootstrap | Une seule fois sur compute-01, jamais relancer |
|
||||
| etcd backup | Critique sur single control-plane — planifier en cron |
|
||||
| RAM workers | 8 GB sur compute-02/03 — toujours définir `resources.requests/limits` |
|
||||
| VIP | Ne pas configurer de VIP = même IP que le nœud — inutile et cassant |
|
||||
| Flannel externe | Ne PAS appliquer le manifest Flannel officiel — Talos l'inclut nativement |
|
||||
| ClusterRoleBinding flannel | Doit pointer vers `kube-system`, pas `kube-flannel` |
|
||||
| MetalLB webhook | Nécessite que Flannel soit 100% healthy avant d'appliquer IPAddressPool |
|
||||
Loading…
Add table
Add a link
Reference in a new issue