Aller au contenu principal
ObservabilitéPrometheusGrafanaMonitoringSRE

Prometheus : architecture et PromQL

30 min de lecture Observabilité Moderne — Chapitre 3

Architecture interne de Prometheus (TSDB, scraping, service discovery), PromQL avancé et recording/alerting rules pour le monitoring production.

Introduction — Pourquoi Prometheus domine le monitoring Cloud Native

Prometheus est le moteur de métriques le plus déployé dans l’écosystème Kubernetes. Créé chez SoundCloud en 2012, 2ème projet CNCF gradué après Kubernetes, il est aujourd’hui le standard de facto pour le monitoring des infrastructures Cloud Native.

Sa force : un modèle pull (il va chercher les métriques), un langage de requête puissant (PromQL), et une intégration native avec Kubernetes via le service discovery. Contrairement aux solutions push (StatsD, Datadog Agent), Prometheus sait immédiatement si une target est down — le scrape échoue, point.

Ce cours couvre l’architecture interne, la maîtrise de PromQL (le langage qui fait toute la différence), et les recording/alerting rules qui transforment des métriques brutes en monitoring intelligent.

🔥 Cas réel : Une équipe SRE a migré de Nagios/Graphite vers Prometheus en 3 semaines. Avant, ils avaient 200 checks statiques et des faux positifs quotidiens. Avec Prometheus et des alertes basées sur des rates et des prédictions, les faux positifs ont chuté de 90% et le MTTR (temps de résolution) est passé de 45 minutes à 12 minutes.


Architecture interne de Prometheus

Prometheus fonctionne en trois couches : le scraping qui collecte les métriques, la TSDB (Time Series Database) qui les stocke, et le moteur de requêtes qui les exploite.

Le scraping est piloté par le service discovery. Sur Kubernetes, Prometheus découvre automatiquement les pods, services et nodes via l’API. Il suffit d’annoter un pod avec prometheus.io/scrape: "true" pour qu’il soit collecté.

La TSDB stocke les données en blocs de 2 heures. Les samples arrivent d’abord dans le head block en mémoire (protégé par un WAL — Write-Ahead Log), puis sont compactés sur disque. Chaque sample ne consomme que ~1.5-2 bytes grâce à une compression agressive.

Voici une configuration de scraping Kubernetes avec service discovery automatique :

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: prod-eu-west
    environment: production

scrape_configs:
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: (.+)
        replacement: ${1}
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        target_label: pod

💡 Tip DevOps : Pour estimer le stockage, calcule : nb_series × (86400 / scrape_interval) × 2 bytes × nb_jours_retention. Avec 100 000 séries, scrape à 15s, rétention 30 jours : ~33 GB. C’est très raisonnable.

⚠️ Attention : Prometheus est conçu pour un seul nœud. Pour le scaling horizontal et la haute disponibilité, utilise Thanos ou Grafana Mimir par-dessus. Prometheus seul est un SPOF — acceptable en staging, pas en production critique.


PromQL : le langage qui fait la différence

PromQL est ce qui distingue Prometheus des autres solutions de monitoring. C’est un langage fonctionnel puissant, mais avec une courbe d’apprentissage. Maîtrise ces fonctions essentielles et tu couvriras 95% des cas d’usage.

Les fonctions les plus importantes à connaître — rate() pour le débit des counters, histogram_quantile() pour les percentiles, et predict_linear() pour l’anticipation :

# rate() — Taux de changement/seconde d'un counter (lissé sur 5min)
rate(http_requests_total[5m])

# increase() — Augmentation absolue sur une fenêtre
increase(http_requests_total[1h])
# "Combien de requêtes dans la dernière heure"

# histogram_quantile() — Percentiles de latence
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)

# predict_linear() — Prédiction linéaire
predict_linear(node_filesystem_avail_bytes[6h], 24*3600)
# "À ce rythme, combien d'espace disque dans 24h ?"

# absent() — Détection de target down
absent(up{job="my-service"})

🧠 À retenir : rate() vs irate()rate() lisse sur toute la fenêtre (5min = moyenne sur 20 samples), idéal pour les alertes et dashboards. irate() calcule entre les 2 derniers samples, plus réactif mais plus bruité — utile pour le debug en temps réel, pas pour les alertes.

Les agrégations et les requêtes avancées sont ce qui rend PromQL réellement exploitable en production :

# Taux d'erreur par service (le KPI le plus important)
sum(rate(http_requests_total{code=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
* 100

# CPU utilisé en pourcentage
100 - (
  avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
  * 100
)

# Prédiction : disque plein dans 4 jours ?
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4*24*3600) < 0

# Comparaison avec la semaine précédente
sum(rate(http_requests_total[5m]))
/
sum(rate(http_requests_total[5m] offset 7d))

# Détection d'anomalie par z-score
(
  rate(http_requests_total[5m])
  - avg_over_time(rate(http_requests_total[5m])[1d:5m])
)
/ stddev_over_time(rate(http_requests_total[5m])[1d:5m])
# |z-score| > 3 = anomalie probable

🔥 Cas réel : La requête predict_linear() a sauvé un e-commerce la veille du Black Friday. L’alerte “disque plein dans 3 jours” s’est déclenchée le mercredi — ils ont étendu le volume EBS à temps. Sans cette prédiction, le service de commandes se serait arrêté le vendredi à 14h, en plein pic de trafic.

💡 Tip DevOps : L’Apdex score est un KPI business puissant que tu peux calculer en PromQL. Il mesure la satisfaction utilisateur sur une échelle de 0 à 1. Formule : (satisfait + tolérable/2) / total. Un score > 0.94 est excellent, < 0.85 nécessite une action.


Recording Rules : pré-calculer pour performer

Les recording rules pré-calculent des requêtes PromQL coûteuses et stockent le résultat comme une nouvelle time series. Le dashboard qui chargeait en 8 secondes charge maintenant en 200ms.

La convention de nommage est level:metric:operations — par exemple service:http_requests:rate5m indique une agrégation au niveau service, métrique http_requests, opération rate sur 5 minutes :

groups:
  - name: http_performance
    interval: 30s
    rules:
      - record: service:http_requests:rate5m
        expr: sum(rate(http_requests_total[5m])) by (service)

      - record: service:http_errors:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{code=~"5.."}[5m])) by (service)
          /
          sum(rate(http_requests_total[5m])) by (service)

      - record: service:http_latency:p99_5m
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          )

  - name: node_performance
    interval: 1m
    rules:
      - record: instance:node_cpu:utilization_ratio
        expr: 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)

      - record: instance:node_memory:utilization_ratio
        expr: 1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

🧠 À retenir : Les recording rules sont aussi la base des alerting rules — alerte sur service:http_errors:ratio_rate5m > 0.05 plutôt que de recalculer la requête complète à chaque évaluation. C’est plus performant et plus lisible.


Alerting Rules : des alertes intelligentes

Les alerting rules transforment tes métriques en alertes actionnables. La clé : chaque alerte doit avoir un for (durée minimum avant déclenchement), une severity, et une runbook_url qui explique quoi faire.

Voici un set d’alertes production couvrant les cas critiques — taux d’erreur, latence, prédiction disque et crashloops :

groups:
  - name: application_alerts
    rules:
      - alert: HighErrorRate
        expr: service:http_errors:ratio_rate5m > 0.05
        for: 5m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "Erreur rate {{ $value | humanizePercentage }} sur {{ $labels.service }}"
          runbook_url: "https://wiki.example.com/runbooks/high-error-rate"

      - alert: HighLatency
        expr: service:http_latency:p99_5m > 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "p99 latence > 1s sur {{ $labels.service }}"

      - alert: DiskWillFillIn4Days
        expr: predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4*24*3600) < 0
        for: 30m
        labels:
          severity: warning
          team: infra
        annotations:
          summary: "Disque {{ $labels.instance }} sera plein dans ~4 jours"

      - alert: PodCrashLooping
        expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 15 > 0
        for: 5m
        labels:
          severity: critical
          team: platform
        annotations:
          summary: "{{ $labels.namespace }}/{{ $labels.pod }} en CrashLoop"

⚠️ Attention : Le for est crucial. Sans lui, un spike de 10 secondes déclenche l’alerte. Avec for: 5m, le problème doit persister 5 minutes — ça élimine les faux positifs des pics transitoires. Mais ne mets pas for: 30m sur une alerte critique, sinon tu seras notifié trop tard.


Bonnes pratiques et pièges à éviter

Bonnes pratiques :

  • Utilise rate() sur au moins 4× l’intervalle de scrape (scrape à 15s → rate(...[1m]) minimum, [5m] recommandé)
  • Crée des recording rules pour toute requête utilisée dans un dashboard ou une alerte
  • Chaque alerte doit avoir : severity, team, runbook_url, et des annotations avec le contexte
  • Applique les frameworks RED (Rate, Errors, Duration) pour les services et USE (Utilization, Saturation, Errors) pour les ressources
  • Sépare les alertes par criticité : critical réveille l’astreinte, warning crée un ticket

Pièges courants :

  • Appliquer rate() sur un gauge → résultat absurde (utilise deriv() pour les gauges)
  • Oublier le by (le) dans histogram_quantile() → erreur silencieuse
  • Trop de labels = explosion de cardinalité → Prometheus ralentit puis OOM. Jamais de user_id ou request_id comme label
  • Alerter sur des valeurs instantanées au lieu de rates → faux positifs garantis
  • Scrape interval trop court (< 10s) → surcharge sans bénéfice réel

🔥 Cas réel : Un dev a ajouté un label user_email sur une métrique HTTP. Avec 500 000 utilisateurs actifs, ça a créé 500 000 time series par endpoint × méthode × code. Prometheus a consommé 32 GB de RAM en quelques heures avant de crasher. La cardinalité des labels est le piège numéro 1.


Résumé

Prometheus est le pilier métriques de ta stack d’observabilité. Son modèle pull avec service discovery Kubernetes rend la collecte automatique. PromQL est un langage puissant dont les fonctions clés — rate(), histogram_quantile(), predict_linear() — couvrent 95% des besoins. Les recording rules optimisent les performances, les alerting rules transforment les métriques en actions concrètes.

🧠 À retenir : La qualité de ton monitoring dépend de trois choses — des métriques bien nommées (semantic conventions), des requêtes PromQL correctes (pas de rate sur un gauge), et des alertes actionnables (avec runbook et contexte). Le reste, c’est du plumbing.

Articles liés