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é | Loki | Elasticsearch |
|---|---|---|
| Modèle d’indexation | Labels uniquement | Index inversé full-text |
| Coût de stockage | ~10x moins | Élevé (index + données) |
| Langage de requête | LogQL (similaire à PromQL) | Lucene / KQL |
| Natif Grafana | Oui — première classe | Via plugin |
| Scalabilité horizontale | Oui (sharding par labels) | Oui (shards) |
| Changements de schéma | Non requis | Réindexation nécessaire |
| Alertes | Loki ruler + Alertmanager | ElastAlert / SIEM |
| Natif Kubernetes | Oui (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
- Dans Grafana, allez dans Connections → Data sources → Add data source → Loki.
- Configurez l’URL sur
http://loki:3100(ou votre adresse Loki). - 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
| Outil | Modèle de stockage | Auto-hébergé | Coût | Natif Grafana | Idéal pour |
|---|---|---|---|---|---|
| Loki | Index de labels + chunks compressés | Oui | Faible | Oui | Équipes avec Grafana/Prometheus |
| Elasticsearch | Index inversé full-text | Oui | Élevé | Via plugin | Recherche full-text, SIEM |
| Fluentd | Agent uniquement (pas de stockage) | Oui | Gratuit | Non | Routage et transformation |
| Datadog Logs | SaaS, propriétaire | Non | Élevé | Non | Enterprise, observabilité complète |
| CloudWatch Logs | SaaS (AWS) | Non | Moyen | Via plugin | Charges 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 logs | Architecture | Stockage | RAM |
|---|---|---|---|
| < 5 Go/jour | Monolithique, nœud unique | Filesystem local | 2 Go |
| 5–50 Go/jour | Monolithique + S3/GCS | Stockage objet | 4–8 Go |
| 50–500 Go/jour | Microservices, 3 ingesters | Objet + TSDB | 16–32 Go |
| > 500 Go/jour | Microservices + Thanos ruler | Objet multi-AZ | 64 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ème | Solution |
|---|---|
connection refused à l’envoi | Vérifiez que le conteneur Loki fonctionne et que le port 3100 est accessible depuis Promtail |
| Timeout des requêtes | Augmentez querier.query_timeout et divisez les requêtes en plages de temps plus courtes |
| Logs Docker manquants | Vérifiez positions.yaml de Promtail pour le chemin du log du conteneur |
| Haute mémoire de l’ingester | Réduisez chunk_idle_period pour décharger les chunks vers le stockage plus tôt |
| La rétention ne fonctionne pas | Assurez-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.