Aller au contenu

TechniquePratique

Labs, checklist de mise en production et aide-mémoire MetricsQL

Pour : ingénieurs et SREPrérequis : Avoir lu les leçons 1 à 5 du parcours et disposer de Docker avec Compose v2.

Lab 1 : déployer la pile et ingérer les premières métriques

Section intitulée « Lab 1 : déployer la pile et ingérer les premières métriques »

Objectif : déployer la pile Docker Compose complète (VictoriaMetrics, OTel Collector, vmagent, vmalert, Alertmanager, Grafana, simulateur), vérifier les métriques dans vmui et dans Grafana. Durée estimée : 30 minutes. Niveau : débutant. Prérequis : Docker avec Compose v2, environ 8 Go de RAM libres (la pile compte neuf conteneurs).

  1. Clonez le dépôt et démarrez la pile :

    Fenêtre de terminal
    git clone https://forge.erythix.tech/labstraining/labs/VMLLM.git
    cd VMLLM
    docker compose up -d --build
    docker compose ps # vérifier que tous les services tournent
  2. Le simulateur démarre avec la pile (service llm-simulator, 10 requêtes par seconde, 3 modèles) et expose ses métriques sur le port 9100. Vérifiez qu’il produit des séries :

    Fenêtre de terminal
    curl -s http://localhost:9100/metrics | grep llm_requests_total | head
  3. Vérifiez l’ingestion dans vmui (http://localhost:8428/vmui) avec la requête :

    sum by (model) (rate(llm_requests_total[1m])) * 60
  4. Ouvrez Grafana (http://localhost:3000). Les tableaux de bord sont provisionnés au démarrage : ouvrez le tableau de bord de production (identifiant llm-prod), défini dans grafana/dashboards/dashboard-llm-prod.json.

Objectif : à partir des métriques du simulateur, écrire les cinq requêtes de production essentielles, les valider dans vmui, puis les ajouter à un tableau de bord Grafana. Durée estimée : 40 minutes. Niveau : intermédiaire. Corrigé : solutions/lab2-queries.md dans le dépôt.

  1. Coût total par modèle sur les dernières 24 heures.
  2. TTFT P95 par fournisseur (histogram_quantile).
  3. Anomalie du score de recherche RAG (z-score par rapport à une référence de 7 jours, voir le module 7 ; la référence exige sept jours d’historique, sur une pile neuve le z-score reste partiel).
  4. Les 5 tenants les plus coûteux (topk + increase).
  5. Efficacité GPU : tokens de sortie par joule (division entre métriques).

Objectif : configurer l’alerte RAGIndexDrift dans vmalert, la tester en injectant une dégradation du score, vérifier la notification dans un canal Slack de test. Durée estimée : 25 minutes, plus les 15 minutes du for: de l’alerte. Niveau : intermédiaire.

  1. Adaptez alerts/llm-quality.yml et alertmanager.yml du dépôt, avec l’URL de votre webhook Slack de test.
  2. Rechargez les règles par une requête GET : curl http://localhost:8880/-/reload (ou docker compose restart vmalert). Rechargez Alertmanager avec docker compose restart alertmanager.
  3. Injectez une dégradation du score : make lab3-drift (ou bash scripts/triggers/01_rag_index_drift.sh). Le script arrête le simulateur nominal et en relance un avec --score-drift 0.5, qui divise les scores par deux.
  4. Dans l’interface de vmalert (http://localhost:8880), vérifiez que l’alerte passe en attente puis se déclenche au bout de 15 minutes.
  5. Confirmez la réception dans Slack, puis restaurez le simulateur nominal : make lab3-restore (ou bash scripts/triggers/_restore.sh).

Objectif : ajouter les règles d’enregistrement essentielles et comparer les temps de réponse des requêtes avec et sans elles, via l’API VictoriaMetrics. Durée estimée : 20 minutes. Niveau : avancé. Corrigé : solutions/lab4-recording-rules.md dans le dépôt (sans retenir ses gains chiffrés, voir l’encadré sur le lab VMLLM).

  1. Copiez les règles du module 10 dans alerts/recording-rules.yml (le dépôt en contient une version proche), puis rechargez vmalert : curl http://localhost:8880/-/reload.

  2. Attendez deux ou trois intervalles d’évaluation, puis vérifiez dans vmui que la série llm:request_duration_ms:p99_5m existe.

  3. Mesurez la requête complète :

    Fenêtre de terminal
    time curl -sG 'http://localhost:8428/api/v1/query' \
    --data-urlencode 'query=histogram_quantile(0.99, sum by (le, model, pipeline_id) (rate(llm_request_duration_ms_bucket[5m])))' \
    > /dev/null
  4. Mesurez la série précalculée :

    Fenêtre de terminal
    time curl -sG 'http://localhost:8428/api/v1/query' \
    --data-urlencode 'query=llm:request_duration_ms:p99_5m' \
    > /dev/null
  5. Répétez chaque mesure une dizaine de fois et comparez les médianes. Faites de même avec increase(llm_cost_usd_total[24h]) et llm:cost_usd:increase24h.

  6. Interprétez : la cardinalité du simulateur est faible (quelques centaines de séries), l’écart absolu sera donc modeste. Il croît avec le nombre de séries lues par la requête complète ; c’est ce rapport que vous devez mesurer sur votre propre instance avant de généraliser les règles.

  • VictoriaMetrics est déployé avec -storageDataPath sur un SSD persistant.
  • La rétention est définie : -retentionPeriod sur chaque instance et -retentionFilter uniquement si vous utilisez la version Enterprise (sinon, des instances distinctes par rétention).
  • vmbackup est configuré vers un stockage compatible S3 (test de restauration réussi).
  • vmagent ou l’OTel Collector ingère les quatre catégories de métriques (qualité, latence, coût, GPU).
  • Les six règles vmalert de production sont chargées et testées (alerte de test reçue dans Slack ou Teams).
  • Au moins deux règles d’enregistrement sont actives pour les coûts.
  • Le tableau de bord Grafana de production est importé et ses variables fonctionnent.
  • vmauth est configuré avec une authentification (basique ou par jeton ; l’acceptation de connexions mTLS est une fonction de vmauth Enterprise), jamais d’accès non authentifié.
  • La cardinalité totale est mesurée et documentée (/api/v1/status/tsdb).
  • Les labels à risque (trace_id, session_id) sont supprimés par réétiquetage.
  • Une alerte d’autosurveillance VictoriaMetricsDown est active.
  • Un runbook d’incident existe (qui appeler, comment restaurer, comment monter en charge).
  • Des utilisateurs Grafana en lecture seule existent pour les rôles non techniques (direction, finance).
  • La cardinalité s’est stabilisée (pas de croissance linéaire).
  • La consommation disque est mesurée et projetée à 90 jours.
  • Au moins une alerte réelle a été reçue et le routage confirmé.
  • La latence P99 des requêtes Grafana est mesurée et comparée à la cible que vous avez fixée (par exemple moins d’une seconde).
  • Un test de restauration vmbackup a été mené sur une instance de test.
  • Les seuils d’alerte ont été revus avec les équipes (faux positifs ?).
  • Les requêtes MetricsQL personnalisées des équipes sont documentées.
# Coût horaire par modèle
sum by (model) (increase(llm_cost_usd_total[1h]))
# Coût quotidien projeté
sum(rate(llm_cost_usd_total[30m])) * 86400
# Les 10 tenants les plus coûteux sur 24 h
topk(10, sum by (tenant_id) (increase(llm_cost_usd_total[24h])))
# Coût moyen par requête (règle d'enregistrement recommandée)
sum(rate(llm_cost_usd_total[5m])) / sum(rate(llm_requests_total[5m]))
# Latence de bout en bout P99 par modèle
histogram_quantile(0.99, sum by (le, model) (rate(llm_request_duration_ms_bucket[5m])))
# TTFT P95 (streaming)
histogram_quantile(0.95, sum by (le, model) (rate(llm_ttft_ms_bucket[5m])))
# Taux d'erreur global
sum(rate(llm_errors_total[5m])) / sum(rate(llm_requests_total[5m]))
# Débit de tokens de sortie par modèle (tokens/s)
sum by (model) (rate(llm_tokens_total{type='output'}[1m]))
# Z-score du score de recherche par rapport à une référence de 7 jours
# (remplace anomaly_score(), qui n'est pas une fonction MetricsQL)
(
avg by (index_id) (rag_retrieval_score_avg)
- avg_over_time(avg by (index_id) (rag_retrieval_score_avg)[7d:1h])
)
/ stddev_over_time(avg by (index_id) (rag_retrieval_score_avg)[7d:1h])
# Si vous utilisez vmanomaly (Enterprise) : interroger la métrique qu'il écrit
anomaly_score > 1
# Pression de contexte P95 par pipeline
histogram_quantile(0.95, sum by (le, pipeline_id) (rate(rag_context_pressure_ratio_bucket[5m])))
# Part de chunks abandonnés (exige un compteur rag_chunks_retrieved_total)
sum by (pipeline_id) (rate(rag_chunks_dropped_total[5m]))
/ sum by (pipeline_id) (rate(rag_chunks_retrieved_total[5m]))
# Utilisation GPU moyenne par nœud et par GPU
avg by (node, gpu) (DCGM_FI_DEV_GPU_UTIL)
# Saturation de la VRAM (alerter avant l'OOM)
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
# Tokens de sortie par joule (global ; les séries DCGM n'ont pas de label de modèle LLM)
sum(rate(llm_tokens_total{type='output'}[5m])) / sum(DCGM_FI_DEV_POWER_USAGE)
# File d'attente vLLM
vllm:num_requests_waiting
# Médiane robuste (ignore les pics)
median_over_time(rag_retrieval_score_avg[30m])
# Ouverture, plus haut, plus bas, clôture des scores RAG (chandeliers)
rollup_candlestick(rag_retrieval_score_avg[1h])
# Trafic quasi nul : la pente d'un compteur est un débit par seconde
abs(deriv(llm_requests_total[10m])) < 0.01
# Comparaison d'une semaine sur l'autre
sum(rate(llm_cost_usd_total[1h]))
/ sum(rate(llm_cost_usd_total[1h] offset 7d))
RessourceURL
Documentation VictoriaMetricsdocs.victoriametrics.com
Référence MetricsQLdocs.victoriametrics.com/metricsql
vmagentdocs.victoriametrics.com/vmagent
vmalertdocs.victoriametrics.com/vmalert
vmauthdocs.victoriametrics.com/vmauth
vmanomalydocs.victoriametrics.com/anomaly-detection
Code source VictoriaMetricsgithub.com/VictoriaMetrics/VictoriaMetrics
OutilURL
OTel Collector Contribgithub.com/open-telemetry/opentelemetry-collector-contrib
DCGM exporter (GPU)github.com/NVIDIA/dcgm-exporter
Métriques vLLMdocs.vllm.ai (section « Metrics »)
Métriques du proxy LiteLLMdocs.litellm.ai/docs/proxy/prometheus
Plugin Grafana VictoriaMetricsgrafana.com/grafana/plugins/victoriametrics-metrics-datasource
prometheus_client pour Pythongithub.com/prometheus/client_python