Funk-lab/admin/k8s/monitoring.md
alkatrazz 3a013834aa fix(webhook): réponse HTTP 200 immédiate + ask-agent asynchrone
BrokenPipeError corrigé : send_response(200) envoyé avant subprocess.
ask-agent lancé en threading.Thread daemon — AlertManager n'attend plus.
NO_COLOR=1 + TERM=dumb pour éviter les codes ANSI dans les logs.

docs: alertmanager-webhook.md — architecture, flux, test, dépannage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:58:39 +02:00

15 KiB
Raw Permalink Blame History

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 :

Ajouter un dashboard custom (GitOps)

  1. Créer k8s/infra/monitoring/dashboards/mon-dashboard.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 ... }
  1. 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

# 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).

# É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).

# 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) 4085
rocm_gpu_utilization_percent Utilisation GPU (%) 0100
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()
# 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, répond 200 immédiatement, et lance ask-agent monitor en arrière-plan. Hermes (profil monitor) analyse et répond dans ses logs.

Documentation complète : admin/ia/alertmanager-webhook.md

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

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.yamlgrafana.adminPasswordgit 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

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

# 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 additionalScrapeConfigsgit push.

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

# 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

# É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