Docker et Docker Compose installés sur votre serveur. Connaissances de base en administration Linux (systemd, fichiers de configuration YAML). Pour l'installation de Docker, consultez le guide d'installation Docker.
Pourquoi Prometheus ? Pull vs Push, architecture réelle
La surveillance d'une infrastructure Linux se résumait longtemps à Nagios et ses plugins, ou à Zabbix et son agent. Ces outils fonctionnent en modèle push : chaque agent envoie ses données vers un collecteur central. Prometheus inverse ce paradigme avec le modèle pull : c'est le serveur Prometheus qui interroge périodiquement chaque cible sur son endpoint /metrics.
Ce renversement n'est pas cosmétique. Il change fondamentalement la manière dont le monitoring est opéré :
- Détection des instances mortes : si Prometheus ne peut pas scraper une cible, la métrique
uppasse à 0. Avec le push, une instance silencieuse est indétectable. - Configuration centralisée : toutes les cibles sont définies dans Prometheus, pas dispersées sur chaque agent. Un seul fichier YAML pour voir toute l'infrastructure.
- Debug simple : l'endpoint
/metricsest du texte brut lisible aveccurl, sans agent intermédiaire à débugger. - Écosystème standardisé : le format d'exposition Prometheus est devenu un standard OpenMetrics adopté par des centaines d'applications (Nginx, PostgreSQL, Redis, Kubernetes, etc.).
Les quatre composants de la stack
┌─────────────────┐ scrape /metrics ┌──────────────────┐ alertes ┌─────────────────┐
│ Node Exporter │ ◄─────────────────── │ Prometheus │ ──────────► │ AlertManager │
│ (port 9100) │ │ (port 9090) │ │ (port 9093) │
│ Nginx Exporter │ ◄─────────────────── │ TSDB locale │ │ Slack / Email │
│ (port 9113) │ │ PromQL │ │ PagerDuty │
│ Blackbox Exp. │ ◄─────────────────── │ Alerting rules │ └─────────────────┘
│ (port 9115) │ └──────────────────┘
└─────────────────┘ │
│ datasource
▼
┌──────────────────┐
│ Grafana │
│ (port 3000) │
│ Dashboards │
│ Alerting │
└──────────────────┘
- Prometheus : collecte (scrape) les métriques, les stocke dans une base de données temporelle locale (TSDB), évalue les règles d'alertes toutes les 15 secondes.
- Exporters : traduisent les métriques système ou applicatives au format Prometheus. Node Exporter pour Linux, Nginx Exporter pour Nginx, Blackbox Exporter pour les sondes HTTP/SSL.
- AlertManager : reçoit les alertes de Prometheus, les déduplique, les groupe, et les route vers les bons canaux avec gestion des silences et des inhibitions.
- Grafana : interface de visualisation qui interroge Prometheus via PromQL pour afficher des dashboards interactifs. Ne stocke rien, ne collecte rien.
Installation : Docker Compose stack complète
Déployer la stack complète avec Docker Compose est la méthode la plus reproductible. Un seul fichier décrit toute l'infrastructure de monitoring.
Structure des fichiers
mkdir -p ~/monitoring/{prometheus,grafana,alertmanager}
mkdir -p ~/monitoring/prometheus/rules
mkdir -p ~/monitoring/grafana/{provisioning/datasources,provisioning/dashboards,dashboards}
cd ~/monitoring
Docker Compose complet
# ~/monitoring/docker-compose.yml
version: '3.8'
networks:
monitoring:
driver: bridge
volumes:
prometheus_data:
driver: local
grafana_data:
driver: local
alertmanager_data:
driver: local
services:
# ─── Prometheus ────────────────────────────────────────────────
prometheus:
image: prom/prometheus:v2.53.0
container_name: prometheus
restart: unless-stopped
user: "65534:65534" # nobody:nobody, pas de root
ports:
- "127.0.0.1:9090:9090" # Écoute uniquement sur localhost
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=10GB'
- '--web.enable-lifecycle'
- '--web.enable-admin-api'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
networks:
- monitoring
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ─── Node Exporter ─────────────────────────────────────────────
node-exporter:
image: prom/node-exporter:v1.8.2
container_name: node-exporter
restart: unless-stopped
pid: host # Accès aux métriques de l'hôte
ports:
- "127.0.0.1:9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
- '--collector.systemd'
networks:
- monitoring
# ─── AlertManager ──────────────────────────────────────────────
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: alertmanager
restart: unless-stopped
user: "65534:65534"
ports:
- "127.0.0.1:9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--web.external-url=http://localhost:9093'
- '--cluster.listen-address=' # Désactive le clustering si instance unique
networks:
- monitoring
depends_on:
- prometheus
# ─── Grafana ───────────────────────────────────────────────────
grafana:
image: grafana/grafana:11.1.0
container_name: grafana
restart: unless-stopped
user: "472:472"
ports:
- "127.0.0.1:3000:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-changeme}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_SERVER_ROOT_URL: https://grafana.example.com
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
networks:
- monitoring
depends_on:
- prometheus
healthcheck:
test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/api/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# ─── Blackbox Exporter ─────────────────────────────────────────
blackbox-exporter:
image: prom/blackbox-exporter:v0.25.0
container_name: blackbox-exporter
restart: unless-stopped
ports:
- "127.0.0.1:9115:9115"
volumes:
- ./prometheus/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
networks:
- monitoring
Notez que tous les ports sont liés à
127.0.0.1 uniquement. Ne jamais exposer Prometheus, AlertManager ou Node Exporter directement sur Internet. Utilisez un reverse proxy Nginx avec TLS pour accéder à Grafana depuis l'extérieur.
Configuration Prometheus
prometheus.yml : scrape_configs et service discovery
# ~/monitoring/prometheus/prometheus.yml
global:
scrape_interval: 15s # Collecte toutes les 15 secondes
evaluation_interval: 15s # Évaluation des règles toutes les 15s
scrape_timeout: 10s # Timeout par scrape
# Labels ajoutés à toutes les métriques de cette instance
external_labels:
datacenter: 'paris-1'
environment: 'production'
# Chargement des règles d'alertes
rule_files:
- "/etc/prometheus/rules/*.yml"
# Configuration AlertManager
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
timeout: 10s
scrape_configs:
# ── Prometheus lui-même ────────────────────────────────────────
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# ── Node Exporter ──────────────────────────────────────────────
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
labels:
hostname: 'monitoring-server'
role: 'monitoring'
# ── Serveurs distants (targets statiques) ──────────────────────
- job_name: 'servers'
scrape_interval: 30s # Override par job
static_configs:
- targets:
- '192.168.1.10:9100'
- '192.168.1.11:9100'
- '192.168.1.12:9100'
labels:
environment: 'production'
role: 'web'
- targets:
- '192.168.1.20:9100'
- '192.168.1.21:9100'
labels:
environment: 'production'
role: 'database'
# ── Blackbox Exporter (sondes HTTP) ───────────────────────────
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- 'https://exemple.com'
- 'https://api.exemple.com/health'
- 'https://grafana.exemple.com'
relabel_configs:
# Copie l'URL cible dans le paramètre ?target=
- source_labels: [__address__]
target_label: __param_target
# Utilise l'URL comme label "instance"
- source_labels: [__param_target]
target_label: instance
# Redirige le scrape vers le Blackbox Exporter
- target_label: __address__
replacement: blackbox-exporter:9115
# ── Blackbox Exporter (expiration SSL) ────────────────────────
- job_name: 'blackbox-ssl'
metrics_path: /probe
params:
module: [ssl_expiry]
static_configs:
- targets:
- 'exemple.com:443'
- 'api.exemple.com:443'
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
# ── Service discovery via fichier (pour infra dynamique) ──────
- job_name: 'dynamic-servers'
file_sd_configs:
- files:
- /etc/prometheus/targets/*.json
refresh_interval: 1m # Recharge la liste chaque minute
Configuration Blackbox Exporter
# ~/monitoring/prometheus/blackbox.yml
modules:
# Vérification HTTP avec code 200
http_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
valid_status_codes: [200, 201, 204]
method: GET
follow_redirects: true
preferred_ip_protocol: "ip4"
tls_config:
insecure_skip_verify: false
# Vérification expiration SSL uniquement
ssl_expiry:
prober: http
timeout: 5s
http:
method: HEAD
fail_if_not_ssl: true
tls_config:
insecure_skip_verify: false
# Ping ICMP
icmp:
prober: icmp
timeout: 5s
icmp:
preferred_ip_protocol: "ip4"
# Vérification port TCP
tcp_connect:
prober: tcp
timeout: 5s
PromQL : requêtes essentielles commentées
PromQL opère sur deux types de sélecteurs : les instant vectors (valeur instantanée) et les range vectors (ensemble de valeurs sur une fenêtre, notés [5m]). La règle d'or : utilisez toujours rate() ou increase() sur les compteurs (métriques suffixées _total), jamais de valeur brute.
5 requêtes essentielles pour surveiller un serveur Linux
# ─── 1. Utilisation CPU en pourcentage ────────────────────────────────────────
# Principe : 100% - % de temps passé en mode "idle"
# rate() calcule le taux de variation/seconde sur 5 minutes (lisse les pics)
# avg by (instance) agrège tous les cœurs CPU par serveur
100 - (
avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100
)
# ─── 2. Mémoire utilisée en pourcentage ───────────────────────────────────────
# MemAvailable inclut la mémoire récupérable (caches) = mémoire réellement libre
# Plus fiable que (MemTotal - MemFree) qui ignore les caches Linux
(1 - (
node_memory_MemAvailable_bytes /
node_memory_MemTotal_bytes
)) * 100
# ─── 3. Espace disque utilisé en pourcentage ──────────────────────────────────
# Filtré sur le filesystem racine, à adapter selon vos points de montage
# node_filesystem_avail_bytes = espace disponible pour les utilisateurs non-root
(1 - (
node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"}
/
node_filesystem_size_bytes{mountpoint="/", fstype!="tmpfs"}
)) * 100
# ─── 4. Trafic réseau entrant (bits/s) ────────────────────────────────────────
# irate() utilise les deux derniers points pour détecter les pics instantanés
# Multiplication par 8 pour convertir octets → bits
# Adapter "eth0" à votre interface réseau principale
irate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8
# ─── 5. Percentile 95 de la durée des requêtes HTTP ──────────────────────────
# histogram_quantile reconstruit les percentiles depuis les buckets
# Nécessite un exporter qui expose des histogrammes (nginx, traefik, etc.)
# le "le" label signifie "less than or equal to" (borne supérieure du bucket)
histogram_quantile(0.95,
sum by (le, job) (
rate(http_request_duration_seconds_bucket[5m])
)
)
Requêtes avancées : topk, prédiction, taux d'erreur
# Top 5 des processus par consommation CPU
topk(5,
sum by (groupname) (
rate(namedprocess_namegroup_cpu_seconds_total[5m])
)
)
# Prédiction : disk full dans combien d'heures ?
# predict_linear projette la tendance des 6 dernières heures
# Alerte si remplissage prévu dans moins de 48h
predict_linear(
node_filesystem_avail_bytes{mountpoint="/"}[6h],
48 * 3600
) < 0
# Taux d'erreurs HTTP (4xx + 5xx) en pourcentage
(
sum(rate(nginx_http_requests_total{status=~"[45].."}[5m]))
/
sum(rate(nginx_http_requests_total[5m]))
) * 100
# Disponibilité sur 24h (pour SLO dashboard)
avg_over_time(up{job="servers"}[24h]) * 100
Règles d'alerte Prometheus
# ~/monitoring/prometheus/rules/node_alerts.yml
groups:
- name: instance_availability
interval: 15s # Override de l'evaluation_interval global
rules:
# ── Instance inaccessible ────────────────────────────────────────────────
- alert: InstanceDown
expr: up == 0
for: 2m # Déclenche seulement si DOWN depuis 2 minutes
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} inaccessible"
description: "Le job {{ $labels.job }} sur {{ $labels.instance }} ne répond plus depuis 2 minutes. Vérifiez l'état du serveur et du service."
runbook: "https://wiki.exemple.com/runbooks/instance-down"
- name: system_resources
rules:
# ── CPU élevé ────────────────────────────────────────────────────────────
- alert: HighCpuUsage
expr: |
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 5m
labels:
severity: critical
annotations:
summary: "CPU critique sur {{ $labels.instance }}"
description: "Utilisation CPU de {{ printf \"%.1f\" $value }}% sur {{ $labels.instance }} depuis 5 minutes."
- alert: HighCpuUsageWarning
expr: |
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 75
for: 10m
labels:
severity: warning
annotations:
summary: "CPU élevé sur {{ $labels.instance }}"
description: "Utilisation CPU de {{ printf \"%.1f\" $value }}% sur {{ $labels.instance }} depuis 10 minutes."
# ── Mémoire critique ─────────────────────────────────────────────────────
- alert: HighMemoryUsage
expr: |
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
for: 5m
labels:
severity: critical
annotations:
summary: "Mémoire critique sur {{ $labels.instance }}"
description: "{{ printf \"%.1f\" $value }}% de mémoire utilisée sur {{ $labels.instance }}."
# ── Disque > 85% ──────────────────────────────────────────────────────────
- alert: DiskSpaceWarning
expr: |
(1 - node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"} /
node_filesystem_size_bytes{mountpoint="/", fstype!="tmpfs"}) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "Espace disque faible sur {{ $labels.instance }}"
description: "{{ printf \"%.1f\" $value }}% du disque utilisé sur {{ $labels.instance }} (partition /)."
- alert: DiskSpaceCritical
expr: |
(1 - node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"} /
node_filesystem_size_bytes{mountpoint="/", fstype!="tmpfs"}) * 100 > 95
for: 5m
labels:
severity: critical
annotations:
summary: "Disque presque plein sur {{ $labels.instance }}"
description: "{{ printf \"%.1f\" $value }}% du disque utilisé sur {{ $labels.instance }}. Intervention urgente requise."
# ── Disque saturé dans 48h ───────────────────────────────────────────────
- alert: DiskWillFillIn48h
expr: |
predict_linear(node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"}[6h], 48 * 3600) < 0
for: 1h
labels:
severity: warning
annotations:
summary: "Disque plein prévu dans 48h sur {{ $labels.instance }}"
description: "Selon la tendance des 6 dernières heures, le disque de {{ $labels.instance }} sera plein dans moins de 48 heures."
- name: ssl_certificates
rules:
# ── Certificat SSL expirant dans 30 jours ────────────────────────────────
- alert: SslCertificateExpiringSoon
expr: |
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 30
for: 1h
labels:
severity: warning
annotations:
summary: "Certificat SSL expire bientôt pour {{ $labels.instance }}"
description: "Le certificat SSL de {{ $labels.instance }} expire dans {{ printf \"%.0f\" $value }} jours."
- alert: SslCertificateExpired
expr: |
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 7
for: 1h
labels:
severity: critical
annotations:
summary: "Certificat SSL critique pour {{ $labels.instance }}"
description: "Le certificat SSL de {{ $labels.instance }} expire dans {{ printf \"%.0f\" $value }} jours. Action immédiate requise."
- name: http_availability
rules:
# ── Endpoint HTTP inaccessible ───────────────────────────────────────────
- alert: HttpEndpointDown
expr: probe_success == 0
for: 3m
labels:
severity: critical
annotations:
summary: "Endpoint HTTP inaccessible : {{ $labels.instance }}"
description: "L'URL {{ $labels.instance }} ne répond plus depuis 3 minutes."
AlertManager : routes, receivers, inhibitions
# ~/monitoring/alertmanager/alertmanager.yml
global:
resolve_timeout: 5m
# Configuration SMTP globale
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alertmanager@exemple.com'
smtp_auth_username: 'alertmanager@exemple.com'
smtp_auth_password: 'votre-app-password'
smtp_require_tls: true
# ─── Arbre de routage ─────────────────────────────────────────────────────────
route:
# Groupement par alertname + datacenter pour éviter les doublons
group_by: ['alertname', 'datacenter', 'job']
group_wait: 30s # Attendre 30s pour grouper les alertes initiales
group_interval: 5m # Intervalle entre notifications pour un groupe actif
repeat_interval: 4h # Répétition si l'alerte persiste
receiver: 'email-ops' # Receiver par défaut
routes:
# Alertes critiques → Slack en priorité + répétition fréquente
- match:
severity: critical
receiver: 'slack-critical'
group_wait: 10s # Moins d'attente pour les critiques
repeat_interval: 30m
continue: true # Continue vers les autres routes (email aussi)
# Alertes critiques → email aussi
- match:
severity: critical
receiver: 'email-ops'
# Alertes warning → Slack canal séparé
- match:
severity: warning
receiver: 'slack-warning'
repeat_interval: 6h
# ─── Receivers ────────────────────────────────────────────────────────────────
receivers:
- name: 'email-ops'
email_configs:
- to: 'ops-team@exemple.com'
subject: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }} - {{ .CommonAnnotations.summary }}'
body: |
{{ range .Alerts }}
Alerte : {{ .Annotations.summary }}
Description : {{ .Annotations.description }}
Sévérité : {{ .Labels.severity }}
Instance : {{ .Labels.instance }}
Début : {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{ end }}
send_resolved: true
headers:
X-Priority: '1'
- name: 'slack-critical'
slack_configs:
- api_url: 'https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXX/xxxxxxxxxxxxxxxxxxxxxxxx'
channel: '#alertes-critiques'
username: 'AlertManager'
icon_emoji: ':rotating_light:'
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}'
text: |
{{ range .Alerts }}
*{{ .Annotations.summary }}*
{{ .Annotations.description }}
*Instance :* {{ .Labels.instance }}
*Datacenter :* {{ .Labels.datacenter }}
{{ end }}
color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
send_resolved: true
- name: 'slack-warning'
slack_configs:
- api_url: 'https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXX/xxxxxxxxxxxxxxxxxxxxxxxx'
channel: '#alertes-monitoring'
username: 'AlertManager'
icon_emoji: ':warning:'
title: '[WARNING] {{ .CommonLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.summary }}\n{{ .Annotations.description }}{{ end }}'
send_resolved: true
# ─── Inhibitions ──────────────────────────────────────────────────────────────
inhibit_rules:
# Si l'instance est DOWN, supprimer les alertes CPU/RAM/Disk de la même instance
- source_match:
alertname: 'InstanceDown'
target_match_re:
alertname: '(HighCpuUsage|HighMemoryUsage|DiskSpaceWarning|DiskSpaceCritical)'
equal: ['instance']
# Si une alerte critique existe, supprimer les warnings du même alertname
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'instance']
Grafana : dashboards, variables et provisioning as code
Datasource provisionnée automatiquement
Le provisioning Grafana permet de déployer les datasources et dashboards sans passer par l'interface web. Configuration as code, versionnée en Git.
# ~/monitoring/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
uid: prometheus-uid # UID fixe pour référencer dans les dashboards
editable: false
jsonData:
httpMethod: POST
prometheusType: Prometheus
prometheusVersion: 2.53.0
timeInterval: 15s
queryTimeout: 60s
Dashboard provisioning
# ~/monitoring/grafana/provisioning/dashboards/default.yml
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: 'Production'
type: file
disableDeletion: true # Empêche la suppression via l'UI
updateIntervalSeconds: 30 # Recharge les fichiers toutes les 30s
allowUiUpdates: false # Les modifications UI ne persistent pas
options:
path: /var/lib/grafana/dashboards
Dashboard as code : Node Exporter simplifié
{
"title": "Infrastructure Overview",
"uid": "infra-overview",
"tags": ["production", "node-exporter"],
"refresh": "30s",
"time": { "from": "now-3h", "to": "now" },
"templating": {
"list": [
{
"name": "instance",
"type": "query",
"label": "Serveur",
"datasource": "Prometheus",
"query": "label_values(node_uname_info, instance)",
"refresh": 2,
"multi": false,
"includeAll": true,
"allValue": ".*"
}
]
},
"panels": [
{
"type": "stat",
"title": "CPU Usage",
"gridPos": { "x": 0, "y": 0, "w": 6, "h": 4 },
"targets": [{
"expr": "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode='idle', instance=~'$instance'}[5m])) * 100)",
"legendFormat": "{{ instance }}"
}],
"options": { "reduceOptions": { "calcs": ["lastNotNull"] } },
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 75 },
{ "color": "red", "value": 90 }
]
}
}
}
}
]
}
Import du dashboard Node Exporter Full (ID 1860)
Grafana dispose d'un catalogue de dashboards communautaires. Le dashboard 1860 (Node Exporter Full) couvre toutes les métriques système en un seul import :
- Allez dans Dashboards > New > Import
- Entrez l'ID 1860 dans le champ "Import via grafana.com"
- Sélectionnez votre datasource Prometheus dans le menu déroulant
- Cliquez sur Import
Autres dashboards utiles : 13978 (Node Exporter Quickstart), 7587 (Docker monitoring), 9614 (Nginx), 9628 (PostgreSQL).
Variables de dashboard
Les variables transforment un dashboard statique en outil interactif multi-serveurs. Créez une variable instance via Dashboard Settings > Variables > New variable :
- Type : Query
- Query :
label_values(node_uname_info, instance) - Refresh : On time range change
- Multi-value : activé pour comparer plusieurs serveurs
Utilisez ensuite $instance dans toutes vos requêtes : node_cpu_seconds_total{instance=~"$instance"}. Le filtre s'applique à tous les panneaux simultanément.
Annotations automatiques depuis AlertManager
Les annotations Grafana permettent de superposer les événements d'alerte sur les graphiques. Configurez une annotation automatique dans les paramètres du dashboard :
- Source : Prometheus
- Query :
ALERTS{alertstate="firing"} - Title :
{{alertname}} - Tags :
{{severity}}
Exporters supplémentaires
Nginx Exporter
# Activer stub_status dans Nginx
# /etc/nginx/conf.d/stub_status.conf
server {
listen 127.0.0.1:8080;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
# Ajout dans docker-compose.yml
nginx-exporter:
image: nginx/nginx-prometheus-exporter:1.1.0
container_name: nginx-exporter
restart: unless-stopped
ports:
- "127.0.0.1:9113:9113"
command:
- -nginx.scrape-uri=http://host-gateway:8080/nginx_status
networks:
- monitoring
extra_hosts:
- "host-gateway:host-gateway"
PostgreSQL Exporter
# Ajout dans docker-compose.yml
postgres-exporter:
image: prometheuscommunity/postgres-exporter:v0.15.0
container_name: postgres-exporter
restart: unless-stopped
environment:
DATA_SOURCE_NAME: "postgresql://exporter:motdepasse@postgres:5432/postgres?sslmode=disable"
ports:
- "127.0.0.1:9187:9187"
networks:
- monitoring
-- Créer l'utilisateur PostgreSQL pour l'exporter
CREATE USER exporter WITH PASSWORD 'motdepasse';
ALTER USER exporter SET SEARCH_PATH TO exporter,pg_catalog;
GRANT CONNECT ON DATABASE postgres TO exporter;
GRANT pg_monitor TO exporter;
Ajout des exporters dans Prometheus
# Ajout dans prometheus.yml / scrape_configs
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
- job_name: 'postgresql'
static_configs:
- targets: ['postgres-exporter:9187']
Démarrage et opérations
# Démarrer toute la stack
cd ~/monitoring
docker compose up -d
# Vérifier l'état de tous les services
docker compose ps
# Voir les logs en temps réel
docker compose logs -f prometheus
# Valider la configuration Prometheus avant reload
docker compose exec prometheus promtool check config /etc/prometheus/prometheus.yml
# Valider les règles d'alertes
docker compose exec prometheus promtool check rules /etc/prometheus/rules/node_alerts.yml
# Recharger Prometheus sans redémarrer (hot-reload)
curl -X POST http://localhost:9090/-/reload
# Vérifier les targets actifs
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -E "health|job|instance"
# Lister les alertes actives dans AlertManager
curl -s http://localhost:9093/api/v2/alerts | python3 -m json.tool
Haute disponibilité : Thanos ou Grafana Mimir
Prometheus seul présente deux limites en production critique : une seule instance (SPOF), et une rétention limitée par la capacité disque locale (15-30 jours recommandés). Thanos et Grafana Mimir résolvent ces deux problèmes.
Architecture Thanos
Instance Prometheus 1 ──── Thanos Sidecar ────┐
│
Instance Prometheus 2 ──── Thanos Sidecar ────┤──► Thanos Query ──► Grafana
│ (dédup)
Prometheus HA pair S3 / Minio ◄────────┘
(rétention longue) Thanos Store
(query S3)
Déploiement Thanos Sidecar
# Extension docker-compose pour Thanos
thanos-sidecar:
image: thanosio/thanos:v0.35.0
container_name: thanos-sidecar
command:
- sidecar
- --prometheus.url=http://prometheus:9090
- --tsdb.path=/prometheus
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:10902
- --objstore.config-file=/etc/thanos/s3.yml
volumes:
- prometheus_data:/prometheus
- ./thanos/s3.yml:/etc/thanos/s3.yml:ro
networks:
- monitoring
thanos-query:
image: thanosio/thanos:v0.35.0
container_name: thanos-query
command:
- query
- --http-address=0.0.0.0:9091
- --endpoint=thanos-sidecar:10901
- --query.replica-label=replica
ports:
- "127.0.0.1:9091:9091"
networks:
- monitoring
# ~/monitoring/thanos/s3.yml
type: S3
config:
bucket: monitoring-thanos
endpoint: s3.eu-west-3.amazonaws.com
region: eu-west-3
access_key: AKIAXXXXXXXXXXXXXXXX
secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Grafana Mimir offre les mêmes fonctionnalités que Thanos (HA, stockage S3, rétention illimitée) avec une architecture monolithe plus simple à déployer. Il accepte nativement les remote_write de Prometheus via le protocole Prometheus. Idéal si vous débutez avec la HA Prometheus.
Rétention et sizing
| Cibles | Métriques/instance | RAM recommandée | Disque (30j) |
|---|---|---|---|
| 5 serveurs | ~1 000 | 512 Mo | 5 Go |
| 20 serveurs | ~1 000 | 2 Go | 20 Go |
| 100 serveurs | ~1 000 | 8 Go | 100 Go |
| 1 000 serveurs | ~1 000 | 32 Go | → Thanos/Mimir |
Troubleshooting
Prometheus ne scrape pas une cible
# Lister les targets et leur état
curl -s http://localhost:9090/api/v1/targets | \
python3 -c "import sys,json; [print(t['scrapeUrl'], t['health'], t.get('lastError','')) for t in json.load(sys.stdin)['data']['activeTargets']]"
# Tester manuellement l'endpoint de la cible
curl -v http://192.168.1.10:9100/metrics | head -20
# Vérifier les logs Prometheus
docker compose logs prometheus --tail=50 | grep -i error
# Tester la connectivité depuis le conteneur Prometheus
docker compose exec prometheus wget -qO- http://node-exporter:9100/metrics | head -5
Grafana ne charge pas les données
# Vérifier que Prometheus répond
curl -s http://localhost:9090/api/v1/query?query=up | python3 -m json.tool
# Tester la datasource depuis Grafana UI
# Configuration > Data Sources > Prometheus > Save & Test
# Vérifier les logs Grafana
docker compose logs grafana --tail=50 | grep -i error
AlertManager ne reçoit pas les alertes
# Vérifier la connexion Prometheus -> AlertManager
curl -s http://localhost:9090/api/v1/alertmanagers | python3 -m json.tool
# Voir les alertes en attente dans Prometheus
curl -s http://localhost:9090/api/v1/alerts | python3 -m json.tool
# Vérifier la config AlertManager
docker compose exec alertmanager amtool check-config /etc/alertmanager/alertmanager.yml
# Tester l'envoi d'une alerte manuelle
curl -XPOST http://localhost:9093/api/v2/alerts -H "Content-Type: application/json" -d '[{
"labels": {"alertname": "TestAlert", "severity": "warning"},
"annotations": {"summary": "Test depuis curl"}
}]'
Votre infrastructure de monitoring est complète. Commencez par importer le dashboard 1860 dans Grafana pour avoir immédiatement une vue exhaustive de vos serveurs Linux. Ajoutez ensuite progressivement vos règles d'alertes et vos exporters métier selon vos besoins.
Conclusion
Vous disposez maintenant d'une stack de monitoring production-grade qui couvre l'ensemble du cycle observabilité :
- Prometheus collecte et stocke les métriques de tous vos serveurs via un modèle pull fiable, avec détection automatique des instances tombées.
- Node Exporter + Blackbox Exporter exposent les métriques système et surveillent vos endpoints HTTP et certificats SSL.
- PromQL permet des requêtes expressives pour calculer CPU, RAM, disk, taux d'erreur et percentiles de latence.
- Grafana visualise tout en dashboards interactifs avec variables, annotations et alerting natif. Le dashboard 1860 couvre 95% des besoins dès l'import.
- AlertManager route les alertes intelligemment avec groupement, inhibitions et silences pour éviter les tempêtes d'alertes.
- Thanos ou Mimir étendent la stack pour la haute disponibilité et la rétention longue durée quand l'infrastructure grandit.
Pour aller plus loin, explorez Loki (centralisation des logs, même stack Grafana), Tempo (tracing distribué) et Pyroscope (profiling continu) pour compléter les trois piliers de l'observabilité : métriques, logs et traces.
Commentaires