Aller au contenu

Surveillance complète avec Prometheus, Grafana et Loki : métriques, logs et conteneurs Docker

Rogelio Guerra Riverón
Auteur
Rogelio Guerra Riverón
Construction de ma propre infrastructure web depuis zéro. Je documente chaque étape : serveurs, réseaux, conteneurs et tout ce qui se présente.

Un serveur sans surveillance est un serveur aveugle. Vous ne savez pas quand le disque se remplit, quel conteneur consomme trop de RAM, ou combien de requêtes 404 votre site génère. Cet article documente comment j’ai configuré la pile complète : Prometheus + Node Exporter + Grafana + Loki + Promtail.

L’architecture
#

[Servidor doméstico]
  ├── node-exporter        → métricas del sistema (CPU, RAM, disco, red)
  ├── docker-stats-        → métricas de contenedores (textfile collector)
  │   collector
  ├── prometheus           → recolecta y almacena métricas
  ├── loki                 → agrega y almacena logs
  ├── promtail             → envía logs de Nginx y syslog a Loki
  └── grafana              → dashboards de todo lo anterior

Tous les services tournent sur Docker, coordonnés par le même docker-compose.yml.

Métriques système : Node Exporter
#

Node Exporter expose les métriques du matériel et du SE. L’astuce : il doit tourner avec network_mode: host pour voir les vraies interfaces réseau du serveur. S’il tourne en réseau Docker, il ne voit que l’interface eth0 du conteneur.

  node-exporter:
    image: prom/node-exporter:v1.8.2
    network_mode: host
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
      - ./textfile-collector:/textfile:ro
    command:
      - --path.procfs=/host/proc
      - --path.sysfs=/host/sys
      - --path.rootfs=/rootfs
      - --web.listen-address=127.0.0.1:9100
      - --collector.textfile.directory=/textfile

Il écoute sur 127.0.0.1:9100. Prometheus l’atteint par 172.17.0.1:9100 (l’IP de l’hôte depuis le réseau Docker).

Métriques de conteneurs : docker stats + textfile collector
#

Le problème avec cAdvisor est qu’il ne fonctionne pas avec Docker 29 et le driver de stockage overlayfs en cgroupv2 — il échoue avec “failed to identify read-write layer ID”.

La solution : un conteneur léger qui exécute docker stats toutes les 30 secondes et écrit le résultat au format Prometheus dans un fichier que Node Exporter lit.

#!/bin/bash
# docker_stats.sh
OUTFILE="/textfile/docker_stats.prom"
TMPFILE="${OUTFILE}.tmp"

{
echo "# HELP docker_container_cpu_percent CPU usage percentage per container"
echo "# TYPE docker_container_cpu_percent gauge"
# ... más definiciones ...

docker stats --no-stream --format \
  '{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}' 2>/dev/null | \
while IFS='|' read -r name cpu mem net; do
    cpu_val=$(echo "$cpu" | tr -d '%' | tr ',' '.')
    # ... conversión de unidades ...
    echo "docker_container_cpu_percent{name=\"${name}\"} ${cpu_val}"
    echo "docker_container_memory_bytes{name=\"${name}\"} ${mem_used_bytes}"
    echo "docker_container_running{name=\"${name}\"} 1"
done

# Contenedores parados
docker ps -a --filter "status=exited" --format '{{.Names}}' 2>/dev/null | \
while read -r name; do
    echo "docker_container_running{name=\"${name}\"} 0"
done

} > "$TMPFILE" && mv "$TMPFILE" "$OUTFILE"

L’écriture atomique (tmp → final) empêche Prometheus de lire un fichier à moitié.

  docker-stats-collector:
    image: docker:27-cli
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./textfile-collector:/textfile
      - ./docker_stats.sh:/docker_stats.sh:ro
    entrypoint: sh -c "apk add --no-cache bc > /dev/null 2>&1; while true; do sh /docker_stats.sh; sleep 30; done"

Prometheus : collecter et conserver
#

  prometheus:
    image: prom/prometheus:v2.51.2
    networks:
      - monitoring
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus
      - --storage.tsdb.retention.time=30d
      - --web.enable-lifecycle

Configuration du scraping :

global:
  scrape_interval: 15s

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

  - job_name: node
    static_configs:
      - targets: [172.17.0.1:9100]
    relabel_configs:
      - target_label: host
        replacement: servidor-casa

172.17.0.1 est l’IP de l’hôte accessible depuis le réseau Docker bridge. Les données sont conservées 30 jours.

Logs : Loki + Promtail
#

Loki stocke les logs sans indexer le contenu complet — uniquement les étiquettes (labels). Promtail les collecte et les envoie avec des étiquettes comme job, host, filename.

  promtail:
    image: grafana/promtail:3.3.2
    user: root
    networks:
      - monitoring
    volumes:
      - ./promtail-config.yml:/etc/promtail/config.yml:ro
      - ./promtail-data:/tmp/promtail
      - ~/infra/web/logs:/logs/nginx:ro
      - /var/log:/logs/host:ro

Il doit tourner en root pour lire /var/log.

Grafana : dashboards
#

Grafana se connecte à Prometheus et Loki comme sources de données. Les dashboards les plus utiles :

Système (Node Exporter) :

  • CPU total et par cœur
  • RAM utilisée / libre / cache
  • Disque : utilisation par partition, IOPS, débit
  • Réseau : trafic entrant/sortant par interface

Conteneurs (docker stats) :

  • CPU % par conteneur
  • RAM par conteneur vs limite
  • État (running/stopped)
  • Trafic réseau par conteneur

Logs (Loki) :

  • Logs Nginx en temps réel
  • Requêtes par code de statut (200, 301, 404, 500)
  • Top des IPs avec le plus de requêtes
  • Top des routes les plus accédées

Problème : [$__range] dans les requêtes instantanées de Loki
#

Lors de l’utilisation de panneaux de type “stat” ou “piechart” avec Loki, la variable [$__range] ne se résout pas — Grafana renvoie “empty duration string”. La solution est d’utiliser une durée fixe :

# MAL (en paneles stat/piechart):
sum by(status) (count_over_time({job="nginx"} | pattern ... [$__range]))

# BIEN:
sum by(status) (count_over_time({job="nginx"} | pattern ... [24h]))

Les panneaux de type “time series” acceptent correctement [$__interval].

Sécurité de la pile
#

  • Prometheus et Loki n’ont pas d’accès externe — uniquement sur le réseau interne monitoring
  • Grafana est le seul point d’accès, protégé avec Traefik et Let’s Encrypt
  • GF_AUTH_ANONYMOUS_ENABLED=false et GF_USERS_ALLOW_SIGN_UP=false dans Grafana
  • Node Exporter écoute uniquement sur 127.0.0.1, non exposé sur toutes les interfaces

Résultat
#

Avec cette stack, tu as une visibilité complète du serveur : quels processus consomment des ressources, quels conteneurs échouent, quelles requêtes reçoit ton web et quelles erreurs il génère. Tout dans des dashboards accessibles depuis monitor.serviciosrogeliowar.com.


Équipement recommandé
#

Liens d’affiliation. Aucun coût supplémentaire pour toi.