TL;DR — Résumé Rapide

Configurez Grafana Loki pour l'agrégation de logs avec Promtail. LogQL, tableaux de bord, alertes et pipelines multi-tenant en production expliqués en détail.

Grafana Loki est un système d’agrégation de logs scalable horizontalement construit sur les mêmes principes que Prometheus — il indexe uniquement les labels, pas le texte complet des logs, ce qui le rend beaucoup moins coûteux que la stack ELK. Ce guide couvre l’architecture de Loki, l’installation via Docker Compose, la configuration de Promtail, le langage de requêtes LogQL, la construction de tableaux de bord Grafana, les alertes, le multi-tenancy et le dimensionnement en production.

Prérequis

  • Docker et Docker Compose (ou un cluster Kubernetes pour l’installation via Helm).
  • Grafana 10+ (ou Grafana Cloud) pour la visualisation.
  • Connaissance de base de la configuration YAML et des outils en ligne de commande.

Architecture de Loki

Loki répartit son travail entre quatre composants principaux :

  • Distributor — Reçoit les flux de logs de Promtail/agents, valide les labels et les distribue aux ingesters via hashing cohérent.
  • Ingester — Conserve les logs récents en mémoire (taille de chunk configurable), décharge les chunks compressés vers le stockage objet et maintient un WAL pour la sécurité en cas de panne.
  • Querier — Exécute les requêtes LogQL sur les chunks en mémoire (récents) et en stockage (historiques).
  • Compactor — Fusionne et déduplique les tables d’index boltdb-shipper et applique les politiques de rétention.

En mode monolithique (par défaut pour les installations à nœud unique), les quatre composants s’exécutent dans un seul binaire. Lorsque le volume de logs dépasse ~50 Go/jour, il est recommandé de passer en mode microservices où chaque composant scale indépendamment.

L’idée clé du design : Loki ne construit jamais d’index inversé du contenu des logs. Une requête avec |= "OutOfMemoryError" effectue un grep distribué sur les chunks correspondant aux labels sélectionnés.


Pourquoi Loki plutôt que ELK ?

FonctionnalitéLokiElasticsearch
Modèle d’indexationLabels uniquementIndex inversé full-text
Coût de stockage~10x moinsÉlevé (index + données)
Langage de requêteLogQL (similaire à PromQL)Lucene / KQL
Natif GrafanaOui — première classeVia plugin
Scalabilité horizontaleOui (sharding par labels)Oui (shards)
Changements de schémaNon requisRéindexation nécessaire
AlertesLoki ruler + AlertmanagerElastAlert / SIEM
Natif KubernetesOui (chart Helm)Oui (opérateur ECK)

Étape 1 : Installation avec Docker Compose

Créez un répertoire de projet et le docker-compose.yml suivant :

version: "3.8"
services:
  loki:
    image: grafana/loki:3.0.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:3.0.0
    volumes:
      - ./promtail-config.yaml:/etc/promtail/config.yaml
      - /var/log:/var/log:ro
      - /run/log/journal:/run/log/journal:ro
    command: -config.file=/etc/promtail/config.yaml

  grafana:
    image: grafana/grafana:10.4.0
    ports:
      - "3000:3000"
    environment:
      GF_AUTH_ANONYMOUS_ENABLED: "true"
      GF_AUTH_ANONYMOUS_ORG_ROLE: Admin

volumes:
  loki-data:

Étape 2 : Configurer Loki

Créez loki-config.yaml :

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

ingester:
  lifecycler:
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
  chunk_idle_period: 5m
  chunk_retain_period: 30s
  wal:
    enabled: true
    dir: /loki/wal

schema_config:
  configs:
    - from: 2024-01-01
      store: boltdb-shipper
      object_store: filesystem
      schema: v12
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/index
    cache_location: /loki/boltdb-cache
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

compactor:
  working_directory: /loki/compactor
  shared_store: filesystem
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h

limits_config:
  retention_period: 30d
  ingestion_rate_mb: 16
  ingestion_burst_size_mb: 32
  max_query_length: 721h

Étape 3 : Configurer Promtail

Créez promtail-config.yaml :

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

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

scrape_configs:
  - job_name: journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
        host: __HOSTNAME__
    relabel_configs:
      - source_labels: [__journal__systemd_unit]
        target_label: unit

  - job_name: nginx
    static_configs:
      - targets: [localhost]
        labels:
          job: nginx
          env: prod
          __path__: /var/log/nginx/access.log

  - job_name: docker
    static_configs:
      - targets: [localhost]
        labels:
          job: docker
          __path__: /var/lib/docker/containers/*/*-json.log
    pipeline_stages:
      - json:
          expressions:
            log: log
            stream: stream
      - output:
          source: log

Démarrez la stack :

docker compose up -d
docker compose logs -f loki

Étape 4 : Langage de Requêtes LogQL

LogQL a deux formes : requêtes de logs et requêtes de métriques.

Sélecteurs de stream (obligatoires, toujours en premier) :

{job="nginx"}
{job="nginx", env="prod"}
{job=~"nginx|apache"}

Expressions de filtre (pipe après le sélecteur) :

{job="nginx"} |= "error"
{job="nginx"} != "health_check"
{job="nginx"} |~ "5[0-9]{2}"

Expressions de parser — extrait des champs des logs structurés :

{job="app"} | json
{job="app"} | logfmt
{job="app"} | json | level="error"

Requêtes de métriques — convertit les logs en séries temporelles :

rate({job="nginx"} |= "error" [5m])
count_over_time({job="app"} | json | level="error" [1h])
sum by (status) (rate({job="nginx"} | logfmt | status=~"5.." [5m]))
topk(5, sum by (uri) (rate({job="nginx"}[10m])))

Étape 5 : Source de Données et Tableaux de Bord Grafana

  1. Dans Grafana, allez dans Connections → Data sources → Add data source → Loki.
  2. Configurez l’URL sur http://loki:3100 (ou votre adresse Loki).
  3. Cliquez sur Save & test — vous verrez “Data source connected.”

Conseils pour le panneau Explore :

  • Utilisez Live tail pour diffuser les logs en temps réel — idéal pour déboguer les déploiements.
  • Cliquez sur n’importe quelle ligne de log pour voir le contexte du log — les lignes avant et après la correspondance.
  • Passez à l’onglet Metrics pour visualiser les requêtes rate() en panneaux de séries temporelles.

Étape 6 : Alertes avec le Ruler Loki

Ajoutez un bloc ruler à loki-config.yaml :

ruler:
  storage:
    type: local
    local:
      directory: /loki/rules
  rule_path: /loki/rules-temp
  alertmanager_url: http://alertmanager:9093
  ring:
    kvstore:
      store: inmemory
  enable_api: true

Créez /loki/rules/fake/rules.yaml :

groups:
  - name: alertes-app
    interval: 1m
    rules:
      - alert: TauxErreurEleve
        expr: |
          sum(rate({job="app"} | json | level="error" [5m])) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Taux d'erreur élevé dans les logs de l'app"

Loki vs. Alternatives

OutilModèle de stockageAuto-hébergéCoûtNatif GrafanaIdéal pour
LokiIndex de labels + chunks compressésOuiFaibleOuiÉquipes avec Grafana/Prometheus
ElasticsearchIndex inversé full-textOuiÉlevéVia pluginRecherche full-text, SIEM
FluentdAgent uniquement (pas de stockage)OuiGratuitNonRoutage et transformation
Datadog LogsSaaS, propriétaireNonÉlevéNonEnterprise, observabilité complète
CloudWatch LogsSaaS (AWS)NonMoyenVia pluginCharges de travail natives AWS

Multi-Tenancy

Activez le multi-tenancy avec auth_enabled: true dans Loki. Chaque requête doit inclure X-Scope-OrgID: <tenant-id>. Configurez Promtail pour envoyer l’en-tête :

clients:
  - url: http://loki:3100/loki/api/v1/push
    tenant_id: equipe-backend

Recommandations de Dimensionnement en Production

Volume de logsArchitectureStockageRAM
< 5 Go/jourMonolithique, nœud uniqueFilesystem local2 Go
5–50 Go/jourMonolithique + S3/GCSStockage objet4–8 Go
50–500 Go/jourMicroservices, 3 ingestersObjet + TSDB16–32 Go
> 500 Go/jourMicroservices + Thanos rulerObjet multi-AZ64 Go+

Activez toujours le WAL (ingester.wal.enabled: true) en production pour éviter la perte de données lors des redémarrages de l’ingester.


Résolution de Problèmes

ProblèmeSolution
connection refused à l’envoiVérifiez que le conteneur Loki fonctionne et que le port 3100 est accessible depuis Promtail
Timeout des requêtesAugmentez querier.query_timeout et divisez les requêtes en plages de temps plus courtes
Logs Docker manquantsVérifiez positions.yaml de Promtail pour le chemin du log du conteneur
Haute mémoire de l’ingesterRéduisez chunk_idle_period pour décharger les chunks vers le stockage plus tôt
La rétention ne fonctionne pasAssurez-vous que compactor.retention_enabled: true et limits_config.retention_period sont définis

Résumé

  • L’indexation par labels uniquement rend Loki 10x moins cher qu’Elasticsearch pour le même volume.
  • Promtail envoie des logs depuis des fichiers, le journal systemd et Docker avec enrichissement de labels.
  • LogQL supporte le filtrage de streams, le parsing JSON/logfmt, les calculs de taux et l’agrégation.
  • Le ruler Loki évalue les alertes LogQL selon un calendrier et les route vers Alertmanager.
  • Pour la production, remplacez le stockage filesystem par S3/GCS et activez WAL et cache de chunks.

Articles Connexes