📈 Prometheus – Monitoring, Métriques & Alerting
Guide complet IDEO‑Lab sur le monitoring (Pull, TSDB, PromQL, Alertmanager).
Vue d'ensemble
Modèle "Pull" vs "Push", Time Series (TSDB).
Pull Model TSDB MonitoringArchitecture
Scraping, TSDB, PromQL, Alertmanager.
Scraping PromQLInstallation (Linux)
Binaire, prometheus.yml, systemd.
Installation (Docker)
docker run, docker-compose (avec Grafana).
Config (prometheus.yml)
global, scrape_configs, static_configs.
Modèle de Données
Métriques & Labels, Time Series, up{}.
Types de Métriques
Counter, Gauge, Histogram, Summary.
Counter Gauge HistogramExporters (node_exporter)
node_exporter (serveurs), blackbox_exporter.
Instrumentation (Client Libs)
Instrumenter du code (Python, PHP, Go).
Client Library /metricsPromQL (Intro)
Sélecteurs ({}), Range Vectors ([5m]), offset.
PromQL (rate)
rate(), increase(). La clé des Counters.
PromQL (Agrégation)
sum(), avg(), by(), without(), topk().
PromQL (Histogram)
_bucket, _sum, histogram_quantile() (percentiles).
Alertmanager (Concept)
Routing, Grouping, Silencing, alertmanager.yml.
Règles d'Alerting
rules.yml, FOR, LABELS, ANNOTATIONS.
Service Discovery
static_config vs file_sd, K8s, Consul.
Fédération & HA
HA (2 Proms), Fédération (Prom -> Prom).
HA FederationCheat-sheet PromQL
rate, sum by, topk, histogram_quantile.
Le standard du Monitoring Cloud-Native
Prometheus (créé par SoundCloud, maintenant CNCF) est un **système de monitoring et d'alerting** open-source. Il est conçu pour la fiabilité et la scalabilité.
Son cœur est une Time Series Database (TSDB) : une base de données optimisée pour stocker des métriques (nombres) indexées par le temps.
Il est au centre de l'écosystème d'Observabilité, souvent associé à Grafana (visualisation) et Alertmanager (alertes).
Le Modèle "Pull" (Scraping)
C'est la philosophie centrale de Prometheus. Contrairement aux anciens systèmes (ex: StatsD) où les applications "poussent" (Push) les métriques vers un serveur, Prometheus "tire" (Pull) les métriques.
Modèle "Pull" (Scraping) :
- Vos applications (ou des "Exporters") exposent leurs métriques sur un endpoint HTTP (ex:
/metrics) en format texte. - Prometheus (configuré) "scrape" (visite) cet endpoint à intervalle régulier (ex:
scrape_interval: 15s). - Prometheus stocke la valeur lue dans sa TSDB.
| Critère | Pull (Prometheus) | Push (Graphite/StatsD) |
|---|---|---|
| Contrôle | Prometheus (serveur) contrôle. | Application (client) contrôle. |
| Découverte | Facile (Service Discovery). | Difficile (l'app doit connaître le serveur). |
| Fiabilité | Sait immédiatement si une cible est "down" (échec du scrape). | Ne sait pas si une app est "down" ou si elle n'envoie rien. |
Les 4 Composants
- 1. Prometheus Server : Le cœur. C'est lui qui fait le "Scraping" (collecte), qui stocke dans sa **TSDB** (Time Series Database), et qui exécute les requêtes **PromQL**.
- 2. Cibles (Targets) / Exporters : Les applications (ex: votre API) ou les "Exporters" (ex:
node_exporterpour les serveurs) qui exposent le endpoint/metrics. - 3. Alertmanager : Un service (souvent séparé) qui reçoit les "alertes" de Prometheus, les dé-duplique, les groupe, et les route (Email, Slack, PagerDuty).
- 4. Clients (UI) : L'UI web de Prometheus (pour PromQL) ou (le plus souvent) Grafana.
Schéma de flux
[Image d'une architecture Prometheus] +------------------------------------------------------+ | SERVEUR PROMETHEUS | | +----------------+ +-----------------------------+ | | | Service | | TSDB | | | | Discovery | | (Stockage local) | | | +----------------+ +-----------------------------+ | | | | (PromQL) | | ▼ ▲ | | +----------------+ | | | | Scraping Engine|--+ | | +----------------+ | | | (Pull /metrics) | | | | | +-----------------------+ | | | | | | ▼ ▼ | | +--------------+ +------------------+ | | | Cible 1 | | Cible 2 (Node) | | | | (App /metrics) | | (node_exporter) | | | +--------------+ +------------------+ | | | | (Alertes) ▼ | | +--------------+ (Requêtes) ^ | | | Alertmanager | | | | +--------------+ | | | | | | | ▼ (Email/Slack) +------------------+ | | | Grafana (UI) | | | +------------------+ | +------------------------------------------------------+
Installation (Binaire)
Prometheus (comme Grafana) est un binaire Go. L'installation consiste à télécharger le .tar.gz et à créer un service.
# (Versions à adapter)
VERSION="2.50.1"
ARCH="linux-amd64"
# 1. Créer un utilisateur 'prometheus' (sécurité)
sudo useradd --system --no-create-home --shell /bin/false prometheus
# 2. Télécharger et extraire
wget https://github.com/prometheus/prometheus/releases/download/v${VERSION}/prometheus-${VERSION}.${ARCH}.tar.gz
tar -xvf prometheus-${VERSION}.${ARCH}.tar.gz
cd prometheus-${VERSION}.${ARCH}
# 3. Créer les dossiers de config et de données
sudo mkdir -p /etc/prometheus
sudo mkdir -p /var/lib/prometheus
# 4. Copier les binaires et la config
sudo cp prometheus promtool /usr/local/bin/
sudo cp -r consoles console_libraries /etc/prometheus/
sudo cp prometheus.yml /etc/prometheus/prometheus.yml
# 5. Définir les permissions
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /usr/local/bin/{prometheus,promtool}Fichier Service (systemd)
Créez /etc/systemd/system/prometheus.service :
[Unit]
Description=Prometheus Time Series Monitoring
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus/ \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.enable-lifecycle
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.targetDémarrage
sudo systemctl daemon-reload sudo systemctl enable --now prometheus sudo systemctl status prometheus # (Ouvrir le port 9090 dans le firewall)
docker run (Simple)
Nécessite un fichier prometheus.yml local.
# (Créer /home/user/prometheus.yml d'abord)
docker run -d \
--name prometheus \
-p 9090:9090 \
-v /home/user/prometheus.yml:/etc/prometheus/prometheus.yml \
-v prom-data:/prometheus \
prom/prometheus:latest
# -v (config)
# -v (persistance)docker-compose.yml (Stack Prometheus + Grafana)
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prom-data:/prometheus
# (Permet à Grafana de se connecter via http://prometheus:9090)
grafana:
image: grafana/grafana-oss:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
volumes:
prom-data:
grafana-data:/etc/prometheus/prometheus.yml
# 'global' : Paramètres par défaut
global:
scrape_interval: 15s # (Scrape toutes les 15 sec)
evaluation_interval: 15s # (Évalue les règles toutes les 15 sec)
# Fichiers de règles d'alerting (voir 4.2)
rule_files:
- "alert.rules.yml"
# Connexion à Alertmanager (voir 4.1)
alerting:
alertmanagers:
- static_configs:
- targets:
- 'localhost:9093'
# --- LA SECTION LA PLUS IMPORTANTE ---
# 'scrape_configs' : Quelles cibles (targets) scraper ?
scrape_configs:
# Job 1: Scraper Prometheus lui-même
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Job 2: Scraper les serveurs (node_exporter)
- job_name: 'node'
static_configs:
- targets: ['192.168.1.10:9100', '192.168.1.11:9100']
# Job 3: Scraper les APIs (service discovery)
- job_name: 'api'
file_sd_configs:
- files:
- '/etc/prometheus/sd_files/api.json'scrape_config (Détail)
Un "job" (job_name) est une collection de cibles (targets) ayant le même but (ex: "tous mes serveurs web").
| Directive | Description |
|---|---|
job_name | Nom du job (devient un label job="node"). |
scrape_interval | (Optionnel) Surcharge le global.scrape_interval. |
metrics_path | (Optionnel) Le chemin à scraper (défaut: /metrics). |
static_configs | Liste manuelle (statique) des cibles (IP:Port). |
file_sd_configs | Service Discovery (dynamique) via un fichier JSON/YAML. |
kubernetes_sd_configs | Service Discovery (natif) pour Kubernetes. |
La Time Series (Série Temporelle)
Une "time series" est un flux de timestamps (temps) et de valeurs (nombres).
Dans Prometheus, chaque time series est identifiée **uniquement** par son **Nom de Métrique** ET son **jeu de Labels** (clés-valeurs).
Syntaxe
METRIC_NAME{LABEL1="value1", LABEL2="value2"} VALEURExemple (/metrics)
# HELP http_requests_total Total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="GET", path="/api/users"} 50
http_requests_total{method="POST", path="/api/users"} 10
http_requests_total{method="GET", path="/api/status"} 102
# HELP node_cpu_usage_percentage CPU usage.
# TYPE node_cpu_usage_percentage gauge
node_cpu_usage_percentage{instance="server1", core="0"} 80.5
node_cpu_usage_percentage{instance="server1", core="1"} 10.0Labels (La puissance)
Les labels sont la clé de PromQL. Ils permettent l'agrégation "multi-dimensionnelle".
Ne *jamais* mettre de valeurs variables (ex: ID utilisateur) dans un nom de métrique. Mettez-les en labels.
Mauvais (Explosion de cardinalité)
http_requests_GET_api_users 50 http_requests_POST_api_users 10
Bon (Labels)
http_requests_total{method="GET", path="/api/users"} 50
http_requests_total{method="POST", path="/api/users"} 10Prometheus ajoute aussi des labels (job, instance) lors du scrape.
C'est le concept le plus important pour utiliser PromQL correctement.
| Type | Description | Usage | Exemple (PromQL) |
|---|---|---|---|
| Counter (Compteur) | Une valeur qui ne fait que **monter** (ou reset à 0). Ex: Nombre de requêtes, erreurs. | Ne jamais l'afficher. Doit TOUJOURS être utilisé avec rate(). | rate(http_requests_total[5m]) |
| Gauge (Jauge) | Une valeur qui peut **monter ou descendre**. Ex: Température, RAM utilisée, CPU actuel. | Afficher la valeur brute, ou avg(). | node_memory_usage_bytes |
| Histogram | Observe et compte dans des "buckets" (paniers) prédéfinis. Ex: Durée des requêtes (0-100ms, 100-200ms...). | Calcul de percentiles (ex: 95ème, 99ème). | histogram_quantile(0.95, ...) |
| Summary (Résumé) | Similaire à Histogram, mais les percentiles sont calculés **côté client**. (Moins flexible, plus coûteux). | (Généralement évité, préférer Histogram). | metric_name{quantile="0.95"} |
Le "Traducteur"
Que faire si la cible ne sait pas exposer de /metrics (ex: un serveur Linux, une BDD PostgreSQL, un routeur Cisco) ?
On utilise un **Exporter** : un petit service qui "traduit" l'état d'un système tiers en métriques Prometheus.
Exporters populaires
node_exporter: Métriques système (CPU, RAM, Disque, Réseau) (pour Linux/Win).postgres_exporter: Métriques PostgreSQL.mysqld_exporter: Métriques MySQL/MariaDB.blackbox_exporter: Monitoring "boîte noire" (Ping, HTTP, DNS).
Exemple : node_exporter
# 1. Télécharger et lancer (sur la cible '1.2.3.4')
wget .../node_exporter-....tar.gz
tar -xvf ...
./node_exporter
# (Expose les métriques sur 1.2.3.4:9100/metrics)
# 2. Configurer Prometheus (prometheus.yml)
scrape_configs:
- job_name: 'node_exporter'
static_configs:
- targets: ['1.2.3.4:9100', '1.2.3.5:9100']Python (prometheus-client)
Comment *créer* vos propres métriques (ex: requêtes, erreurs métier) dans votre code.
pip install prometheus-client
from flask import Flask
from prometheus_client import Counter, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware
app = Flask(__name__)
# 1. Définir le Counter (globale)
REQUEST_COUNTER = Counter(
'http_app_requests_total',
'Total HTTP Requests',
['method', 'path'] # Labels
)
@app.route('/')
def home():
# 2. Incrémenter le Counter
REQUEST_COUNTER.labels(method='GET', path='/').inc()
return "Hello!"
# 3. Exposer le /metrics
app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {
'/metrics': make_wsgi_app()
})PHP (prom-client-php)
L'instrumentation PHP est plus complexe (modèle "share nothing"). Elle nécessite un "Adapter" (ex: APCu, Redis) pour stocker les métriques entre les requêtes.
composer require promphp/prometheus_client_php
<?php
// (Nécessite 'pecl install apcu')
// adapter.php
use Prometheus\CollectorRegistry;
use Prometheus\Storage\APCu;
$adapter = new APCu();
$registry = new CollectorRegistry($adapter);
// index.php
require 'adapter.php';
$counter = $registry->getOrRegisterCounter(
'app',
'http_requests_total',
'Total requests',
['path']
);
$counter->inc(['/']); // Incrémenter
// metrics.php
require 'adapter.php';
use Prometheus\RenderTextFormat;
$renderer = new RenderTextFormat();
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $renderer->render($registry->getMetricFamilySamples());Sélecteurs ({})
Comment filtrer les time series.
# 1. Nom de la métrique
http_requests_total
# 2. Nom + Labels (exact)
http_requests_total{job="api", method="GET"}
# 3. Opérateurs de Label
# (!= : différent, =~ : regex, !~ : not regex)
http_requests_total{job="api", method!="POST"}
http_requests_total{path=~"/api/v[12]/.*"}Vecteurs (Instant vs Range)
Instant Vector (Vecteur Instantané)
Retourne une seule valeur (la plus récente) par time series. C'est le défaut.
node_cpu_usage_percentage # (Affiche le CPU *maintenant*)
Range Vector (Vecteur de Plage) [5m]
Retourne l'historique des valeurs sur une plage de temps (ex: "les 5 dernières minutes").
node_cpu_usage_percentage[5m]
Attention : Un Range Vector ne peut pas être affiché tel quel. Il doit être passé à une fonction (rate, increase...).
Le problème du "Counter"
Si vous affichez un "Counter" (http_requests_total), le graphe ne fera que monter. Ce n'est pas utile.
Ce qu'on veut, c'est le **taux d'augmentation** (ex: requêtes *par seconde*).
rate()
Calcule le taux d'augmentation *par seconde*, basé sur un Range Vector.
# Calcule le taux de requêtes/seconde, # moyenné sur les 5 dernières minutes. rate(http_requests_total[5m])
increase()
Calcule l'augmentation *totale* sur la plage de temps.
# Calcule le nombre total de requêtes # (pas par sec) des 30 dernières minutes. increase(http_requests_total[30m])
irate() (Avancé)
rate() moyenne sur 5m (lissé). irate() (instant rate) ne regarde que les 2 derniers points. Utile pour voir les "pics" (spikes).
Opérateurs d'agrégation
(sum, avg, min, max, count, topk...)
# (Input: 100 series de req/sec, 1 par 'path') rate(http_requests_total[5m]) # (Output: 1 seule valeur = le total) sum(rate(http_requests_total[5m]))
Clauses by & without (Le "GROUP BY")
# (Input: 100 series avec labels {job, instance, path})
rate(http_requests_total[5m])
# (Output: Req/sec, groupées par 'job' et 'path')
# (Supprime 'instance')
sum(rate(http_requests_total[5m])) by (job, path)
# (Output: Req/sec, groupées par TOUT sauf 'instance')
# (Plus facile si on a 10 labels)
sum(rate(http_requests_total[5m])) without (instance)topk
# Top 3 des instances par utilisation CPU topk(3, node_cpu_usage_percentage)
Les Histogrammes sont complexes. C'est un type de métrique (Counter) qui expose 3 séries :
_bucket{le="..."}: (Compteur) Nombre d'observations *inférieures ou égales* à "le" (less than or equal)._count: Nombre total d'observations (identique à_bucket{le="+Inf"})._sum: Somme totale des valeurs (pour calculer la moyenne).
histogram_quantile()
N'utilisez *jamais* la moyenne (avg) pour les temps de réponse. Utilisez les percentiles (P95, P99).
Cette fonction magique prend un _bucket (en rate()) et calcule le percentile.
# Calcule le 95ème percentile (P95) # de la durée des requêtes (moyenné sur 5m) histogram_quantile( 0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, job) )
Rôle d'Alertmanager
Prometheus *génère* les alertes (basé sur les règles). Alertmanager (un service séparé) les *gère*.
Rôles clés :
- Dé-duplication : Si 100 serveurs sont "down", ne reçoit qu'une alerte.
- Grouping (Groupage) : Regroupe 100 alertes "DiskFull" en 1 seule notification.
- Routing (Routage) : Envoie les alertes "critical" à PagerDuty, les "warning" à Slack.
- Silencing (Silence) : Permet de taire les alertes (ex: maintenance).
alertmanager.yml
global:
resolve_timeout: 5m
route:
# Route racine (défaut)
receiver: 'slack-default'
group_by: ['alertname', 'cluster']
# Attendre 1m avant 1ère notif, 1h avant rappel
group_wait: 1m
group_interval: 10m
repeat_interval: 1h
# Routage spécifique
routes:
- receiver: 'pagerduty-critical'
match:
severity: 'critical' # (Label de l'alerte)
- receiver: 'slack-warning'
match:
severity: 'warning'
receivers:
- name: 'slack-default'
slack_configs:
- api_url: 'https://hooks.slack.com/...'
channel: '#alerts'
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: ...Les règles sont définies dans des fichiers .yml (ex: alert.rules.yml) chargés par Prometheus (via rule_files dans prometheus.yml).
groups:
- name: host_alerts
rules:
# Règle 1: Instance "Down"
- alert: InstanceDown
# La requête PromQL
expr: up{job="node"} == 0
# Durée (l'alerte doit être vraie pendant 1m)
for: 1m
# Labels (pour le routage Alertmanager)
labels:
severity: critical
# Annotations (le message)
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{ $labels.instance }} (Job: {{ $labels.job }}) est 'down' depuis 1 minute."
# Règle 2: CPU Élevé
- alert: HighCpuUsage
expr: node_cpu_usage_percentage{job="node"} > 80
for: 5m
labels:
severity: warning
annotations:
summary: "CPU élevé sur {{ $labels.instance }}"
description: "CPU à {{ $value }}% depuis 5 minutes."static_configs (Statique)
Simple, mais rigide. Vous devez lister chaque cible manuellement. Si une IP change, le .yml doit être modifié et Prometheus rechargé (HUP).
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['1.2.3.4:9100', '1.2.3.5:9100']Service Discovery (Dynamique)
Prometheus peut "découvrir" ses cibles en lisant une source de vérité externe (Consul, Kubernetes API, ou un simple fichier).
file_sd_configs (Fichier SD)
# prometheus.yml
scrape_configs:
- job_name: 'node'
file_sd_configs:
- files:
- '/etc/prometheus/targets/nodes.json'
# (Prometheus surveille ce JSON en permanence)
# /etc/prometheus/targets/nodes.json
[
{
"targets": ["1.2.3.4:9100", "1.2.3.5:9100"],
"labels": {
"env": "prod",
"dc": "datacenter1"
}
},
{
"targets": ["10.0.0.1:9100"],
"labels": {
"env": "staging"
}
}
]Haute Disponibilité (HA)
La méthode HA standard consiste à lancer **deux** serveurs Prometheus identiques, qui scrapent les **mêmes** cibles.
Les deux envoient leurs alertes à Alertmanager, qui s'occupe de la dé-duplication (grâce aux labels identiques).
Grafana est configuré avec les deux serveurs Prometheus comme Data Source (en cas de panne de l'un).
Fédération
Pour agréger les données de plusieurs serveurs Prometheus (ex: un par datacenter) vers un Prometheus "global".
# (Config du Prometheus Global)
scrape_configs:
- job_name: 'federate-dc1'
# Scrape le endpoint /federate (optimisé)
metrics_path: '/federate'
params:
'match[]': # Ne récupérer que les métriques agrégées
- '{job="node"}'
static_configs:
- targets: ['prometheus.dc1.internal:9090']
- job_name: 'federate-dc2'
...
static_configs:
- targets: ['prometheus.dc2.internal:9090']Fonctions (Counters)
# Req/sec (moyenne sur 5m) rate(http_requests_total[5m]) # Augmentation (sur 1h) increase(http_requests_total[1h]) # Req/sec (pics, basé sur les 2 derniers points) irate(http_requests_total[1m])
Agrégation
# Total des req/sec, groupées par 'job' sum(rate(http_requests_total[5m])) by (job) # CPU moyen (toutes instances) avg(node_cpu_usage_percentage) # CPU moyen (sans le label 'core') avg(node_cpu_usage_percentage) without (core) # Top 5 des instances par RAM topk(5, node_memory_usage_bytes) # Compter le nombre de time series count(http_requests_total)
Histogrammes (Percentiles)
# 95ème percentile de la latence histogram_quantile( 0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, job) ) # Moyenne (calculée depuis un histogramme) sum(rate(http_request_duration_seconds_sum[5m])) by (job) / sum(rate(http_request_duration_seconds_count[5m])) by (job)
Opérateurs
# Opérateurs (>, <, ==, !=) node_cpu_usage_percentage > 80 # Arithmétique # (RAM utilisée en % (Gauge / Gauge)) 100 * (node_memory_total_bytes - node_memory_free_bytes) / node_memory_total_bytes
