Aller au contenu
medium

05 — Loki : centralisation des logs

23 min de lecture

Les métriques vous disent quoi (combien de requêtes, quelle latence). Les logs vous disent pourquoi (le message d’erreur exact, la stack trace). Après avoir configuré Prometheus pour les métriques et Alertmanager pour les notifications, vous allez maintenant ajouter la gestion des logs centralisés.

Loki est souvent appelé le “Prometheus des logs”. Comme Prometheus, il indexe par labels plutôt que par contenu complet. Cela le rend léger, économique et rapide — parfait pour Kubernetes.

À la fin de ce module, vous saurez :

  1. Expliquer l’architecture Loki/Promtail
  2. Installer Loki et Promtail sur Kubernetes
  3. Requêter les logs avec LogQL
  4. Parser les logs JSON pour extraire des champs
  5. Générer des métriques à partir des logs
  6. Créer des dashboards et alertes sur les logs
  1. Prometheus et Grafana fonctionnent

    Fenêtre de terminal
    kubectl get pods -n observability -l "app.kubernetes.io/name in (prometheus,grafana)"

    Ce que vous devez voir : Les pods en Running.

  2. L’application OpenTelemetry Demo génère du trafic

    Fenêtre de terminal
    kubectl get pods -n otel-demo | head -5

    Ce que vous devez voir : Plusieurs pods en Running.

  3. Le repo Helm Grafana est disponible

    Fenêtre de terminal
    helm repo list | grep grafana

    Ce que vous devez voir : La ligne grafana.

    Si absent :

    Fenêtre de terminal
    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update
  4. Vous êtes dans le répertoire du lab

    Fenêtre de terminal
    cd ~/lab-observability

Architecture Loki

ComposantRôleAnalogie
PodsÉcrivent leurs logs sur stdout/stderrL’application qui parle
PromtailCollecte les logs et les enrichit avec les labels KubernetesLe coursier qui transporte le courrier
LokiStocke les logs, indexe par labelsL’archiviste qui classe le courrier
GrafanaInterface pour chercher et visualiserLe guichet pour consulter les archives

L’approche traditionnelle (ELK/Splunk) :

  • Indexe tout le contenu de chaque log
  • Recherche full-text puissante
  • Stockage coûteux (chaque mot est indexé)
  • Ingestion lourde

L’approche Loki :

  • Indexe seulement les labels (namespace, pod, container…)
  • Stockage économique (les logs bruts sont compressés)
  • Recherche par labels d’abord, puis filtrage de contenu
  • Ingestion légère
CritèreLokiELK/Splunk
IndexationLabels seulementContenu complet
Coût stockage✅ Faible❌ Élevé
Recherche full-text⚠️ Séquentielle✅ Indexée
Intégration Grafana✅ Native⚠️ Plugin
Syntaxe requêteLogQL (≈ PromQL)Propre à chaque outil
Complexité déploiement✅ Simple⚠️ Complexe

Conclusion : Loki est le choix recommandé pour la majorité des cas DevOps. Réservez ELK/Splunk aux besoins de recherche full-text intensive (logs d’application avec messages très variés, analyse forensique).

Le chart loki-stack de Grafana installe Loki et Promtail ensemble. C’est la méthode la plus simple pour commencer.

Examinez la configuration que nous allons utiliser :

Fenêtre de terminal
cat 05-loki/helm-values/loki-stack.yaml

Contenu :

# Loki - serveur de logs
loki:
enabled: true
persistence:
enabled: true
size: 10Gi
config:
limits_config:
retention_period: 168h # 7 jours
reject_old_samples: true
reject_old_samples_max_age: 168h
chunk_store_config:
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: true
retention_period: 168h
# Promtail - collecteur de logs
promtail:
enabled: true
config:
clients:
- url: http://loki:3100/loki/api/v1/push
snippets:
pipelineStages:
# Parser les logs JSON automatiquement
- cri: {}
- json:
expressions:
level: level
msg: msg
trace_id: trace_id
span_id: span_id
- labels:
level:
trace_id:
span_id:

Ce que fait cette configuration :

SectionEffet
loki.persistenceLes logs sont stockés sur disque (pas perdus au redémarrage)
loki.config.limits_config.retention_periodLes logs plus vieux que 7 jours sont supprimés
promtail.config.snippets.pipelineStagesLes logs JSON sont parsés et le niveau extrait comme label
  1. Installez loki-stack

    Fenêtre de terminal
    helm upgrade --install loki grafana/loki-stack \
    -n observability \
    -f 05-loki/helm-values/loki-stack.yaml \
    --wait

    Ce que vous devez voir :

    Release "loki" does not exist. Installing it now.
    level=WARN msg="this chart is deprecated"
    NAME: loki
    LAST DEPLOYED: [date]
    NAMESPACE: observability
    STATUS: deployed

    Patientez 1-2 minutes que les pods démarrent.

  2. Vérifiez que Loki tourne

    Fenêtre de terminal
    kubectl get pods -n observability -l app=loki

    Ce que vous devez voir :

    NAME READY STATUS RESTARTS AGE
    loki-0 1/1 Running 0 2m

    Le pod loki-0 est en Running.

  3. Vérifiez que Promtail tourne

    Fenêtre de terminal
    kubectl get pods -n observability -l app.kubernetes.io/name=promtail

    Ce que vous devez voir :

    NAME READY STATUS RESTARTS AGE
    loki-promtail-xxxxx 1/1 Running 0 2m

    Promtail est un DaemonSet : il y a un pod par node de votre cluster. Si vous avez 3 nodes, vous verrez 3 pods.

  4. Vérifiez que Promtail collecte des logs

    Fenêtre de terminal
    kubectl logs -n observability -l app.kubernetes.io/name=promtail --tail=10

    Ce que vous devez voir : Des lignes mentionnant les fichiers de logs découverts et les streams créés. Par exemple :

    level=info msg="Adding target" ...
    level=info msg="start" ...

    Si vous voyez des erreurs de connexion à Loki, attendez quelques secondes et réessayez.

Grafana doit savoir où trouver Loki pour pouvoir l’interroger.

  1. Ouvrez Grafana

    Grâce au NodePort configuré dans le module 03, Grafana est accessible directement :

    Fenêtre de terminal
    minikube service grafana -n observability --url

    Ou accédez directement à http://<MINIKUBE_IP>:30030

    Connectez-vous avec admin / admin.

  2. Allez dans Connections → Data sources

  3. Cliquez sur “Add data source”

  4. Cherchez et sélectionnez “Loki”

  5. Configurez la connexion

    ChampValeur
    NameLoki
    URLhttp://loki:3100

    Laissez les autres champs par défaut.

  6. Cliquez sur “Save & test”

    Ce que vous devez voir : Un message vert “Data source successfully connected”.

  1. Dans Grafana, cliquez sur l’icône boussole (Explore) dans le menu latéral

  2. En haut, sélectionnez “Loki” dans le dropdown des datasources

  3. Vous voyez : Un éditeur de requête et un sélecteur de plage temporelle

Tapez cette requête dans l’éditeur :

{namespace="otel-demo"}

Cliquez sur Run query (ou Shift+Enter).

Ce que vous devez voir :

  • Une liste de logs avec des timestamps
  • Chaque log affiche ses labels (namespace, pod, container)
  • Les logs les plus récents en haut

Les labels disponibles dépendent de ce que Promtail a collecté. Les labels Kubernetes courants sont :

LabelDescriptionExemple
namespaceLe namespace Kubernetesotel-demo
podLe nom du podotel-demo-frontend-xyz
containerLe nom du conteneurfrontend
appLe label app du podfrontend
jobLe job de scraping Promtailkubernetes-pods

Exemples de sélecteurs :

# Logs d'un namespace spécifique
{namespace="otel-demo"}
# Logs d'un pod spécifique
{namespace="otel-demo", pod="otel-demo-frontend-abc123"}
# Logs de tous les pods dont le nom commence par "frontend"
{namespace="otel-demo", pod=~".*frontend.*"}
# Logs de tous les namespaces sauf kube-system
{namespace!="kube-system"}

Les opérateurs de sélection :

OpérateurSignificationExemple
=Égal exactement{namespace="otel-demo"}
!=Différent de{namespace!="kube-system"}
=~Correspond au regex{pod=~".*frontend.*"}
!~Ne correspond pas au regex{pod!~".*test.*"}

Après avoir sélectionné les streams, vous pouvez filtrer les lignes par leur contenu :

{namespace="otel-demo"} |= "error"

Cette requête retourne les logs du namespace otel-demo qui contiennent le mot “error”.

Les opérateurs de filtre de ligne :

OpérateurSignificationExemple
|=Contient{app="frontend"} |= "error"
!=Ne contient pas{app="frontend"} != "health"
|~Correspond au regex{app="frontend"} |~ "status=(4|5)[0-9]{2}"
!~Ne correspond pas au regex{app="frontend"} !~ "DEBUG"

Exemples pratiques :

# Logs contenant "error" (case-insensitive avec regex)
{namespace="otel-demo"} |~ "(?i)error"
# Logs contenant "error" mais pas "healthcheck"
{namespace="otel-demo"} |= "error" != "healthcheck"
# Logs avec un code HTTP 4xx ou 5xx
{namespace="otel-demo"} |~ "HTTP/(4|5)[0-9]{2}"
# Logs contenant une trace_id (pour la corrélation)
{namespace="otel-demo"} |~ "trace_id"

La plupart des applications modernes génèrent des logs en JSON. LogQL peut les parser.

Exemple de log JSON :

{"level":"error","msg":"Failed to process order","order_id":"12345","trace_id":"abc123"}

Parser avec | json :

{namespace="otel-demo"} | json

Après | json, les champs JSON deviennent des labels que vous pouvez utiliser :

# Filtrer par niveau d'erreur
{namespace="otel-demo"} | json | level="error"
# Filtrer par code HTTP
{namespace="otel-demo"} | json | status_code >= 400
# Combiner plusieurs filtres
{namespace="otel-demo"} | json | level="error" | service_name="checkout"

Vous pouvez modifier l’affichage des logs :

# Afficher seulement certains champs
{namespace="otel-demo"} | json | line_format "{{.level}} - {{.msg}}"
# Format plus lisible
{namespace="otel-demo"} | json | line_format "[{{.level}}] {{.service_name}}: {{.msg}}"

Exercice 1 : Explorer les logs de l’application démo

Section intitulée « Exercice 1 : Explorer les logs de l’application démo »
  1. Listez tous les logs du namespace otel-demo

    {namespace="otel-demo"}

    Observation : Beaucoup de logs ! Réduisez la plage temporelle à 15 minutes.

  2. Filtrez sur les erreurs

    {namespace="otel-demo"} |= "error"

    Combien de logs d’erreur voyez-vous ?

  3. Parsez le JSON et filtrez par niveau

    {namespace="otel-demo"} | json | level="error"

    Question : Y a-t-il une différence avec la requête précédente ?

    (Oui, la précédente cherche le texte “error” n’importe où ; celle-ci cherche le champ JSON level égal à “error”)

  4. Identifiez les pods les plus bavards

    {namespace="otel-demo"} | json

    Regardez le panneau latéral (si disponible) pour voir la distribution par pod.

  5. Cherchez les logs avec un trace_id

    {namespace="otel-demo"} | json | trace_id != ""

    Ces logs pourront être corrélés avec les traces Tempo (module 07).

LogQL peut compter et mesurer les logs, transformant des logs en métriques.

Logs par seconde :

rate({namespace="otel-demo"}[5m])

Cette requête retourne le taux de logs par seconde sur les 5 dernières minutes, groupé par stream.

Logs par seconde, agrégés par pod :

sum by (pod) (rate({namespace="otel-demo"}[5m]))

Logs par seconde, agrégés par niveau :

sum by (level) (rate({namespace="otel-demo"} | json [5m]))
# Nombre total de logs d'erreur sur la dernière heure
count_over_time({namespace="otel-demo"} |= "error" [1h])
# Par pod
sum by (pod) (count_over_time({namespace="otel-demo"} |= "error" [1h]))
# Ratio erreurs / total logs par service
(
sum by (service_name) (
rate({namespace="otel-demo"} | json | level="error" [5m])
)
)
/
(
sum by (service_name) (
rate({namespace="otel-demo"} | json [5m])
)
)
  1. Comptez les logs par seconde de chaque pod

    sum by (pod) (rate({namespace="otel-demo"}[5m]))

    Ce que vous devez voir : Un graphique avec une ligne par pod.

  2. Identifiez le top 5 des pods les plus bavards

    topk(5, sum by (pod) (rate({namespace="otel-demo"}[5m])))
  3. Comptez les erreurs par niveau de log

    sum by (level) (rate({namespace="otel-demo"} | json [5m]))
  4. Alertez si plus de 10 erreurs par minute

    D’abord, vérifiez le taux d’erreurs :

    sum(rate({namespace="otel-demo"} | json | level="error" [1m])) * 60

    Cette valeur (erreurs par minute) peut être utilisée pour une alerte Grafana.

Promtail fait plus que collecter les logs. Il peut les transformer.

Les “stages” sont des étapes de traitement appliquées à chaque log :

pipeline_stages:
# 1. Parser le format CRI (Container Runtime Interface)
- cri: {}
# 2. Parser le JSON
- json:
expressions:
level: level
message: msg
trace_id: trace_id
# 3. Ajouter des labels
- labels:
level:
trace_id:
# 4. Filtrer (drop) les logs inutiles
- match:
selector: '{app="frontend"}'
stages:
- drop:
expression: "healthcheck"
# 5. Modifier le contenu
- replace:
expression: "password=.*"
replace: "password=***"

Explication de chaque stage :

StageRôleExemple
criParse le format des logs Kubernetes (CRI)Extrait le timestamp
jsonParse le contenu JSONExtrait level, msg, etc.
labelsAjoute des labels à partir des champs extraitslevel devient un label filtrable
dropSupprime les logs correspondantsIgnore les healthchecks
replaceModifie le contenuMasque les mots de passe

Les stack traces Java ou Python s’étendent sur plusieurs lignes :

2024-01-15 10:30:45 ERROR Something went wrong
java.lang.NullPointerException
at com.example.MyClass.method(MyClass.java:42)
at com.example.Main.main(Main.java:10)

Configuration pour les regrouper :

pipeline_stages:
- multiline:
firstline: '^\d{4}-\d{2}-\d{2}' # Nouvelle entrée = commence par une date
max_wait_time: 3s
  1. Créez un nouveau dashboard ou ouvrez un existant

  2. Add panel → Visualization: Logs

  3. Configurez la requête

    • Data source : Loki
    • Query : {namespace="otel-demo"} | json
  4. Dans Panel options

    OptionValeur recommandée
    TimeShow
    Unique labelsShow
    Common labelsHide
    Wrap linesOn
  5. Sauvegardez le panel

  1. Add panel → Visualization: Time series

  2. Configurez la requête

    sum by (level) (rate({namespace="otel-demo"} | json [5m]))
  3. Dans Panel options

    • Title : “Log rate par niveau”
    • Legend : Table, valeurs
  4. Sauvegardez

  1. Add panel → Visualization: Bar gauge

  2. Configurez la requête

    sum by (service_name) (
    count_over_time({namespace="otel-demo"} | json | level="error" [1h])
    )
  3. Dans Panel options

    • Title : “Erreurs par service (dernière heure)”
    • Thresholds : 0=vert, 10=jaune, 50=rouge
  4. Sauvegardez

Grafana peut créer des alertes basées sur des requêtes Loki.

  1. Allez dans Alerting → Alert rules

  2. Cliquez sur “New alert rule”

  3. Configurez la requête

    • Data source : Loki

    • Query :

      sum(count_over_time({namespace="otel-demo"} | json | level="error" [5m]))
  4. Configurez la condition

    • Expression : count > 10
    • For : 5 minutes
  5. Configurez les labels et annotations

    Summary: Plus de 10 erreurs en 5 minutes dans otel-demo
  6. Sauvegardez

Loki compresse bien les logs (ratio ~10:1 typique).

Formule d’estimation :

Espace = (logs_bruts_par_jour_GB / 10) × jours_rétention

Exemple :

  • 50 GB de logs bruts par jour
  • Rétention 7 jours
  • Espace nécessaire ≈ (50 / 10) × 7 = 35 GB

Dans les values Helm :

loki:
config:
limits_config:
retention_period: 168h # 7 jours
reject_old_samples: true
reject_old_samples_max_age: 168h
table_manager:
retention_deletes_enabled: true
retention_period: 168h
ÉtapeCommande/ActionCe que vous cherchez
1. Promtail tourne ?kubectl get pods -n observability -l app.kubernetes.io/name=promtailPods Running
2. Loki tourne ?kubectl get pods -n observability -l app=lokiPod Running
3. Logs Promtailkubectl logs -n observability -l app.kubernetes.io/name=promtail --tail=50Pas d’erreurs de connexion
4. Les pods cibles ont des logs ?kubectl logs -n otel-demo <pod> --tail=5Des logs s’affichent
5. Datasource Loki OK ?Grafana → Connections → Data sources → Loki → Save & test”Successfully connected”

Loki est surchargé.

CauseSolution
Trop de requêtes simultanéesRéduisez les dashboards actifs
Requêtes trop largesAjoutez des filtres de labels
Ressources insuffisantesAugmentez memory/CPU de Loki
CauseSolution
Plage temporelle trop largeRéduisez à 1h ou moins
Pas de filtre de labelsAjoutez {namespace="..."} au minimum
Trop de parsing JSONFiltrez par texte d’abord (`
CauseSolution
Limite de taille de ligneAugmentez max_line_size dans Promtail
Logs multilignes non groupésAjoutez une stage multiline
  1. Loki et Promtail sont déployés

    Fenêtre de terminal
    kubectl get pods -n observability -l "app in (loki)" && \
    kubectl get pods -n observability -l app.kubernetes.io/name=promtail

    Les pods sont en Running.

  2. La datasource Loki fonctionne

    Dans Grafana → Connections → Data sources → Loki : “Successfully connected”

  3. Les logs sont collectés

    Dans Grafana → Explore → Loki :

    {namespace="otel-demo"}

    Des logs s’affichent.

  4. Le parsing JSON fonctionne

    {namespace="otel-demo"} | json | level="error"

    Des logs filtrés s’affichent.

  5. Vous savez générer des métriques

    sum by (pod) (rate({namespace="otel-demo"}[5m]))

    Un graphique s’affiche.

  1. Loki indexe les labels, pas le contenu — ce qui le rend léger et économique
  2. Promtail collecte les logs des pods et les enrichit avec les labels Kubernetes
  3. LogQL ressemble à PromQL : sélecteurs de stream {} + filtres |= |~ + transformations | json
  4. Parser JSON permet de filtrer sur les champs : | json | level="error"
  5. Les fonctions rate() et count_over_time() transforment les logs en métriques
  6. Grafana Alerting peut créer des alertes basées sur des requêtes Loki

Vous avez maintenant les métriques (Prometheus) et les logs (Loki). Il vous manque le troisième pilier de l’observabilité : les traces distribuées. Dans le prochain module, nous ajoutons Tempo pour suivre les requêtes à travers tous vos services.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.