infra / monitoring-observabilite
Logs centralisés : concepts ELK et Loki
Explication
Ce que vous allez apprendre
- Comprendre la différence philosophique entre la stack ELK et Loki
- Déployer Loki avec Promtail, son agent de collecte de logs
- Étiqueter des logs avec des labels pour les rendre filtrables rapidement
- Écrire des requêtes LogQL, le langage de requête des logs inspiré de PromQL
- Mettre en place une politique de rétention pour maîtriser le coût de stockage des logs
Dans quel contexte ?
Une alerte Prometheus signale un taux d'erreur anormal sur mon-api (leçon précédente), mais elle ne dit pas POURQUOI ces requêtes échouent. Il faut aller lire le détail exact de l'erreur — et sur une infrastructure avec plusieurs serveurs, se connecter en SSH sur chacun pour grep un fichier local n'est plus une option viable.
D'abord, il faut choisir une philosophie de stockage des logs
Deux grandes familles d'outils existent, avec des approches fondamentalement différentes. La stack ELK (Elasticsearch, Logstash ou Fluentd, Kibana) indexe le CONTENU complet de chaque ligne de log, ce qui permet une recherche plein texte extrêmement puissante, mais coûte cher en stockage et en CPU à grande échelle.
Loki, développé par Grafana Labs, prend le parti inverse : il n'indexe QUE les labels associés à chaque flux de logs, exactement comme Prometheus le fait pour les métriques. Le contenu brut du log, lui, n'est ni indexé ni analysé à l'écriture — seulement filtré à la lecture.
| Critère | ELK / EFK | Loki |
|---|---|---|
| Indexation | Contenu complet (full-text) | Labels seulement |
| Coût à grande échelle | Élevé | Réduit |
| Recherche | Puissante, plein texte natif | Par label, puis filtre texte sur le résultat |
| Intégration Grafana | Via plugin | Native |
Prérequis
Avoir déjà installé Grafana (leçon précédente) facilite la visualisation des logs de cette leçon, mais Loki peut aussi être interrogé indépendamment via son API.
Une fois ce choix compris, comment les logs arrivent-ils réellement jusqu'à Loki ?
Aucune application n'envoie directement ses logs à Loki. Un agent de collecte intermédiaire, Promtail (l'équivalent de Fluentd côté ELK), lit les fichiers de logs locaux et les transmet en les enrichissant de labels utiles : le nom du job, l'environnement, éventuellement des champs extraits du contenu lui-même.
C'est précisément ce rôle de "traducteur" qui permet à Loki de rester léger : Promtail fait le travail d'extraction et d'étiquetage AVANT que la donnée n'atteigne le stockage central.
Il reste une question naturelle : comment interroger tout ça ?
LogQL reprend délibérément la syntaxe de PromQL : filtrer par label entre accolades ({job="nginx"}), puis affiner avec des filtres texte (|= "timeout" pour "contient", != pour "ne contient pas"). Cette continuité stylistique n'est pas un hasard, elle vient du même éditeur, Grafana Labs.
Astuce
LogQL permet même de calculer des métriques directement depuis un flux de logs, avec la même syntaxe rate(...) que PromQL : sum(rate({job="nginx"} | json | status>=500 [5m])) by (path) calcule un débit d'erreurs 5xx en partant des logs bruts, sans qu'aucune métrique dédiée n'ait été instrumentée dans le code.
Le piège que presque toutes les équipes découvrent trop tard
Piège fréquent
Les logs, contrairement aux métriques, croissent en volume proportionnellement au trafic ET ne s'auto-compriment jamais avec le temps. Sans politique de rétention active (retention_period pour Loki, ou une politique ILM avec des phases hot/warm/delete pour Elasticsearch), le stockage explose silencieusement pendant des mois avant qu'une facture cloud alarmante ne révèle le problème.
Maintenant que logs et métriques sont tous les deux centralisés, il reste un angle mort : suivre une seule requête à travers plusieurs services différents. C'est exactement l'objet du tracing distribué, sujet de la prochaine leçon.
Commandes & code
Logs centralisés : concepts ELK et Loki
Deux philosophies principales :
ELK / EFK (Elasticsearch + Logstash/Fluentd + Kibana)
-> indexe le CONTENU complet de chaque log (full-text search puissant)
-> coûteux en stockage et CPU à grande échelle
Loki (Grafana Labs)
-> n'indexe QUE les labels (comme Prometheus), pas le contenu du log lui-même
-> beaucoup moins cher à opérer, recherche par label puis grep sur le contenu
-> s'intègre nativement dans Grafana, aux côtés de Prometheus# docker-compose.yml — stack Loki + Promtail (agent de collecte) + Grafana
services:
loki:
image: grafana/loki:3.1.0
ports: ["3100:3100"]
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:3.1.0
volumes:
- /var/log:/var/log:ro
- ./promtail-config.yml:/etc/promtail/config.yml:ro
command: -config.file=/etc/promtail/config.yml# promtail-config.yml : quoi lire, comment l'étiqueter
server:
http_listen_port: 9080
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx
static_configs:
- targets: ["localhost"]
labels:
job: nginx
env: production
__path__: /var/log/nginx/*.log
pipeline_stages:
- json: # parse le JSON produit par log_format json_combined (cours Nginx)
expressions:
status: status
path: request_uri
- labels:
status: # promeut "status" en LABEL indexé (recherche rapide)# LogQL : le PromQL des logs, avec la même logique de labels
{job="nginx"} # tous les logs du job nginx
{job="nginx", status="500"} # filtré par label status=500
{job="nginx"} |= "timeout" # filtre texte : contient "timeout"
{job="nginx"} != "healthcheck" # exclut les lignes contenant "healthcheck"
{job="nginx"} | json | status >= 500 # parse JSON puis filtre sur un champ numérique
# Métriques dérivées des logs (comme PromQL, applicable sur un flux de logs)
sum(rate({job="nginx"} | json | status>=500 [5m])) by (path) # débit d'erreurs 5xx par route, calculé depuis les LOGS# Elasticsearch/Fluentd (alternative ELK) : exemple de config Fluentd pour parser des logs JSON
<source>
@type tail
path /var/log/nginx/access.log
tag nginx.access
<parse>
@type json
</parse>
</source>
<match nginx.access>
@type elasticsearch
host elasticsearch
port 9200
index_name nginx-logs-%Y.%m.%d # un index par jour : facilite la rétention/purge
</match># Bonnes pratiques de rétention (les logs coûtent cher à grande échelle)
# Loki : retention_period dans loki-config.yaml
# Elasticsearch : ILM (Index Lifecycle Management) — hot (7j, SSD rapide) -> warm (30j) -> delete (90j)
curl -X PUT "localhost:9200/_ilm/policy/logs-policy" -H "Content-Type: application/json" -d '{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_size": "10gb", "max_age": "1d" } } },
"warm": { "min_age": "7d", "actions": { "shrink": { "number_of_shards": 1 } } },
"delete":{ "min_age": "90d", "actions": { "delete": {} } }
}
}
}'Résumé
- ELK indexe le contenu complet des logs (recherche puissante, coût élevé) ; Loki n'indexe que les labels (moins cher, recherche par label puis filtre texte).
- Le pattern "agent de collecte" (Promtail, Fluentd) qui lit des fichiers/journald et pousse vers un backend centralisé est universel.
- LogQL réutilise la syntaxe et la logique de PromQL : filtres par label, filtre texte, et même des métriques calculées depuis un flux de logs.
- La rétention doit être gérée activement (ILM,
retention_period) : sans purge, le stockage de logs croît indéfiniment.
Exercices pratiques
Mission : retrouver l'aiguille dans la botte de logs nginx
Objectif : Écrire des requêtes LogQL précises pour isoler des erreurs spécifiques dans un flux de logs nginx étiqueté par Promtail, et calculer un débit d'erreurs directement depuis les logs.
Contexte
Les logs nginx de production sont collectés par Promtail vers Loki, au format JSON, avec un pipeline qui extrait status et request_uri et promeut status en label. Un incident de timeouts sporadiques touche uniquement la route de paiement, et personne ne sait encore si ça vient d'une hausse du volume ou d'un vrai taux d'erreur en hausse.