← Tous les cours / Prometheus & métriques Cours
00 — VUE D'ENSEMBLE

Prometheus : la collecte active

Prometheus va chercher les métriques lui-même — il ne les reçoit pas. Une décision d'architecture qui simplifie la gestion de centaines de cibles.

Contrairement à StatsD ou Telegraf qui reçoivent des métriques (modèle push), Prometheus les collecte lui-même à intervalle régulier — c'est le modèle pull. Toutes les 15 ou 30 secondes, il envoie une requête HTTP GET à chaque cible configurée et récupère les métriques exposées.

Scrape
L'opération de collecte s'appelle un scrape. Chaque cible expose un endpoint HTTP (par défaut /metrics) que Prometheus interroge selon la configuration scrape_interval.

Ce modèle a des avantages concrets :

Les composants de la stack

Application
/metrics
←scrape
Prometheus
TSDB + PromQL
query→
Grafana
Dashboards
Prometheus
alertes→
Alertmanager
Email / Slack / PD

Prometheus server collecte les métriques, les stocke dans sa base de données temporelle (TSDB), évalue les règles d'alerte et expose une API PromQL.

Exporters — processus séparés qui traduisent les métriques d'un système tiers (OS, base de données, proxy…) au format Prometheus. Prometheus les scrape exactement comme une application instrumentée.

Alertmanager reçoit les alertes de Prometheus, les déduplique, les groupe et les route vers les bons canaux (email, Slack, PagerDuty, Webhook…).

💡 Pushgateway — l'exception au modèle pull : certains jobs éphémères (scripts batch, tâches CI) ne peuvent pas être scrappés car ils s'arrêtent avant l'intervalle de scrape. Le Pushgateway est un relais : le job y pousse ses métriques, Prometheus scrape le Pushgateway. À utiliser avec parcimonie — uniquement pour les jobs batch qui ne tournent pas en continu.

Le format d'exposition

Le format texte que Prometheus attend est simple et lisible :

# HELP http_requests_total Nombre de requêtes HTTP reçues
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1234
http_requests_total{method="POST",status="201"} 87
http_requests_total{method="GET",status="404"} 12

# HELP http_request_duration_seconds Latence des requêtes HTTP
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 240
http_request_duration_seconds_bucket{le="0.5"} 310
http_request_duration_seconds_bucket{le="1"}   320
http_request_duration_seconds_bucket{le="+Inf"} 322
http_request_duration_seconds_sum 45.7
http_request_duration_seconds_count 322

Chaque ligne est une série temporelle : un nom de métrique plus un ensemble de labels entre accolades. Prometheus horodate chaque valeur au moment du scrape et la stocke dans sa TSDB.

🔑 Clé de lecture : les labels sont les dimensions de tes métriques. http_requests_total{method="GET",status="200"} et http_requests_total{method="POST",status="500"} sont deux séries distinctes, même si elles partagent le même nom de métrique.
01 — LES 4 TYPES

Les quatre types de métriques

Counter, Gauge, Histogram, Summary — quatre patterns pour quatre comportements. Choisir le bon dès le départ évite beaucoup de ré-instrumentation.

Le standard OpenMetrics définit quatre types de métriques. Prometheus les implémente tous. Choisir le bon type n'est pas anodin : il détermine quelles fonctions PromQL tu peux utiliser et comment les données s'aggrègent entre plusieurs instances.

Counter
Valeur qui ne fait qu'augmenter. Elle revient à 0 uniquement si le processus redémarre. Tu ne lis jamais sa valeur brute — tu appliques rate() pour obtenir le taux par seconde.

Exemples : requêtes reçues, octets transmis, erreurs depuis le démarrage, tâches traitées.
Gauge
Valeur instantanée qui peut monter et descendre. Elle représente un état à un instant donné. Tu peux la lire directement sans transformation.

Exemples : mémoire utilisée, connexions actives, température CPU, taille d'une file d'attente.
Histogram
Distribue les observations dans des buckets prédéfinis et les accumule. Permet d'estimer des percentiles côté Prometheus via histogram_quantile(). Agrégeable entre instances.

Exemples : latence des requêtes HTTP, taille des payloads, durée des transactions.
Summary
Calcule les quantiles côté client sur une fenêtre glissante. Précis, mais les quantiles ne peuvent pas être agrégés entre plusieurs instances.

Exemples : latence P99 d'un composant qui tourne en une seule instance.

Counter vs Gauge — la règle du redémarrage

Si la valeur peut repartir à zéro naturellement dans le domaine métier (le nombre de connexions actives monte et descend), c'est un Gauge. Si elle ne peut que croître jusqu'au prochain redémarrage (le nombre total de requêtes traitées), c'est un Counter.

⚠️ Erreur classique : modéliser un compteur cumulatif comme un Gauge. Un Gauge qui compterait le "total de requêtes" perdrait son historique à chaque redémarrage. Un Counter est fait pour ça — rate() gère le reset automatiquement en détectant la chute à zéro.

Histogram vs Summary

CritèreHistogramSummary
Où sont calculés les quantilesCôté serveur (PromQL)Côté client (SDK)
Agrégation multi-instancesPossibleImpossible
Précision des quantilesEstimée (dépend des buckets)Exacte
Buckets à configurer à l'avanceOuiNon
RecommandationPréféréCas spécifiques
💡 Choisis Histogram dans la quasi-totalité des cas. Il est agrégeable entre instances — essentiel dès que ton service scale horizontalement. Summary ne vaut que si tu as une seule instance et que tu as besoin de quantiles très précis sans approximation.

Le simulateur ci-dessous te permet d'explorer le comportement de chaque type.

Clique sur un type pour voir son comportement.

02 — PROMQL

PromQL : interroger le temps

PromQL traite des séries temporelles, pas des lignes dans une table. Comprendre la différence entre instant vector et range vector change tout.

PromQL opère sur des séries temporelles — des listes de paires (timestamp, valeur) identifiées par un nom de métrique et un ensemble de labels. Comprendre le modèle de données est la clé pour écrire des requêtes correctes.

Instant vector
Un ensemble de séries temporelles, chacune avec une seule valeur au moment de la requête.
http_requests_total → renvoie la valeur actuelle de chaque série correspondante.
Range vector
Un ensemble de séries temporelles avec toutes leurs valeurs dans une fenêtre temporelle.
http_requests_total[5m] → renvoie les 5 dernières minutes de données pour chaque série.

Sélecteurs et filtres

Les labels permettent de filtrer les séries :

# Toutes les séries http_requests_total
http_requests_total

# Seulement les requêtes GET avec status 200
http_requests_total{method="GET", status="200"}

# Requêtes avec status autre que 200
http_requests_total{status!="200"}

# Requêtes dont le path commence par /api/
http_requests_total{path=~"/api/.*"}

# Requêtes dont le path ne commence PAS par /health
http_requests_total{path!~"/health.*"}

Les fonctions essentielles

FonctionEntréeRésultat
rate(v[d])Range vector CounterTaux par seconde (lissé sur la durée)
irate(v[d])Range vector CounterTaux instantané (2 derniers points)
increase(v[d])Range vector CounterAugmentation absolue sur la durée
sum(v)Instant vectorSomme de toutes les séries
avg(v)Instant vectorMoyenne de toutes les séries
max(v) / min(v)Instant vectorMax / Min
topk(k, v)Instant vectorLes k séries avec la plus haute valeur
histogram_quantile(φ, v)Histogram (buckets)Estimation du percentile φ

Agrégation par dimension

Les clauses by et without contrôlent quels labels sont conservés après l'agrégation :

# Taux total de requêtes, agrégé par méthode HTTP
sum(rate(http_requests_total[5m])) by (method)

# Taux par instance, toutes méthodes confondues
sum(rate(http_requests_total[5m])) without (method, status)

# P99 de latence, agrégé sur tous les services
histogram_quantile(0.99,
  sum(rate(http_duration_seconds_bucket[5m])) by (le)
)
🔑 Règle d'or : utilise toujours rate() ou increase() sur un Counter — jamais la valeur brute. La valeur brute inclut les resets et ne peut pas être comparée entre instances. rate(requests_total[5m]) te donne le nombre de requêtes par seconde, lissé sur 5 minutes.
⚠️ Piège irate vs rate : irate est réactif mais bruité (2 points seulement). Pour les dashboards stables, utilise rate. irate n'a de sens que pour les alertes sur des pics très courts.

Explore les exemples ci-dessous pour te familiariser avec la syntaxe.

Sélectionne une requête et clique sur Exécuter pour voir le résultat simulé.

03 — EXPORTERS

Exporters : adapter n'importe quelle source

Un exporter est un traducteur qui convertit les métriques d'un système tiers au format Prometheus. La bibliothèque officielle couvre l'essentiel dès le départ.

La plupart des systèmes que tu veux monitorer (base de données, système d'exploitation, proxy…) n'exposent pas nativement des métriques au format Prometheus. Les exporters comblent ce manque : ce sont des petits processus qui interrogent le système cible, convertissent les données et les exposent sur /metrics.

Exporter
Un processus auxiliaire qui tourne à côté du système qu'il instrumente, interroge son API ou ses fichiers système, et expose les métriques au format texte Prometheus sur un port HTTP dédié.
MySQL
port 3306
→ requête
mysqld_exporter
port 9104
← scrape
Prometheus

Exporters officiels courants

ExporterCiblePortMétriques clés
node_exporterServeur Linux/Unix9100CPU, mémoire, disque, réseau
blackbox_exporterEndpoint HTTP/TCP/ICMP9115Disponibilité, latence, certificat TLS
mysqld_exporterMySQL / MariaDB9104QPS, connexions, slow queries, InnoDB
redis_exporterRedis9121Mémoire, commandes/s, hit rate, connexions
postgres_exporterPostgreSQL9187Connexions, locks, vacuum, taille DB
nginx_exporternginx9113Requêtes/s, connexions actives, codes HTTP
cadvisorConteneurs Docker/k8s8080CPU, mémoire, réseau par conteneur

Instrumentation directe dans le code

Pour tes propres applications, tu n'as pas besoin d'un exporter séparé. Tu intègres directement la bibliothèque cliente Prometheus dans ton code :

# PHP — avec la bibliothèque prometheus/client_php
$registry = CollectorRegistry::getDefault();

// Counter
$counter = $registry->getOrRegisterCounter(
    'http', 'requests_total',
    'Nombre de requêtes HTTP',
    ['method', 'status']
);
$counter->inc(['GET', '200']);

// Histogram
$histogram = $registry->getOrRegisterHistogram(
    'http', 'request_duration_seconds',
    'Latence des requêtes HTTP',
    ['method'],
    [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
);
$histogram->observe(0.042, ['GET']);
🔑 Convention de nommage : les métriques instrumentées directement suivent le pattern namespace_subsystem_name_unit. Exemples : http_requests_total, db_query_duration_seconds, cache_hit_ratio. L'unité est toujours en suffixe (_seconds, _bytes, _total).

Endpoint /metrics dans Symfony

Pour exposer les métriques Prometheus dans une application Symfony, l'approche la plus simple est d'ajouter un contrôleur dédié :

// src/Controller/MetricsController.php
#[Route('/metrics', methods: ['GET'])]
public function metrics(CollectorRegistry $registry): Response
{
    $renderer = new RenderTextFormat();
    return new Response(
        $renderer->render($registry->getMetricFamilySamples()),
        200,
        ['Content-Type' => RenderTextFormat::MIME_TYPE]
    );
}
💡 Sécurise l'endpoint /metrics en production. Il expose des informations internes sur ton application. Restreins l'accès par IP (Prometheus server uniquement) ou par token bearer dans la configuration nginx/Caddy.
04 — ALERTING

Alertes : de l'évaluation à la notification

Une alerte Prometheus passe par trois états avant d'arriver dans ta boîte mail. Comprendre ce pipeline évite les faux positifs et les silences inattendus.

Une alerte Prometheus n'est pas juste une condition — c'est un pipeline avec plusieurs étapes. Comprendre chaque maillon de ce pipeline est ce qui sépare une configuration d'alerting fiable d'une qui produit des notifications manquées ou des faux positifs.

Les règles d'alerte

Les règles d'alerte sont définies en YAML dans des fichiers que Prometheus charge au démarrage :

# rules/api-alerts.yml
groups:
  - name: api
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
            / sum(rate(http_requests_total[5m])) > 0.05
        for: 2m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: "Taux d'erreurs HTTP élevé"
          description: "{{ $value | humanizePercentage }} de requêtes 5xx"

Les trois états d'une alerte

Inactive → Pending → Firing
  • Inactive : l'expression est fausse. Rien ne se passe.
  • Pending : l'expression est vraie, mais on attend la durée for pour confirmer (évite les faux positifs sur des pics courts).
  • Firing : l'expression est restée vraie pendant toute la durée for. Prometheus envoie l'alerte à l'Alertmanager.

Alertmanager

L'Alertmanager reçoit les alertes firing de Prometheus, les traite et les route :

# alertmanager.yml
route:
  group_by: ['alertname', 'team']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'slack-backend'
  routes:
    - match:
        severity: critical
      receiver: 'pagerduty'

receivers:
  - name: 'slack-backend'
    slack_configs:
      - api_url: 'https://hooks.slack.com/...'
        channel: '#alerts-backend'
  - name: 'pagerduty'
    pagerduty_configs:
      - routing_key: '<clé PagerDuty>'
🔑 Grouping : l'Alertmanager regroupe les alertes qui partagent les mêmes labels group_by pour ne pas inonder les canaux quand plusieurs composants tombent en même temps (ex: déploiement cassé = N alertes → 1 notification groupée).

Inhibitions et silences

Inhibitions — une alerte critique peut bloquer les alertes warning associées. Si l'API est entièrement down, inutile d'envoyer "latence élevée" en plus.

Silences — un mécanisme pour taire des alertes pendant une fenêtre de maintenance, sans modifier la config.

Simule ci-dessous la transition d'une alerte à travers ses états.

Bouge le curseur pour faire varier le taux d'erreurs et observe la progression de l'alerte.


0.0%

05 — CARDINALITÉ

Cardinalité : le piège qui fait exploser la mémoire

Chaque combinaison unique de labels crée une série temporelle distincte. Un label à haute cardinalité peut transformer un cluster Prometheus stable en désastre opérationnel.

La cardinalité désigne le nombre de séries temporelles distinctes dans Prometheus. Elle est déterminée par toutes les combinaisons possibles de labels. C'est le facteur numéro un qui impacte la consommation mémoire et les performances des requêtes.

Cardinalité = produit des cardinalités de chaque label
Si une métrique a un label method (5 valeurs) et un label status (10 valeurs), elle peut avoir jusqu'à 5 × 10 = 50 séries. Avec un label user_id sur 1 million d'utilisateurs : 5 × 10 × 1 000 000 = 50 millions de séries.

Bons et mauvais labels

✓ Labels à faible cardinalité

  • method : GET, POST, PUT, DELETE… (5–10 valeurs)
  • status : 200, 404, 500… (10–20 valeurs)
  • service : api, worker, scheduler… (< 50 valeurs)
  • region : eu-west-1, us-east-1… (5–20 valeurs)

✗ Labels à haute cardinalité

  • user_id : des millions de valeurs
  • email : non borné
  • session_id : nouveau à chaque connexion
  • trace_id ou request_id : unique par requête
  • path avec paramètres non normalisés (/users/123, /users/456…)

Impact concret

Chaque série temporelle consomme de la mémoire dans Prometheus (en moyenne 1–2 Ko par série active). Une métrique avec 1 million de séries représente 1–2 Go de RAM rien que pour cette métrique.

⚠️ Piège du path non normalisé : http_requests_total{path="/users/123"} et http_requests_total{path="/users/456"} sont deux séries distinctes. Si tu as 10 000 utilisateurs actifs, c'est 10 000 séries. Normalise les chemins : /users/:id → une seule série.

Diagnostiquer les problèmes de cardinalité

Prometheus expose lui-même des métriques sur sa cardinalité :

# Nombre total de séries actives
prometheus_tsdb_head_series

# Top 10 des métriques par cardinalité
topk(10, count by (__name__)({__name__!~"^scrape_.*"}))

Grafana propose aussi un "Cardinality Explorer" dans son interface pour identifier les métriques problématiques.

Explore ci-dessous comment le nombre de séries explose selon le nombre de valeurs de chaque label.

Ajuste les sliders pour simuler la cardinalité d'une métrique selon ses labels.

Séries temporelles totales : 1

06 — SYNTHÈSE

Synthèse : cinq réflexes pour une stack solide

Du choix du type de métrique au routing des alertes, voici ce que tu dois retenir pour opérer Prometheus avec confiance.

Voici les cinq réflexes qui font la différence entre une stack Prometheus opérationnelle et une source de problèmes en production.

1. Choisir le bon type de métrique

TypeQuand l'utiliserPiège à éviter
Counter Tout ce qui s'incrémente sans revenir en arrière : requêtes, erreurs, octets Lire la valeur brute — toujours utiliser rate()
Gauge État instantané : connexions actives, mémoire, temperature Confondre avec un Counter pour des événements cumulatifs
Histogram Distributions de latence, taille de payloads — surtout en multi-instances Mal choisir les buckets — trop larges ou trop petits pour la distribution réelle
Summary Quantiles précis sur une seule instance Tenter d'agréger des Summary de plusieurs instances — c'est mathématiquement incorrect

2. Contrôler la cardinalité dès l'instrumentation

Avant d'ajouter un label, demande-toi : combien de valeurs distinctes peut-il prendre ? Si la réponse est "ça dépend des données métier et peut être illimité" (user_id, email, request_id), ne l'utilise pas comme label. Ces données vont dans les logs ou les traces, pas dans les métriques.

3. Toujours utiliser rate() sur les Counters

La valeur brute d'un Counter est inutilisable directement dans les dashboards et les alertes. Elle inclut les resets au redémarrage et n'est pas comparable entre instances. rate(metric[5m]) normalise tout ça et te donne le taux par seconde, lissé sur 5 minutes.

4. Configurer le for dans les alertes

Ne jamais mettre for: 0m sur une alerte de production. Un pic à 100% CPU pendant 15 secondes ne justifie pas un appel PagerDuty à 3h du matin. for: 2m ou for: 5m élimine les faux positifs sur des conditions transitoires.

5. Monitorer Prometheus lui-même

# Santé de Prometheus
up{job="prometheus"}                  # 1 si Prometheus lui-même scrape
prometheus_tsdb_head_series            # nombre de séries actives
rate(prometheus_target_scrapes_exceeded_sample_limit_total[5m]) # erreurs de scrape

# Cibles manquées
up == 0                                # toute cible down
💡 Alerte sur up == 0 en priorité. Si Prometheus ne peut plus scraper une cible, il ne peut pas alerter dessus. C'est un angle mort par définition — l'alerte up == 0 est le filet de sécurité.

Guide de choix rapide

SituationOutil
Combien de fois X s'est produit ?Counter + rate()
Quel est l'état actuel de X ?Gauge
Quelle est la distribution des latences ?Histogram + histogram_quantile()
Le taux d'erreurs dépasse 5% pendant 2 min ?Règle d'alerte sur Counter + for: 2m
Mon service est-il disponible ?up == 0 ou blackbox_exporter
Pourquoi cette alerte a-t-elle été émise ?Corrélation logs (Loki) + traces (Tempo)
🔑 La stack LGTM en complément : Prometheus ne couvre que les métriques. Pour une observabilité complète, combine-le avec Loki (logs), Tempo (traces distributed) et Grafana (visualisation unifiée). Le cours Observabilité I détaille cette architecture d'ensemble.