Creer un dashboard de monitoring avec Grafana

Credit : Logo officiel

Creer un dashboard de monitoring avec Grafana

Dylan D. — Agent Support Technique Serveur DevOps 1919 mots 10 min de lecture

Pourquoi Grafana + Prometheus est devenu mon stack de monitoring par defaut

Lundi 8h45, alerte SMS : "web2.monsite.fr disk usage 92%". Le serveur d'un client tournait depuis 14 mois sans broncher, et logrotate avait silencieusement arrete de tourner sur un volume de logs Nginx en croissance constante. Sans Grafana et son alerte configuree 2 ans plus tot, je l'aurais decouvert quand le site serait tombe. La, 15 minutes pour purger, 5 pour relancer logrotate, 0 minute de downtime.

Grafana + Prometheus c'est la stack que j'utilise sur tous mes serveurs perso et chez plusieurs clients. La doc officielle est correcte mais suppose que vous savez deja ce que vous faites. Cet article est le guide que j'aurais aime trouver quand j'ai monte ma premiere stack en 2021 : installation, requetes PromQL utiles, dashboards a importer, alertes vraiment utiles, et les pieges qui font perdre 2 jours a chaque setup.

L'architecture en gros

[Serveurs cibles]                  [Serveur monitoring]              [Vous]
  node_exporter:9100  ----+
  nginx_exporter:9113  ---+--->  Prometheus:9090  --->  Grafana:3000  --->  Browser
  mysql_exporter:9104  ---+        (scrape, store)       (dashboards)
  blackbox_exporter:9115 -+        (PromQL queries)      (alertes)

Prometheus tire les metriques toutes les 15 secondes (modele pull). Grafana interroge Prometheus pour afficher des dashboards. Les exporters traduisent l'etat d'un systeme/service en metriques au format Prometheus.

Dimensionnement : un VPS 2 vCPU / 4 Go RAM tient 30 serveurs cibles avec 15 jours de retention. Au-dela, passer a 8 Go et SSD NVMe.

Installer node_exporter sur les serveurs a surveiller

node_exporter collecte CPU, RAM, disque, reseau, processus, etc. A installer sur CHAQUE machine a monitorer.

cd /tmp
VERSION=1.8.2
wget https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/node_exporter-${VERSION}.linux-amd64.tar.gz
tar xzf node_exporter-${VERSION}.linux-amd64.tar.gz
sudo mv node_exporter-${VERSION}.linux-amd64/node_exporter /usr/local/bin/
sudo useradd -r -s /sbin/nologin node_exporter

Service systemd /etc/systemd/system/node_exporter.service :

[Unit]
Description=Prometheus Node Exporter
After=network.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=:9100 \
  --collector.systemd \
  --collector.processes
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter

# Verifier
curl -s http://localhost:9100/metrics | head -20
sudo systemctl status node_exporter

Important : ne pas exposer le port 9100

Les exporters n'ont aucune authentification. Bloquer le port 9100 sur le pare-feu vis-a-vis d'Internet. Soit limiter via UFW :

sudo ufw allow from 10.0.0.5 to any port 9100 proto tcp

Soit utiliser un VPN entre Prometheus et les exporters (WireGuard est parfait pour ca). Cf hardening Linux.

Installer Prometheus sur le serveur de monitoring

VERSION=2.54.1
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v${VERSION}/prometheus-${VERSION}.linux-amd64.tar.gz
tar xzf prometheus-${VERSION}.linux-amd64.tar.gz
sudo mv prometheus-${VERSION}.linux-amd64/{prometheus,promtool} /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo cp -r prometheus-${VERSION}.linux-amd64/{consoles,console_libraries} /etc/prometheus/
sudo useradd -r -s /sbin/nologin prometheus
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

Config /etc/prometheus/prometheus.yml :

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    monitor: 'main'

rule_files:
  - "alerts.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['localhost:9093']

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'nodes'
    static_configs:
      - targets:
          - 'web1.monsite.fr:9100'
          - 'web2.monsite.fr:9100'
          - 'db1.monsite.fr:9100'
        labels:
          env: 'production'

  - job_name: 'nginx'
    static_configs:
      - targets: ['web1.monsite.fr:9113', 'web2.monsite.fr:9113']

  - job_name: 'mysql'
    static_configs:
      - targets: ['db1.monsite.fr:9104']

  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://monsite.fr
          - https://shop.monsite.fr
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: localhost:9115

Service systemd /etc/systemd/system/prometheus.service :

[Unit]
Description=Prometheus
After=network.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/ \
  --storage.tsdb.retention.time=15d \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.listen-address=127.0.0.1:9090
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus

# Verifier que les targets sont up
curl -s http://127.0.0.1:9090/api/v1/targets | jq '.data.activeTargets[] | {job:.labels.job,health}'

Installer Grafana

sudo apt install -y apt-transport-https software-properties-common gnupg
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" \
  | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install grafana
sudo systemctl enable --now grafana-server

Acces sur http://serveur:3000 (admin/admin, changer immediatement).

Configurer la source de donnees Prometheus

Dans Grafana :

  1. Connections > Data sources > Add new data source
  2. Choisir Prometheus
  3. URL : http://localhost:9090
  4. Save & Test

Vous devriez voir "Successfully queried the Prometheus API".

Importer des dashboards : ne reinventez pas la roue

Grafana a une bibliotheque enorme de dashboards prets a l'emploi. Mes incontournables :

ID Nom Pour
1860 Node Exporter Full Metriques systeme detaillees
12708 Node Exporter Quickstart Vue d'ensemble simplifiee
12559 Linux Stats Alternative legere
12006 NGINX Stats Nginx
7362 MySQL Overview MySQL/MariaDB
13659 Blackbox Exporter Uptime checks

Importer :

  1. Dashboards > New > Import
  2. Entrer l'ID (ex : 1860)
  3. Selectionner la source Prometheus
  4. Load

Le dashboard 1860 est massif : 200+ panels couvrant CPU, memoire, disque, reseau, IO, processus, conntrack. J'aurais mis 2 semaines a le refaire moi-meme.

Requetes PromQL essentielles

Pour creer vos propres panels ou comprendre les dashboards importes :

# CPU utilise en pourcentage
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memoire utilisee en pourcentage
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# Espace disque utilise sur la partition root
(1 - node_filesystem_avail_bytes{mountpoint="/"} 
   / node_filesystem_size_bytes{mountpoint="/"}) * 100

# Trafic reseau entrant en bytes par seconde
irate(node_network_receive_bytes_total{device="eth0"}[5m])

# Charge moyenne sur 1 minute
node_load1

# Uptime en jours
(time() - node_boot_time_seconds) / 86400

# Top 5 des processus consommant le plus de CPU
topk(5, rate(namedprocess_namegroup_cpu_seconds_total[5m]))

# Nombre de requetes Nginx par seconde
rate(nginx_http_requests_total[5m])

# Latence moyenne MySQL
avg(rate(mysql_global_status_questions[5m]))

# Disponibilite des sites (1 = up, 0 = down)
probe_success

# Certificat SSL expire dans X jours
(probe_ssl_earliest_cert_expiry - time()) / 86400

Alertes : la valeur reelle de la stack

Un dashboard sans alerte, c'est une jolie deco. Configurer les alertes dans /etc/prometheus/alerts.yml :

groups:
  - name: serveurs
    rules:
      - alert: HighCpuUsage
        expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU eleve sur {{ $labels.instance }}"
          description: "CPU > 85% depuis 5 minutes (valeur actuelle: {{ $value | humanize }}%)"

      - alert: DiskSpaceLow
        expr: (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 > 85
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "Disque presque plein sur {{ $labels.instance }}"

      - alert: InstanceDown
        expr: up == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.instance }} ne repond plus"

      - alert: HighMemoryUsage
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
        for: 5m
        labels:
          severity: warning

      - alert: SslCertExpiringSoon
        expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 14
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Certificat SSL expire dans {{ $value | humanize }} jours"

      - alert: NginxHighErrorRate
        expr: rate(nginx_http_requests_total{status=~"5.."}[5m]) > 1
        for: 5m
        labels:
          severity: warning

Verifier la syntaxe :

promtool check rules /etc/prometheus/alerts.yml
sudo systemctl reload prometheus

Notifications via Alertmanager ou Grafana

Deux options. Soit Alertmanager (more flexible), soit les contact points natifs de Grafana (plus simple).

Dans Grafana : Alerting > Contact points > New > selectionner Email/Slack/Discord/Telegram. Tester avec le bouton "Test".

Personnellement, j'utilise Alertmanager pour le routage avance (nuit = SMS uniquement critical, jour = mail + Slack).

Securiser Grafana en production

Ne JAMAIS exposer Grafana directement en HTTP sur Internet. Editer /etc/grafana/grafana.ini :

[server]
root_url = https://monitoring.monsite.fr/
serve_from_sub_path = false
http_addr = 127.0.0.1
http_port = 3000

[security]
admin_password = MotDePasseFort2026!
cookie_secure = true
strict_transport_security = true
strict_transport_security_max_age_seconds = 86400
content_security_policy = true

[users]
allow_sign_up = false
auto_assign_org_role = Viewer

[auth]
disable_login_form = false
oauth_auto_login = false

[smtp]
enabled = true
host = smtp.gmail.com:587
user = monitoring@monsite.fr
password = AppPassword
from_address = monitoring@monsite.fr

Reverse proxy Nginx avec SSL devant. Modele : reverse proxy Nginx avec SSL :

server {
    listen 443 ssl http2;
    server_name monitoring.monsite.fr;

    ssl_certificate /etc/letsencrypt/live/monitoring.monsite.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monitoring.monsite.fr/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /api/live/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:3000;
    }
}

Erreurs courantes et leur fix

1. Targets en "Down" dans Prometheus

90% du temps : pare-feu bloque le port 9100, ou node_exporter n'ecoute que sur 127.0.0.1. Verifier :

curl http://web1.monsite.fr:9100/metrics
sudo ss -tlnp | grep 9100
sudo ufw status

2. Disque /var/lib/prometheus se remplit vite

Retention par defaut 15 jours peut etre trop long. Reduire dans le service systemd : --storage.tsdb.retention.time=7d. Surveillance :

du -sh /var/lib/prometheus/

3. Dashboard 1860 affiche "No data"

Les labels ne matchent pas. Le dashboard utilise job="node" mais votre config utilise job="nodes". Soit renommer dans prometheus.yml, soit editer le dashboard pour ajuster les variables.

4. Alertes ne se declenchent pas

Verifier dans Prometheus UI > Alerts. Si "Pending" mais jamais "Firing", la duree for: est plus longue que l'incident. Reduire pour tester.

5. Grafana lent avec gros dashboards

Reduire le scrape_interval cause beaucoup de points. Soit downsampling avec recording rules :

groups:
  - name: aggregations
    interval: 1m
    rules:
      - record: instance:node_cpu:avg1m
        expr: 100 - avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[1m])) * 100

Puis utiliser instance:node_cpu:avg1m dans les dashboards : 60x moins de points a charger.

Ajouter d'autres exporters utiles

L'ecosysteme Prometheus offre des exporters pour pratiquement tout. Ceux que j'ajoute systematiquement selon le contexte :

nginx_exporter

docker run -d --name nginx-exporter --restart unless-stopped \
  -p 9113:9113 nginx/nginx-prometheus-exporter:latest \
  -nginx.scrape-uri=http://nginx:8080/stub_status

Necessite d'activer le module stub_status dans Nginx :

location /stub_status {
    stub_status;
    access_log off;
    allow 127.0.0.1;
    deny all;
}

mysqld_exporter

VERSION=0.15.1
wget https://github.com/prometheus/mysqld_exporter/releases/download/v${VERSION}/mysqld_exporter-${VERSION}.linux-amd64.tar.gz
tar xzf mysqld_exporter-*.tar.gz
sudo mv mysqld_exporter-*/mysqld_exporter /usr/local/bin/

Fichier /etc/mysqld_exporter.cnf :

[client]
user=exporter
password=ExporterPassword!

User MySQL minimal :

CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'ExporterPassword!';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';

blackbox_exporter

Indispensable pour surveiller la disponibilite externe de vos sites :

VERSION=0.25.0
wget https://github.com/prometheus/blackbox_exporter/releases/download/v${VERSION}/blackbox_exporter-${VERSION}.linux-amd64.tar.gz
tar xzf blackbox_exporter-*.tar.gz
sudo mv blackbox_exporter-*/blackbox_exporter /usr/local/bin/

Il mesure : disponibilite HTTP, code de retour, latence, validite du certificat SSL, expiration. Le dashboard 7587 affiche tout ca proprement.

Sauvegarder Grafana et Prometheus

Deux choses a sauvegarder :

  1. La base SQLite Grafana qui contient dashboards, users, datasources : /var/lib/grafana/grafana.db
  2. Les dashboards en JSON exportes (provisioning)

Script backup :

#!/bin/bash
DATE=$(date +%Y%m%d)
sudo systemctl stop grafana-server
cp /var/lib/grafana/grafana.db /backup/grafana_${DATE}.db
sudo systemctl start grafana-server

# Export des dashboards via API
for uid in $(curl -s -u admin:pwd http://localhost:3000/api/search | jq -r '.[].uid'); do
    curl -s -u admin:pwd http://localhost:3000/api/dashboards/uid/${uid} \
      > /backup/dashboards/${uid}.json
done

Les donnees temporelles Prometheus dans /var/lib/prometheus/ sont en general considerees comme jetables (15 jours de retention), mais si vous voulez les preserver, snapshot via API :

curl -XPOST http://127.0.0.1:9090/api/v1/admin/tsdb/snapshot
# Snapshot dans /var/lib/prometheus/snapshots/<timestamp>/

Pour aller plus loin

Du monitoring qui sert vraiment

Un bon dashboard Grafana, ce n'est pas celui qui a 80 panels colores. C'est celui qui repond en 5 secondes a la question "qu'est-ce qui cloche en ce moment ?". Mes regles : un dashboard "Overview" avec 6-8 panels max (CPU, RAM, disque, reseau, requetes/s, erreurs 5xx, latence, uptime), et des dashboards specialises (Nginx, MySQL, applicatifs) accessibles en un clic. Et surtout, des alertes calibrees : trop d'alertes tue les alertes. Mieux vaut 5 alertes par semaine pertinentes que 50 alertes "warning" qu'on apprend a ignorer. Le jour ou ca casse vraiment, vous voulez que le SMS reveille, pas qu'il soit le 30e de la nuit.

# Articles similaires

Sur les memes sujets et plus loin