Table des matières :
- Architecture Prometheus : pull, targets, TSDB et service discovery
- Le modèle de données (labels) : la cardinalité, cet ennemi intime
- PromQL en production : requêtes, subqueries, recording rules et pièges
- Alertmanager : du signal au pager sans hystérie
- Grafana : dashboards, alerting, provisioning et sécurité d’accès
- Passer à l’échelle : Kubernetes, remote_write, Thanos/Mimir et gouvernance
Architecture Prometheus : pull, targets, TSDB et service discovery
Prometheus, en 2026, reste cet outil de monitoring qui refuse obstinément de « faire comme les autres » avec un modèle pull (scrape) plutôt qu’un push généralisé. Et oui, ça marche encore très bien, surtout dès qu’on sort des slides PowerPoint et qu’on parle architecture monitoring réelle : réseau capricieux, autoscaling, microservices qui naissent et meurent plus vite que votre café ne refroidit. L’idée centrale : Prometheus scrape des endpoints /metrics exposés par vos services (ou par des exporters), stocke tout dans sa TSDB locale, et exécute des règles (recording + alerting) sur ces séries temporelles.
Pour poser la base, la documentation officielle rappelle : « Prometheus stores all data as time series, i.e. metrics information is stored with the timestamp at which it was recorded, alongside optional key-value pairs called labels. » (Prometheus Docs, Data model : Prometheus Docs — Data model). Autrement dit : une métrique = un nom + un set de labels + des points dans le temps. Tout le reste (PromQL, Alertmanager, Grafana) n’est que l’art de ne pas se noyer dans cette mer de séries.
Le pipeline logique ressemble à ça :
- Targets (apps instrumentées, Node Exporter, NGINX exporter, kube-state-metrics, etc.) → endpoint HTTP
- Scrape par Prometheus via
scrape_configs+ service discovery (Kubernetes, Consul, EC2, fichier statique…) - TSDB locale : WAL + blocks, rétention (ex. 15j) et compaction
- Rules : recording rules pour pré-calculer, alerting rules pour produire des alertes
- Alertmanager : routage/dédoublonnage/silences
- Grafana : visualisation et parfois alerting (si vous aimez mélanger les genres)
Ce modèle « pull » a deux conséquences pratiques qu’on sous-estime souvent :
- Le réseau devient une contrainte de design : Prometheus doit atteindre vos targets. En environnement segmenté (VPC, clusters privés, DMZ), il est parfois plus simple de placer Prometheus « au plus près » (un Prometheus par cluster) plutôt que de vouloir tout centraliser.
- Vous contrôlez la charge d’observation : ce n’est pas l’app qui décide de pousser à 1s « parce que quelqu’un a coché une case ». Le
scrape_interval, lescrape_timeoutet les relabelings vous permettent d’ajuster l’effort de monitoring.
Un repère simple pour matérialiser l’impact de l’intervalle de scrape (ordre de grandeur) :
| Paramètre | Exemple | Effet direct |
|---|---|---|
| Targets | 200 | plus il y en a, plus le scrape coûte CPU/réseau |
| Séries exposées / target | 1 000 | dépend des exporters + instrumentation |
scrape_interval |
15s vs 60s | ×4 sur le nombre d’échantillons ingérés |
| Échantillons / jour | targets × séries × (86400/interval) |
mesure utile pour anticiper volume + stockage |
Même sans connaître « les bytes par sample », cette formule vous évite les surprises : réduire de 15s à 60s divise par 4 l’ingestion (et souvent la facture de remote storage), au prix d’une résolution plus faible.
Exemple minimal de scrape_config (Kubernetes à part, parce que sinon ça finit en roman) :
scrape_configs:
- job_name: "reverse-proxy"
scrape_interval: 15s
static_configs:
- targets: ["haproxy-exporter:9101", "nginx-exporter:9113"]
Deux détails qui font une vraie différence en prod :
scrape_timeout<scrape_interval: sinon vous créez des scrapes qui se chevauchent quand une target est lente (c’est rarement ce que vous voulez).- Nommez vos labels de manière stable (
job,instance,service,cluster,env) : si chaque équipe invente ses propres conventions, les requêtes globales deviennent une archéologie.
Et si vous cherchez un démarrage pragmatique, le site a déjà un pas-à-pas utile : guide de démarrage Prometheus (quickstart et mise en production). Ici on va plutôt parler du « après » : quand ça tourne, que ça scale, et que les labels commencent à vous regarder de travers.
Le modèle de données (labels) : la cardinalité, cet ennemi intime
Prometheus est incroyablement tolérant… jusqu’au moment où vous inventez une métrique par utilisateur (user_id) ou par URL complète (/produit/123456?utm_source=…). Là, la TSDB devient une machine à brûler de la RAM. La cardinalité, c’est le nombre de combinaisons distinctes de labels pour une métrique. Trop de cardinalité ⇒ explosion de séries ⇒ ingestion coûteuse, requêtes lentes, compactions douloureuses.
Le sarcasme facile, c’est de dire « n’utilisez pas de labels à haute cardinalité ». Le conseil utile, c’est de donner une règle concrète :
- Gardez des labels bornés (status code, method, handler, instance, pod, namespace, cluster).
- Évitez les dimensions non bornées (UUID, email, IP client, URL brute, message d’erreur).
- Préférez une normalisation :
route(templated) plutôt quepath,error_classplutôt queerror_message.
Sur un e-commerce, un anti-pattern classique est la mesure http_request_duration_seconds{path="/product/123"}. À la place, instrumentez au niveau framework (route patterns) et ajoutez un label route="/product/:id". Puis, si vous tenez vraiment à la segmentation marketing, faites-le côté analytics, pas côté TSDB de prod.
Pour rendre ça très concret, voici une grille de lecture rapide (utile en revue de PR d’instrumentation) :
| Dimension | Exemple | Verdict | Pourquoi |
|---|---|---|---|
| Code HTTP | status="502" |
✅ | borné |
| Méthode | method="POST" |
✅ | borné |
| Route | route="/checkout/:step" |
✅ | borné si templated |
| ID commande | order_id="A9F3..." |
❌ | non borné |
email="…" |
❌ | non borné + risque données personnelles | |
| Message d’erreur | error="timeout after 31.2s" |
❌ | non borné + bruit |
| Région/zone | zone="eu-west-3a" |
✅ | borné et utile en géo-diagnostic |
Le point « géo » est important en exploitation multi-région (ou multi-datacenters). Un label region/zone bien choisi permet de repérer un problème localisé (latence, erreur réseau, saturation) sans créer de cardinalité délirante.
Autre levier souvent oublié : couper la cardinalité avant ingestion avec le relabeling. Exemple typique : un exporter ajoute un label pod_uid ou container_id que vous n’exploiterez jamais dans vos dashboards.
scrape_configs:
- job_name: "kubernetes-pods"
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Exemple : supprimer un label instable / à haute cardinalité
- action: labeldrop
regex: "pod_uid|container_id"
C’est moins sexy qu’un nouveau dashboard, mais c’est l’une des meilleures « optimisations perf » que vous puissiez faire : moins de séries, donc moins de RAM, moins de churn, et des requêtes plus stables.
La vraie subtilité : histograms vs summaries. Les histograms (*_bucket) coûtent plus cher en séries, mais ils rendent possible le calcul de quantiles agrégés (par service, par cluster) via histogram_quantile(). Les summaries calculent des quantiles côté client et s’agrègent mal. Donc : pour du SLO/latence multi-instances, histograms + buckets raisonnables (pas 50 buckets « parce qu’on peut »).
Enfin, l’« observabilité moderne » vous pousse vers des exemplars (lien métrique → trace). Si vous unifiez tout avec OpenTelemetry, vous pouvez exporter des métriques propres et relier des pics de latence à des traces, plutôt que d’ouvrir 12 onglets et de deviner. Le contexte est bien résumé dans l’article maison : OpenTelemetry : unifier métriques, traces et logs pour l’observabilité.
Dernier point (pragmatique, et particulièrement vrai en UE) : surveillez votre instrumentation comme un flux « potentiellement sensible ». Même si Prometheus n’est pas un outil d’analytics, des labels mal pensés peuvent embarquer des identifiants (emails, IP, user IDs). La meilleure stratégie reste simple : ne jamais mettre de données utilisateur dans des labels, et limiter les détails aux logs/traces avec une politique de rétention/accès adaptée.
PromQL en production : requêtes, subqueries, recording rules et pièges
PromQL n’est pas « dur », il est juste impitoyable avec les approximations. La doc officielle : Prometheus querying basics est claire, mais en prod on veut des patterns. Le premier : ne requêtez pas des compteurs bruts, requêtez des dérivées.
Exemple sur un service web :
sum by (service) (rate(http_requests_total{job="api"}[5m]))
Ça vous donne le débit (req/s). Pour un taux d’erreur, même logique :
sum(rate(http_requests_total{job="api",status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="api"}[5m]))
Deux précisions qui évitent des graphiques « étranges » :
rate()vsirate():irate()est plus réactif mais plus bruité (pratique pour du debug court terme). Pour des dashboards et alertes,rate()sur 5m/10m est généralement plus stable.- Fenêtre de calcul : une fenêtre trop courte sur un trafic faible donne des taux erratiques. Une règle simple : choisissez une fenêtre qui contient assez d’événements (ex. 5m sur un endpoint à 1 req/min, c’est souvent insuffisant).
Deuxième pattern : la latence P95 avec histogram. Supposons http_request_duration_seconds_bucket :
histogram_quantile(
0.95,
sum by (le, service) (rate(http_request_duration_seconds_bucket{job="api"}[5m]))
)
Troisième pattern : subqueries pour lisser sans tomber dans la moyenne sur 24h « qui cache tout ». Exemple : P95 sur 5 minutes, observé sur 1h avec pas de 1m :
max_over_time(
(
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
)[1h:1m]
)
Un piège fréquent en PromQL, c’est l’agrégation incohérente. Exemple classique : vous calculez un ratio, mais vous n’agrégerez pas de la même manière au numérateur et au dénominateur (résultat : ratio faux ou séries qui ne « matchent » pas). Pour les ratios, gardez le même by (...) des deux côtés, ou forcez le matching (on(...)) quand c’est nécessaire.
Et maintenant, la partie que tout le monde oublie : les recording rules. Si votre Grafana exécute 30 panneaux, chacun avec 3 sum(rate(...)) sur 30 jours, vous n’avez pas « un dashboard », vous avez un test de charge involontaire sur Prometheus. Enregistrez les agrégats coûteux dans des séries pré-calculées (ex. job:http_requests:rate5m) et utilisez-les dans Grafana.
- Recording rules pour dashboards : agrégats par
service,namespace,clustersur 5m/1m. - Recording rules pour alerting : mêmes séries, mais dédiées à l’alerte (stables et peu coûteuses).
- Nommage lisible : un standard du type
service:metric:rate5moujob:metric:sum_rate5mévite la jungle.
Cas vécu (et très e-commerce) : un pic de trafic + une campagne + une régression qui augmente la latence DB. Sans recording rules, les requêtes PromQL deviennent les premières victimes collatérales : timeouts, dashboards vides, et l’équipe on-call qui ne voit plus rien. Avec des règles, Prometheus continue de servir des agrégats stables même sous charge, et vous gardez un minimum d’observabilité quand vous en avez le plus besoin.
Si vous ne deviez retenir qu’un « réflexe PromQL » : distinguez bien ce qui sert au debug (requêtes ad hoc, topk, filtres fins) de ce qui sert au pilotage (séries agrégées, lissées, pré-calculées).
Alertmanager : du signal au pager sans hystérie
Alertmanager n’est pas un « plugin de notifications ». C’est votre pare-feu anti-bruit entre Prometheus et vos humains. La doc l’énonce simplement : « The Alertmanager handles alerts sent by client applications such as the Prometheus server. » (Prometheus Docs, Alertmanager : Prometheus Alertmanager docs). Le mot important est handles : group, dedupe, route, inhibit, silence. Sans ça, vous finissez avec 500 messages Slack pour un seul incident… et un Slack qui ressemble à un DDoS émotionnel.
La recette de base en architecture saine :
- Dans Prometheus : alerting rules orientées symptômes (SLO, saturation, erreurs), pas « CPU à 80% parce que j’ai vu ça dans un blog de 2017 ».
- Dans Alertmanager : routage par labels (
team,severity,env,service) + grouping (ex.group_by: ["alertname", "cluster", "service"]). - Inhibition : si le réseau du cluster est down, inhibez les alertes applicatives qui en découlent.
Exemple de routage (très simplifié) :
route:
receiver: "slack-default"
group_by: ["alertname", "service", "cluster"]
group_wait: 30s
group_interval: 5m
repeat_interval: 2h
routes:
- matchers:
- severity="critical"
receiver: "pager"
Le point souvent raté : l’usage de for: dans les alerting rules. Mettre for: 0m sur des signaux bruités (latence P95) est une excellente façon de réveiller des gens pour du jitter normal. Un for: 5m ou 10m fait souvent le tri. Oui, ça retarde la détection de quelques minutes. Non, ce n’est pas une tragédie si votre alerte est bien choisie.
Pour rendre les alertes réellement actionnables, imposez-vous une checklist minimaliste :
- Symptôme clair (ce qui est impacté) : erreur, latence, indisponibilité, saturation.
- Portée (où) :
service,cluster,namespace,region. - Seuil justifié (pourquoi) : lié à un SLO ou à une capacité connue.
- Action (quoi faire) : runbook, commande, dashboard, owner.
Côté intégration opérationnelle, pensez « incident response » : templates de messages avec lien direct vers le dashboard Grafana, runbook URL, et labels consistants. Et tant qu’à faire, mettez Alertmanager en HA (au moins deux réplicas, idéalement trois) ; sinon, votre système d’alerting devient un SPOF, ce qui est une forme de poésie involontaire.
Pour cadrer l’approche SLO/alerting (et éviter l’alerte “métrique isolée”), une lecture de référence reste le chapitre Monitoring Distributed Systems du Google SRE Book : Monitoring Distributed Systems — Google SRE Book (utile pour remettre les alertes « symptômes » au centre).
Grafana : dashboards, alerting, provisioning et sécurité d’accès
Grafana est l’interface qui transforme Prometheus en quelque chose que les humains acceptent de regarder. La documentation résume : « Grafana is an open source visualization and analytics software. » (Grafana Docs, Introduction : Grafana Docs — Introduction). Très bien. Maintenant, faisons-le correctement : des dashboards orientés décision, pas des sapins de Noël RGB.
Une stratégie efficace :
- Dashboards “service overview” : throughput, error rate, saturation (CPU/mem), latence P50/P95, files d’attente.
- Dashboards “dependency” : DB, cache, reverse proxy, DNS, storage.
- Dashboards “platform” : Kubernetes, nodes, réseau, ingress.
Un bon test : si, pendant un incident, votre dashboard « overview » ne permet pas en 60 secondes de dire si c’est un problème de trafic, d’erreurs, de latence ou de saturation, il est décoratif.
Pour un environnement avec NGINX/HAProxy/Varnish, c’est souvent là que vous voyez les symptômes avant l’app : connexions, erreurs TLS, saturation, taux de cache hit. Vous pouvez connecter ces sujets à vos briques infra : approfondir NGINX (HTTP/2, HTTP/3 QUIC et TLS 1.3), mettre en place HAProxy de façon sécurisée et optimiser la couche cache avec Varnish.
Ensuite, arrêtez de configurer Grafana “à la main” sur une instance unique. Provisionnez : datasources, dashboards, alerting, permissions. En Git. Avec revue. Sinon, le jour où vous reconstruisez, tout disparaît (et vous redécouvrez la joie des sauvegardes). Bonus : provisionner permet d’industrialiser via CI/CD ; si votre infra est Kubernetes, l’article mettre en place un pipeline CI/CD Kubernetes avec GitHub Actions et Argo CD donne une base de méthode.
Un petit plus côté usage : exploitez les variables de dashboard (service/cluster/namespace) et imposez des valeurs par défaut « sûres ». Un dashboard qui charge tous les services sur 30 jours par défaut, c’est le même anti-pattern que des requêtes PromQL non pré-calculées : vous mettez votre outil à genoux juste en l’ouvrant.
Enfin, sécurité. Grafana a des rôles, des organisations, du SSO, et des tokens. Très bien… mais si vous exposez Grafana en clair sur Internet « parce que c’est pratique », vous méritez les surprises. Appliquez TLS, SSO, MFA, network policies côté cluster, et une hygiène MCO/MCS. Sur le durcissement Kubernetes, le rappel utile est ici : Sécurisation Kubernetes : plan d’action 30 jours pour PME.
- Séparer lecture et administration : tout le monde n’a pas besoin de créer/éditer des datasources, ni de gérer les alertes.
- Tracer les changements : provisioning + Git = historique et revue. En audit (ou après incident), c’est précieux.
Passer à l’échelle : Kubernetes, remote_write, Thanos/Mimir et gouvernance
Le Prometheus “single binary” est génial… jusqu’à ce que vous vouliez : 90 jours de rétention, 10 clusters, et des requêtes globales. La TSDB locale est pensée pour être rapide en local, pas pour devenir votre data lake d’observabilité. Donc, à l’échelle, vous avez généralement trois leviers : fédération, sharding, et remote storage.
- Fédération : un Prometheus “global” scrape des séries agrégées depuis des Prometheus “leaf”. Avantage : simple. Inconvénient : limites sur le détail, et complexité de hiérarchies.
- Sharding : diviser les targets entre plusieurs Prometheus (par job/namespace/zone). Avantage : ingestion répartie. Inconvénient : requêtes cross-shard plus dures.
- remote_write : envoyer les séries vers du long terme (Thanos, Cortex/Mimir, solutions managées). Avantage : rétention et requêtes globales. Inconvénient : coût, latence, et… gouvernance.
Dans Kubernetes, n’oubliez pas les vrais sujets qui coûtent du temps : service discovery qui change, relabeling, rotation des pods, et surtout le volume de métriques. kube-state-metrics + cAdvisor + exporters + instrumentation applicative = énorme. Sans politique de cardinalité (et sans enregistrement d’agrégats), vous atteignez vite des dizaines de millions de séries sur un gros parc. Et là, ce n’est plus « Prometheus est lent », c’est « on a instrumenté n’importe comment ». Oui, c’est moins glamour, mais c’est souvent la réalité.
Deux points très concrets quand vous passez à remote_write :
- La file de retry devient critique : si le backend distant est lent/indisponible, Prometheus doit bufferiser. Sur un cluster, cela implique disque et limites bien dimensionnés (et une stratégie claire : perdre du détail ou saturer).
- La gouvernance des métriques devient un sujet “FinOps” : chaque label inutile finit par coûter en stockage et en requêtes.
Sans ajouter une dissertation, un mini-tableau « d’orientation » (choix fréquent) :
| Besoin | Option fréquente | Idée clé |
|---|---|---|
| Long terme + requête globale | Thanos | s’appuie sur object storage, bon pour multi-cluster |
| Long terme + multi-tenant + règles à grande échelle | Grafana Mimir | conçu pour grosse volumétrie, remote_write natif |
| Réduire la charge Prometheus | Agent mode / scrape + remote_write | Prometheus “collecteur”, calcul déplacé |
La gouvernance, c’est aussi relier monitoring et performance web côté utilisateur. Si votre Prometheus vous dit « tout va bien » mais que vos utilisateurs ragent, vous avez un problème de périmètre. Complétez avec du RUM/synthétique pour la couche front : Real User Monitoring : RUM vs monitoring synthétique pour la performance web. Puis, mettez une boucle d’amélioration : SLO → alerting → post-mortem → ajustements métriques. Pour industrialiser ce maintien en conditions opérationnelles et sécurité (et éviter le monitoring “one shot”), le cadre MCO/MCS est ici : MCO/MCS – Maintien en Conditions Opérationnelles / Maintien en Conditions de Sécurité.
Si vous devez retenir une seule idée de cette architecture Prometheus + PromQL + Alertmanager + Grafana : la stack est excellente, mais elle ne pardonne pas l’absence de design. Un bon monitoring ne se résume pas à “installer Helm et espérer”. C’est de l’ingénierie : modèle de données, règles de calcul, alerting actionnable, dashboards versionnés, et scalabilité pensée — bref, tout ce qui fait gagner du temps le jour où ça brûle.