Retour au cours

infra / docker-compose

Observabilité : logs centralisés multi-services

Leçon 181 exercice

Explication

Ce que vous allez apprendre

  • Comprendre la limite de docker compose logs -f dès qu'une stack compte de nombreux services
  • Collecter les logs de tous les conteneurs avec Promtail vers un point central (Loki)
  • Comprendre pourquoi Loki indexe uniquement les labels, pas le texte intégral des logs
  • Explorer et filtrer des logs multi-services avec le langage LogQL
  • Provisionner automatiquement une datasource Grafana pour éviter une reconfiguration manuelle

Dans quel contexte ?

La stack boutique-api compte désormais dix services : l'API, ses trois replicas, la base, le cache, Traefik, et plusieurs workers. Un client signale une erreur 500 précise à 14h32, mais retracer cette erreur en ouvrant docker compose logs -f sur chaque service un par un prendrait des heures. Avec Loki et Grafana, une seule requête LogQL retrouve instantanément le message d'erreur, quel que soit le conteneur qui l'a émis.

Le problème passé un certain nombre de services

D'abord, avec un ou deux services, docker compose logs -f suffit largement pour déboguer : tu vois tout défiler dans un seul terminal. Mais dès qu'une stack compte une dizaine de services, comme l'API, la base, le cache et le proxy Traefik vu à la leçon précédente, cette approche devient impraticable.

Ce qui devient difficile à faire manuellement

Imagine une erreur qui traverse plusieurs services en quelques secondes : impossible de la retracer en ouvrant chaque flux de logs un par un. Il faut un endroit central où chercher, avec une seule requête, à travers TOUS les services en même temps.

Première brique : collecter les logs

Promtail est l'agent qui résout la première moitié du problème : il lit directement sur le disque de l'hôte les fichiers de logs JSON que Docker écrit pour chaque conteneur, dans /var/lib/docker/containers, et les envoie vers un point central.

Deuxième brique : stocker et indexer

Ce point central s'appelle Loki. Il a une particularité importante à bien comprendre : contrairement à Elasticsearch, il n'indexe QUE les labels (le nom du conteneur, le niveau de log), jamais le texte intégral des messages. Résultat : Loki consomme beaucoup moins de ressources, au prix d'une recherche plein texte un peu plus lente.

Troisième brique : visualiser et chercher

Grafana vient ensuite se brancher sur Loki comme source de données, pour offrir une interface où explorer ces logs. Une fois configurée dans grafana-provisioning/datasources/loki.yml, cette connexion est automatique dès le démarrage, sans reconfiguration manuelle à chaque redémarrage de la stack.

Comment interroger concrètement ces logs

Le langage de requête de Loki s'appelle LogQL, et il fonctionne en deux temps : on sélectionne d'abord un flux par ses labels, comme {container="docker-compose-api-1"}, puis on filtre le texte à l'intérieur, comme |= "ERROR". C'est un peu comme un grep appliqué à distance sur tous les conteneurs concernés, en une seule requête.

BriqueRôle
PromtailCollecte les logs JSON de chaque conteneur sur le disque hôte
LokiStocke et indexe uniquement les labels (léger, pas de texte intégral)
GrafanaInterface de recherche et de visualisation, branchée sur Loki
LogQLLangage de requête : {label="x"} |= "texte"

Prérequis

Cette leçon suppose que tu es à l'aise avec les réseaux Compose et les volumes (leçons précédentes) : la stack d'observabilité est elle-même un ensemble de services Compose classiques.

Les pièges à connaître

Sans limite max-size/max-file sur le driver de logs de chaque service, le disque de l'hôte peut se remplir avant même que Promtail n'ait le temps d'envoyer les logs vers Loki. Autre piège : oublier de provisionner la datasource Grafana automatiquement, ce qui obligerait à la reconfigurer à la main à chaque redémarrage complet de l'environnement.

Piège fréquent

Un service batch_job sans limite de logs qui écrit une ligne par seconde peut remplir plusieurs gigaoctets sur le disque de l'hôte en quelques jours, avant même que Promtail n'ait pu tout transmettre à Loki. Fixe toujours max-size et max-file sur chaque service, même secondaire.

Ce cours touche ici à sa fin : tu es maintenant capable de construire une stack Compose complète, de son premier service jusqu'à son observabilité en production.

Commandes & code

Observabilité : logs centralisés multi-services

Sans agrégation centrale, corréler les logs de 10 services par docker compose logs devient impraticable en production.

yaml
services:
  api:
    build: ./api
    logging:
      driver: "json-file"      # driver par défaut, lu par Promtail via le disque
      options:
        max-size: "10m"
        max-file: "3"
        tag: "{{.Name}}"

  loki:
    image: grafana/loki:3.1.0
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - loki_data:/loki
    networks:
      - observability

  promtail:
    image: grafana/promtail:3.1.0
    command: -config.file=/etc/promtail/config.yml
    volumes:
      - ./promtail-config.yml:/etc/promtail/config.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro   # lit les logs JSON des conteneurs
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - loki
    networks:
      - observability

  grafana:
    image: grafana/grafana:11.2.0
    ports:
      - "3001:3000"
    volumes:
      - ./grafana-provisioning:/etc/grafana/provisioning:ro   # datasource Loki auto-configurée
      - grafana_data:/var/lib/grafana
    depends_on:
      - loki
    networks:
      - observability

networks:
  observability:

volumes:
  loki_data:
  grafana_data:
yaml
# promtail-config.yml - scrape les logs Docker via le provider docker (labels = métadonnées)
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        target_label: container
      - source_labels: [__meta_docker_container_log_stream]
        target_label: stream
yaml
# grafana-provisioning/datasources/loki.yml - datasource pré-configurée au démarrage
apiVersion: 1
datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    isDefault: true
bash
# Requêter les logs directement via LogQL, sans ouvrir Grafana
curl -s -G "http://localhost:3100/loki/api/v1/query_range" \
  --data-urlencode 'query={container="docker-compose-api-1"} |= "ERROR"' \
  --data-urlencode 'limit=50'

Résumé

  • Loki indexe seulement les LABELS (pas le texte complet) : très léger comparé à Elasticsearch.
  • Promtail découvre les conteneurs via le socket Docker et lit leurs logs JSON directement sur disque.
  • La provision Grafana (datasources/*.yml) évite de reconfigurer la datasource à chaque redémarrage.
  • LogQL ({label="x"} |= "texte") filtre les logs par label puis par contenu, comme un grep distribué.

Exercices pratiques

1 disponible
1

Mission : retrouver une erreur 500 parmi dix services

Objectif : Utiliser LogQL pour retrouver un incident précis et sécuriser la rétention des logs contre la saturation disque.

Contexte

La stack boutique-api compte désormais dix services. Un client signale une erreur 500 précise à 14h32. Un développeur cherche à la retrouver dans Loki avec une requête qui ne filtre que sur le label container, sans jamais utiliser |=. Par ailleurs, le service batch_job n'a aucune limite de logs configurée.

Corrige la requête et sécurise la configuration des logs.

Résoudre l’exercice →