Métriques à l'échelle — pourquoi Prometheus seul ne suffit pas
100 microservices × 50 métriques × 15 secondes = 90 milliards de points sur six mois. Un disque de pod ne peut pas tenir ça.
Tu as 100 microservices qui tournent dans ton cluster Kubernetes. Chacun d'eux expose un endpoint /metrics : une page de texte brut que Prometheus lit toutes les 15 secondes pour savoir comment va le service.
# Ce qu'un endpoint /metrics retourne
http_requests_total{method="GET", status="200"} 4821
http_request_duration_seconds_bucket{le="0.1"} 3987
process_resident_memory_bytes 52428800
Prometheus lit ces valeurs, les horodate et les stocke dans sa base de données interne. Jusqu'ici, ça tient la route. Mais maintenant imagine que tu veux garder ces données six mois pour faire du capacity planning.
100 services × 50 métriques × 1 scrape toutes les 15 secondes = 340 000 points par minute. Sur six mois, c'est environ 90 milliards de points. Aucun disque de pod Kubernetes ne peut tenir ça — et même s'il pouvait, tu perdrais tout si le pod redémarre.
Ce cours conçoit la solution étape par étape. Voici les composants que tu vas apprendre à assembler :
/metrics
scrape 15s
Sidecar · Query · Store · Compactor
long terme
Simulation du scraping Prometheus : plusieurs services génèrent des métriques. Regarde le disque du pod se remplir — et les anciens blocs disparaître quand la rétention est dépassée.
Comment Prometheus stocke les données — samples, chunks et blocs
Prometheus ne stocke pas les données ligne par ligne. Il les groupe en blocs de 2h sur disque — et le bloc scellé est l'unité de tout le reste.
Pour comprendre pourquoi Prometheus seul ne suffit pas, tu dois comprendre comment il stocke les données. Ce n'est pas une base de données classique avec des lignes et des colonnes. C'est une base de données de séries temporelles, organisée en blocs.
Du sample au bloc
Chaque valeur collectée (un sample) contient deux choses : un timestamp Unix et une valeur numérique. Prometheus les accumule d'abord en mémoire dans des structures appelées chunks, puis les écrit sur disque dans des blocs — chacun correspondant à une fenêtre de temps (2 heures par défaut).
ts + valeur
en mémoire
sur disque
Un bloc sur disque ressemble à ceci :
/prometheus/data/
01BX6V8YMJT23VB/ ← répertoire du bloc (ULID)
chunks/
000001 ← données compressées des séries
index ← index des labels pour les requêtes
meta.json ← intervalle de temps, stats
tombstones ← marqueurs de suppression
La fenêtre de rétention
Prometheus supprime automatiquement les blocs plus vieux que sa fenêtre de rétention. La valeur par défaut est 15 jours, configurable avec le flag --storage.tsdb.retention.time=15d.
Le problème : une fois un bloc supprimé, ces données sont perdues à jamais. Augmenter la rétention consomme plus de disque, mais ne résout pas le fond du problème — un pod Kubernetes avec un volume de quelques dizaines de GB ne pourra jamais stocker des années de métriques.
Pendant les deux premières heures, le bloc actif est en cours d'écriture. Quand la fenêtre de 2h se ferme, le bloc est scellé — plus aucune donnée n'y sera ajoutée. C'est précisément à ce moment que Thanos Sidecar peut l'attraper et l'envoyer vers S3.
--storage.tsdb.min-block-duration=2h et --storage.tsdb.max-block-duration=2h pour forcer des blocs de taille fixe. Cela garantit que le Sidecar reçoit des blocs complets et immuables à intervalles réguliers.
Blocs qui s'accumulent sur le disque Prometheus. Quand le disque dépasse la rétention, les plus anciens sont supprimés. Ajuste la rétention pour voir l'effet.
Thanos Sidecar — archiver les blocs dans S3
Un container qui tourne aux côtés de Prometheus, attrape les blocs scellés et les uploade vers S3. La rétention courte devient un choix, pas une contrainte.
La solution au problème de rétention est simple en principe : envoyer les blocs scellés vers un stockage objet bon marché et durable comme Amazon S3. En pratique, Prometheus n'a aucune connaissance de S3. C'est le rôle du Thanos Sidecar.
Un sidecar est un deuxième container qui tourne dans le même pod qu'une application principale. Il partage le même réseau, le même système de fichiers monté, mais a son propre binaire. Le Thanos Sidecar tourne aux côtés de Prometheus et accède à son répertoire de données via un volume partagé.
Voici à quoi ressemble la configuration dans un StatefulSet :
# Extrait du StatefulSet Prometheus
containers:
- name: prometheus
image: prom/prometheus:v2.51.0
args:
- --storage.tsdb.path=/prometheus
- --storage.tsdb.min-block-duration=2h # blocs fixes pour Thanos
- --storage.tsdb.max-block-duration=2h
- --storage.tsdb.retention.time=12h # court : Thanos prend le relais
volumeMounts:
- name: prometheus-data
mountPath: /prometheus
- name: thanos-sidecar ← le sidecar
image: thanosio/thanos:v0.35.0
args:
- sidecar
- --prometheus.url=http://localhost:9090
- --tsdb.path=/prometheus ← même volume que Prometheus
- --objstore.config-file=/etc/thanos/objstore.yaml
volumeMounts:
- name: prometheus-data
mountPath: /prometheus ← volume partagé
- name: thanos-objstore
mountPath: /etc/thanos
La configuration du bucket S3 dans objstore.yaml :
type: S3
config:
bucket: my-thanos-metrics
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1
Ce que fait le Sidecar
Le Sidecar surveille le répertoire de données Prometheus. Quand un nouveau bloc scellé (complet, immuable) apparaît, il l'uploade vers S3. À partir de ce moment, les données existent à deux endroits : les données récentes (< 12h dans cet exemple) restent sur le pod, toutes les autres sont dans S3.
Cycle de vie d'un bloc : écriture par Prometheus → scellage → upload par le Sidecar vers S3. Les blocs verts sont disponibles localement, les bleus sont archivés dans S3.
Haute disponibilité — deux répliques Prometheus
Un seul Prometheus, c'est un single point of failure. Deux répliques qui scrape tout en parallèle éliminent ce risque — la déduplication viendra après.
Un seul pod Prometheus, c'est un single point of failure. Si ce pod redémarre — pour une mise à jour, un node qui tombe, n'importe quelle raison — les données des dernières minutes (celles pas encore scellées et uploadées) sont perdues.
La solution est simple : faire tourner deux répliques Prometheus qui scrape les mêmes services en parallèle. Si l'une tombe, l'autre continue.
Chaque réplique scrape tous les services indépendamment. Chacune a son propre Sidecar qui uploade vers le même bucket S3. En cas de panne d'une réplique, l'autre conserve les données récentes et le Sidecar continue d'archiver. Les données ne sont pas perdues.
# StatefulSet avec deux répliques
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: prometheus
spec:
replicas: 2 ← deux pods
selector:
matchLabels:
app: prometheus
template:
spec:
containers:
- name: prometheus
args:
- --storage.tsdb.path=/prometheus
- --web.enable-lifecycle
- --storage.tsdb.retention.time=12h
- name: thanos-sidecar
args:
- sidecar
- --prometheus.url=http://localhost:9090
- --tsdb.path=/prometheus
- --objstore.config-file=/etc/thanos/objstore.yaml
Le problème de la duplication
Deux Prometheus qui scrape les mêmes services produisent des données en double dans S3. Si tu interroges directement les deux, tu obtiens deux fois les mêmes valeurs pour chaque métrique.
Thanos Query résoudra ce problème plus loin dans le cours via la déduplication. Pour l'instant, sache qu'on ajoute un label prometheus_replica à chaque instance pour qu'elles soient distinguables :
containers:
- name: prometheus
args:
- --web.enable-lifecycle
# Pod 0 :
- --storage.tsdb.path=/prometheus
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: thanos-sidecar
args:
- sidecar
- --prometheus.labels=prometheus_replica=$(POD_NAME)
/metrics, et deux fois plus de données dans S3. En pratique, sur des petits services, ce surcoût est négligeable.
Deux répliques Prometheus. Clique sur Kill Pod pour simuler une panne — la deuxième réplique continue de scraper et de tenir les données récentes.
Store Gateway — rendre S3 interrogeable
S3 stocke les fichiers mais ne sait pas lire du PromQL. Le Store Gateway fait le pont : il indexe les blocs Thanos et répond aux requêtes historiques.
Les données historiques sont maintenant dans S3. Mais S3 est un stockage objet muet : il conserve des fichiers, mais il ne sait pas interpréter leur contenu. Si tu lui demandes "quelles étaient les requêtes par seconde il y a trois mois ?", il ne peut pas répondre.
Pour rendre les blocs Thanos dans S3 interrogeables, on ajoute le Store Gateway.
Le Store Gateway est un composant Thanos qui s'installe entre ton cluster et S3. Il lit l'index de chaque bloc dans S3, charge les métadonnées en mémoire (pas les données entières), et est capable de répondre à des requêtes PromQL en allant chercher les données pertinentes dans S3 à la demande.
# Déploiement du Store Gateway
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-store
spec:
replicas: 1
template:
spec:
containers:
- name: thanos-store
image: thanosio/thanos:v0.35.0
args:
- store
- --objstore.config-file=/etc/thanos/objstore.yaml
- --data-dir=/data ← cache local des index (pas les blocs entiers)
- --grpc-address=0.0.0.0:10901
volumeMounts:
- name: thanos-objstore
mountPath: /etc/thanos
Le Store Gateway expose une interface gRPC que Thanos Query appelle pour lire les données historiques. Il ne charge les données S3 qu'au moment où une requête l'exige — pas tout en mémoire en permanence.
Ce qu'il ne fait pas
Le Store Gateway ne répond pas aux requêtes directement depuis Grafana ou Prometheus. Son seul client est Thanos Query. Il s'occupe exclusivement de la couche d'accès aux données historiques.
--data-dir). Ce cache accélère les requêtes en évitant de re-télécharger les index depuis S3 à chaque fois. Il n'a pas besoin d'être énorme — quelques GB suffisent pour la plupart des setups.
Une requête arrive sur des données historiques. Observe ce qui se passe avec et sans Store Gateway.
Thanos Query — la porte d'entrée unique
Fan-out vers les Sidecars + Store Gateway, fusion récent/historique, déduplication des répliques. Pour Grafana, c'est un Prometheus ordinaire.
Les données récentes se trouvent dans les Sidecars des deux répliques Prometheus. Les données historiques se trouvent dans le Store Gateway. Et personne ne devrait avoir à interroger chacun séparément, puis assembler les résultats à la main.
C'est le rôle de Thanos Query : la porte d'entrée unique qui reçoit toutes les requêtes PromQL et se charge de les fan-outer vers les bons composants.
(1) Il reçoit une requête PromQL. (2) Il l'envoie en parallèle à tous ses "stores" : les Sidecars des deux Prometheus + le Store Gateway. (3) Il fusionne les résultats en une seule timeline continue. (4) Il déduplique les données identiques venant des deux répliques Prometheus.
# Déploiement de Thanos Query
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-query
spec:
template:
spec:
containers:
- name: thanos-query
image: thanosio/thanos:v0.35.0
args:
- query
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:9090
# Les Sidecars Prometheus
- --store=prometheus-0.prometheus:10901
- --store=prometheus-1.prometheus:10901
# Le Store Gateway
- --store=thanos-store:10901
# Label utilisé pour la déduplication des répliques
- --query.replica-label=prometheus_replica
La déduplication
Sans déduplication, une requête sur http_requests_total retournerait deux séries identiques — une par réplique. --query.replica-label=prometheus_replica indique à Thanos Query quel label distingue les répliques. Il garde alors le sample le plus récent disponible entre les deux et présente une seule série propre.
Thanos Query reçoit une requête et la distribue à tous les composants. Observe le fan-out et la déduplication.
Grafana — une seule datasource, tout l'historique
Grafana pointe sur Thanos Query. Une requête PromQL donne accès à 6 mois de métriques, quelle que soit la plage de temps choisie.
Grafana est l'interface de visualisation. Il ne stocke aucune donnée lui-même — il envoie des requêtes PromQL à une datasource et affiche les résultats sous forme de graphes, jauges, tableaux.
Avec Thanos en place, la configuration de Grafana est simple : une seule datasource, pointée sur Thanos Query. Grafana ne sait pas qu'il y a deux Prometheus, un Store Gateway et un bucket S3 derrière. Pour lui, c'est un seul endpoint qui répond à du PromQL.
# Datasource Grafana (provisioning via ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Thanos
type: prometheus
url: http://thanos-query:9090 ← Thanos Query, pas Prometheus
isDefault: true
jsonData:
timeInterval: "15s"
queryTimeout: "60s"
Grafana envoie des requêtes PromQL en précisant une plage de temps. Thanos Query détermine automatiquement quels composants consulter en fonction de cette plage : Sidecars pour les données récentes, Store Gateway pour les données historiques.
Quelques requêtes utiles
# Taux de requêtes par service (dernier 5 minutes)
sum by (service) (rate(http_requests_total[5m]))
# Latence p99 (percentile 99%)
histogram_quantile(0.99,
sum by (le, service) (rate(http_request_duration_seconds_bucket[5m]))
)
# Utilisation mémoire de tous les pods
sum by (pod) (container_memory_working_set_bytes{namespace="production"})
Sélectionne une plage de temps — observe quels composants Thanos Query consulte en réponse.
Compacteur — garder S3 sain sur la durée
Fusion de petits blocs, downsampling (15s → 5min → 1h), application de la rétention longue. Une seule instance, toujours.
S3 a de la place en quasi-illimité et pour pas cher. Mais sans maintenance, le bucket finit par contenir des milliers de petits blocs de 2h, ce qui rend les requêtes longue durée lentes et le bucket difficile à gérer. Le Compacteur s'occupe de ça.
(1) Compaction : fusionne les petits blocs de 2h en blocs de 48h, puis de semaines. Moins de fichiers dans S3, requêtes plus efficaces. (2) Downsampling : crée des versions basse résolution des données (1 point / 5 min, puis 1 point / 1h). (3) Rétention : supprime les données plus vieilles que la fenêtre configurée.
La compaction
Sans compaction, après 30 jours tu as 360 blocs de 2h dans S3. Une requête sur "les 30 derniers jours" doit ouvrir et lire chacun d'eux. Avec compaction, ces 360 blocs deviennent quelques dizaines de blocs plus grands, et la requête est beaucoup plus rapide.
Le downsampling
Quand tu regardes "les 6 derniers mois" dans Grafana, tu n'as pas besoin de voir un point toutes les 15 secondes — un point toutes les heures suffit pour avoir une courbe lisible. Le Compacteur crée automatiquement des versions basse résolution :
| Résolution | Granularité | Quand utilisée |
|---|---|---|
| Raw | 1 point / 15s | Derniers jours (haute précision) |
| 5min | 1 point / 5 min | Dernières semaines |
| 1h | 1 point / 1h | Derniers mois |
# Déploiement du Compacteur (Job ou Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-compactor
spec:
replicas: 1 ← TOUJOURS une seule instance (accès exclusif au bucket)
template:
spec:
containers:
- name: thanos-compactor
image: thanosio/thanos:v0.35.0
args:
- compact
- --wait ← tourne en continu
- --objstore.config-file=/etc/thanos/objstore.yaml
- --data-dir=/data
# Rétention par résolution
- --retention.resolution-raw=30d
- --retention.resolution-5m=90d
- --retention.resolution-1h=365d
replicas: 2 sur ce déploiement.
Blocs qui s'accumulent dans S3 sur la durée. Le Compacteur les fusionne régulièrement — observe la réduction du nombre de blocs et l'apparition des versions downsamplées.
Architecture complète — tout assembler
Vue d'ensemble du pipeline, rôle de chaque composant, points de vigilance en production.
Voici l'architecture complète telle qu'elle tourne en production. Chaque composant a un rôle précis, et ensemble ils résolvent un problème que Prometheus seul ne peut pas résoudre.
/metrics × 100
scrape 15s · rétention 12h
blocs scellés → S3
stockage long terme
PromQL dashboard
fan-out · dédup
données récentes
données S3
fusion · downsample · rétention
blocs compactés
Rôle de chaque composant
| Composant | Rôle | Où tourne |
|---|---|---|
| Prometheus ×2 | Scrape, stockage court terme (12h) | StatefulSet, 2 répliques |
| Thanos Sidecar ×2 | Upload des blocs scellés vers S3 | Container dans le pod Prometheus |
| Thanos Store Gateway | Rend S3 queryable via gRPC | Deployment, 1+ répliques |
| Thanos Query | Fan-out, fusion, déduplication | Deployment, scalable horizontalement |
| Thanos Compacteur | Fusionne blocs, downsample, rétention | Deployment, 1 seule instance |
| Grafana | Visualisation PromQL | Deployment, datasource → Thanos Query |
| Amazon S3 | Stockage objet des blocs Thanos | Service AWS managé |