Prometheus, Loki & Grafana
Stack d'observabilité moderne pour le monitoring d'infrastructure et d'applications.
Les 3 Piliers de l'Observabilité
Vue d'Ensemble
flowchart LR
APP[Application] --> METRICS[Métriques<br/>Prometheus]
APP --> LOGS[Logs<br/>Loki]
APP --> TRACES[Traces<br/>Tempo/Jaeger]
METRICS --> GRAFANA[Grafana<br/>Dashboards]
LOGS --> GRAFANA
TRACES --> GRAFANA
GRAFANA --> ALERTS[Alerting<br/>Alertmanager]
Comparatif
| Pilier | Technologie | Question | Format | Exemple |
|---|---|---|---|---|
| Métriques | Prometheus | Quoi ? | Séries temporelles (chiffres) | cpu_usage{host="web1"} 90 |
| Logs | Loki / ELK | Pourquoi ? | Texte structuré ou non | ERROR: Connection timeout at line 42 |
| Traces | Tempo / Jaeger | Où ? | Spans avec timing | GET /api/users → 250ms (DB: 200ms) |
Quand utiliser quoi ?
Scénario : "Le site est lent !"
1. MÉTRIQUES (Prometheus)
→ Latence P95 des requêtes HTTP : 2.5s (normal = 200ms)
→ CPU du pod "api" : 95%
2. LOGS (Loki)
→ Recherche "error" dans les logs de "api"
→ Trouve : "Database connection pool exhausted"
3. TRACES (Tempo)
→ Requête /api/checkout prend 2.3s
→ Décomposition : DB query = 2.1s, API call = 0.2s
→ Le problème est la query SQL lente
Prometheus : Le Collecteur de Métriques
Architecture Pull
Contrairement à d'autres systèmes (Graphite, InfluxDB), Prometheus va chercher les métriques (pull).
┌─────────────┐ HTTP GET /metrics ┌──────────────┐
│ Prometheus │ ─────────────────────────────────► │ Application │
│ Server │ ◄───────────────────────────────── │ (/metrics) │
└─────────────┘ 200 OK + metrics format └──────────────┘
│
│ Scrape toutes les 15s (configurable)
│
▼
┌─────────────┐
│ Time Series│
│ Database │
└─────────────┘
Avantages :
- Prometheus contrôle la fréquence de collecte
- Pas de dépendance réseau depuis les apps
- Détection automatique des cibles down
Configuration de Base
# prometheus.yml
global:
scrape_interval: 15s # Collecter toutes les 15s
evaluation_interval: 15s # Évaluer les règles toutes les 15s
scrape_configs:
# Job 1 : Prometheus lui-même
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Job 2 : Node Exporter (métriques système)
- job_name: 'node'
static_configs:
- targets: ['node1:9100', 'node2:9100']
# Job 3 : Application custom
- job_name: 'myapp'
static_configs:
- targets: ['app1:8080', 'app2:8080']
Les Exporters (Adaptateurs)
Prometheus ne sait scraper que le format Prometheus (texte simple). Les exporters convertissent les métriques système/applicatives.
| Exporter | Usage | Port | Métriques clés |
|---|---|---|---|
| node_exporter | Hardware Linux | 9100 | node_cpu_seconds_total, node_memory_*, node_disk_* |
| cadvisor | Conteneurs Docker | 8080 | container_cpu_usage_seconds_total, container_memory_usage_bytes |
| mysqld_exporter | MySQL/MariaDB | 9104 | mysql_global_status_connections, mysql_global_status_queries |
| blackbox_exporter | Probing HTTP/TCP/ICMP | 9115 | probe_success, probe_duration_seconds |
| postgres_exporter | PostgreSQL | 9187 | pg_stat_database_*, pg_locks_count |
# Installation node_exporter (systemd)
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xvf node_exporter-1.7.0.linux-amd64.tar.gz
sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/
# Créer le service
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<EOF
[Unit]
Description=Node Exporter
[Service]
ExecStart=/usr/local/bin/node_exporter
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
# Vérifier
curl http://localhost:9100/metrics
PromQL : Le Langage de Requête
# Métrique instantanée
node_cpu_seconds_total
# Filtrer par label
node_cpu_seconds_total{mode="idle"}
# Taux sur 5 minutes (pour les counters)
rate(http_requests_total[5m])
# Agrégation par instance
sum by (instance) (rate(http_requests_total[5m]))
# Top 5 des pods par CPU
topk(5, sum by (pod) (rate(container_cpu_usage_seconds_total[5m])))
# Mémoire disponible en %
100 * (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# Disque plein à >80%
(node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.2
# Prédiction : disque plein dans 4h
predict_linear(node_filesystem_free_bytes[1h], 4*3600) < 0
Types de Métriques
| Type | Description | Exemple | Utilisation |
|---|---|---|---|
| Counter | Toujours croissant | http_requests_total |
Utiliser rate() ou increase() |
| Gauge | Peut monter/descendre | node_memory_free_bytes |
Utiliser tel quel |
| Histogram | Distribution de valeurs | http_request_duration_seconds |
Utiliser _bucket, _sum, _count |
| Summary | Quantiles précalculés | rpc_duration_seconds |
Utiliser {quantile="0.95"} |
Loki : Prometheus pour les Logs
Concept Clé : Pas d'Indexation Full-Text
┌─────────────────────────────────────────────┐
│ ELK Stack (Elasticsearch) │
│ - Indexe TOUT le texte │
│ - Heavy : 10 GB de logs = 30 GB d'index │
│ - Coûteux en CPU/RAM/disque │
└─────────────────────────────────────────────┘
VS
┌─────────────────────────────────────────────┐
│ Grafana Loki │
│ - Indexe UNIQUEMENT les labels │
│ - Light : 10 GB de logs = 10.5 GB stockés │
│ - Grep distribué sur les logs bruts │
└─────────────────────────────────────────────┘
Philosophie : Loki ne cherche pas à indexer tout le texte comme Elasticsearch. Il stocke les logs bruts et utilise des labels (comme Prometheus) pour les filtrer.
Architecture
┌──────────────┐
│ Application │
│ (stdout) │
└──────┬───────┘
│
▼
┌──────────────┐ HTTP Push ┌──────────────┐
│ Promtail │ ──────────────────► │ Loki │
│ (Log Shipper)│ │ (Server) │
└──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ Grafana │
│ (Frontend) │
└──────────────┘
- Promtail : Agent installé sur chaque serveur/pod, lit les logs et les envoie à Loki
- Loki : Serveur central qui stocke et indexe les logs
- Grafana : Interface de visualisation
Installation Stack Loki (Docker Compose)
# docker-compose.yml
version: "3.8"
services:
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:2.9.0
volumes:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail-config.yaml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
grafana:
image: grafana/grafana:10.2.0
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
volumes:
- grafana-data:/var/lib/grafana
volumes:
loki-data:
grafana-data:
# promtail-config.yaml
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Logs système
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*.log
# Logs Docker
- job_name: containers
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
target_label: 'container'
LogQL : Le Langage de Requête
# Recherche simple
{job="varlogs"}
# Filtrer par container
{container="nginx"}
# Recherche de texte (insensible à la casse)
{container="nginx"} |= "error"
{container="nginx"} |~ "(?i)error"
# Exclure
{container="nginx"} != "200 OK"
# Multiple conditions
{job="varlogs", level="error"} |= "database"
# Regex
{container="api"} |~ "HTTP [45][0-9]{2}"
# Parser JSON
{container="api"} | json | level="error"
# Métriques depuis logs (count over time)
count_over_time({container="nginx"} |= "error" [5m])
# Taux d'erreurs
rate({container="nginx"} |= "error" [5m])
# Top 5 des IPs avec le plus de 404
topk(5,
sum by (ip) (
count_over_time({container="nginx"} |= "404" | regexp `(?P<ip>\d+\.\d+\.\d+\.\d+)` [5m])
)
)
Grafana : La Vitrine
Concept de Data Source
Grafana ne stocke aucune donnée. Il se connecte à des sources externes.
┌──────────────────────────────────────────┐
│ Grafana │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Data Sources │ │
│ │ - Prometheus (metrics) │ │
│ │ - Loki (logs) │ │
│ │ - Tempo (traces) │ │
│ │ - PostgreSQL, MySQL, InfluxDB... │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Dashboards │ │
│ │ - Infrastructure │ │
│ │ - Application Performance │ │
│ │ - Business Metrics │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
Ajouter une Data Source (UI)
1. Grafana → Configuration → Data Sources
2. Add data source
3. Choisir "Prometheus"
4. URL : http://prometheus:9090
5. Save & Test
Ajouter une Data Source (Code)
# datasources.yaml (volume monté dans /etc/grafana/provisioning/datasources/)
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
- name: Loki
type: loki
access: proxy
url: http://loki:3100
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
Dashboards Communautaires
Grafana possède un store de dashboards prêts à l'emploi : grafana.com/grafana/dashboards
| Dashboard | ID | Usage |
|---|---|---|
| Node Exporter Full | 1860 | Monitoring serveur Linux complet |
| Docker Container & Host | 10619 | Monitoring Docker |
| Kubernetes Cluster Monitoring | 7249 | K8s overview |
| NGINX Ingress Controller | 9614 | Ingress K8s |
| PostgreSQL Database | 9628 | Monitoring PostgreSQL |
# Importer un dashboard via UI
Grafana → Dashboards → Import → Enter ID : 1860 → Load
# Ou via API
curl -X POST http://admin:admin@localhost:3000/api/dashboards/import \
-H "Content-Type: application/json" \
-d '{
"dashboard": {
"id": null,
"uid": null,
"title": "Node Exporter Full"
},
"overwrite": true,
"inputs": [{
"name": "DS_PROMETHEUS",
"type": "datasource",
"pluginId": "prometheus",
"value": "Prometheus"
}]
}'
Panels : Les Briques des Dashboards
| Type de Panel | Usage | Exemple |
|---|---|---|
| Time series | Courbes de métriques | CPU over time |
| Gauge | Valeur instantanée avec seuils | Disk usage 75% |
| Stat | Valeur unique grande | Total requests: 1.2M |
| Bar chart | Comparaisons | Requests by endpoint |
| Table | Données tabulaires | Top 10 slowest queries |
| Logs | Affichage de logs Loki | Stream logs from pods |
| Heatmap | Distribution de latences | P50, P95, P99 histogram |
Variables : Dashboards Dynamiques
Variable "instance" :
Query : label_values(node_cpu_seconds_total, instance)
Utilisation dans query :
rate(node_cpu_seconds_total{instance="$instance"}[5m])
Permet de créer un dropdown dans le dashboard pour sélectionner l'instance à monitorer.
Alerting : Grafana Alerting (v9+)
# Exemple d'alerte : CPU > 80% pendant 5 minutes
- alert: HighCPU
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU on {{ $labels.instance }}"
description: "CPU usage is {{ $value }}%"
Grafana peut envoyer les alertes vers :
- Slack
- PagerDuty
- Webhook
- Microsoft Teams
- Discord
Stack Complète : Docker Compose Exemple
# docker-compose.yml - Stack complète Prometheus + Loki + Grafana
version: "3.8"
services:
# === MÉTRIQUES ===
prometheus:
image: prom/prometheus:v2.48.0
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
node-exporter:
image: prom/node-exporter:v1.7.0
ports:
- "9100:9100"
command:
- '--path.rootfs=/host'
volumes:
- '/:/host:ro,rslave'
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.0
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
# === LOGS ===
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
volumes:
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:2.9.0
volumes:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail-config.yaml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
# === VISUALISATION ===
grafana:
image: grafana/grafana:10.2.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
volumes:
prometheus-data:
loki-data:
grafana-data:
# Démarrer la stack
docker-compose up -d
# Accès
# Grafana : http://localhost:3000 (admin/admin)
# Prometheus : http://localhost:9090
# Loki : http://localhost:3100
Référence Rapide
# === PROMETHEUS ===
# Vérifier targets
curl http://localhost:9090/api/v1/targets
# Exécuter une query PromQL via API
curl -G http://localhost:9090/api/v1/query \
--data-urlencode 'query=up'
# Recharger la config sans restart
curl -X POST http://localhost:9090/-/reload
# === LOKI ===
# Pousser des logs manuellement
curl -X POST http://localhost:3100/loki/api/v1/push \
-H "Content-Type: application/json" \
-d '{
"streams": [{
"stream": {"job": "test"},
"values": [["'"$(date +%s)000000000"'", "test log message"]]
}]
}'
# Query LogQL via API
curl -G http://localhost:3100/loki/api/v1/query \
--data-urlencode 'query={job="varlogs"}'
# === GRAFANA ===
# Créer une API key
curl -X POST http://admin:admin@localhost:3000/api/auth/keys \
-H "Content-Type: application/json" \
-d '{"name":"mykey", "role":"Admin"}'
# Lister les dashboards
curl http://admin:admin@localhost:3000/api/search
# === EXPORTERS ===
# Node Exporter
curl http://localhost:9100/metrics | grep node_cpu
# cAdvisor
curl http://localhost:8080/metrics | grep container_cpu
Dépannage
Prometheus : Targets Down
# Vérifier la connectivité
curl http://target-host:9100/metrics
# Vérifier les logs Prometheus
docker logs prometheus
# Causes communes :
# - Firewall bloque le port
# - Exporter non démarré
# - Mauvaise URL dans prometheus.yml
Loki : No Logs Received
# Vérifier Promtail
docker logs promtail
# Tester l'envoi manuel
curl -X POST http://localhost:3100/loki/api/v1/push \
-H "Content-Type: application/json" \
-d '{
"streams": [{
"stream": {"job": "test"},
"values": [["'"$(date +%s)000000000"'", "test"]]
}]
}'
# Vérifier les labels dans Grafana Explore
# → Loki data source → Label browser
Grafana : No Data
# Vérifier la data source
Grafana → Configuration → Data Sources → Test
# Vérifier la query dans Explore
Grafana → Explore → Choisir Prometheus → Metrics browser
# Vérifier les logs Grafana
docker logs grafana