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.
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 :
- Prometheus contrôle qui il scrape — les applications n'ont pas besoin de connaître l'adresse du serveur de métriques
- Si une cible ne répond plus, Prometheus le détecte immédiatement et peut alerter dessus
- La configuration est centralisée dans
prometheus.yml, pas dispersée dans chaque service
Les composants de la stack
/metrics
TSDB + PromQL
Dashboards
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…).
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.
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.
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.
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.
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.
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.
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.
rate() gère le reset automatiquement en détectant la chute à zéro.
Histogram vs Summary
| Critère | Histogram | Summary |
|---|---|---|
| Où sont calculés les quantiles | Côté serveur (PromQL) | Côté client (SDK) |
| Agrégation multi-instances | Possible | Impossible |
| Précision des quantiles | Estimée (dépend des buckets) | Exacte |
| Buckets à configurer à l'avance | Oui | Non |
| Recommandation | Préféré | Cas spécifiques |
Le simulateur ci-dessous te permet d'explorer le comportement de chaque type.
Clique sur un type pour voir son comportement.
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.
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.
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
| Fonction | Entrée | Résultat |
|---|---|---|
rate(v[d]) | Range vector Counter | Taux par seconde (lissé sur la durée) |
irate(v[d]) | Range vector Counter | Taux instantané (2 derniers points) |
increase(v[d]) | Range vector Counter | Augmentation absolue sur la durée |
sum(v) | Instant vector | Somme de toutes les séries |
avg(v) | Instant vector | Moyenne de toutes les séries |
max(v) / min(v) | Instant vector | Max / Min |
topk(k, v) | Instant vector | Les 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) )
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.
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é.
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.
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é.
port 3306
port 9104
Exporters officiels courants
| Exporter | Cible | Port | Métriques clés |
|---|---|---|---|
node_exporter | Serveur Linux/Unix | 9100 | CPU, mémoire, disque, réseau |
blackbox_exporter | Endpoint HTTP/TCP/ICMP | 9115 | Disponibilité, latence, certificat TLS |
mysqld_exporter | MySQL / MariaDB | 9104 | QPS, connexions, slow queries, InnoDB |
redis_exporter | Redis | 9121 | Mémoire, commandes/s, hit rate, connexions |
postgres_exporter | PostgreSQL | 9187 | Connexions, locks, vacuum, taille DB |
nginx_exporter | nginx | 9113 | Requêtes/s, connexions actives, codes HTTP |
cadvisor | Conteneurs Docker/k8s | 8080 | CPU, 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']);
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] ); }
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 : l'expression est fausse. Rien ne se passe.
- Pending : l'expression est vraie, mais on attend la durée
forpour 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>'
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%
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.
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 valeursemail: non bornésession_id: nouveau à chaque connexiontrace_idourequest_id: unique par requêtepathavec 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.
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.
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
| Type | Quand l'utiliser | Piè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
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
| Situation | Outil |
|---|---|
| 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) |