Logstash OutOfMemoryError : Heap vs Direct Memory sur Kubernetes — Post-Mortem complet

Quand Logstash perd la mémoire — Cyber Expert
Incident Post-Mortem · ELK Stack · Kubernetes

Quand Logstash perd la mémoire — anatomie d'un OOM en production

Cyber Expert Lecture : 8 min ELK · JVM · K8s

« On n'a plus de logs. Plus de dashboards. » Une phrase banale en apparence, mais qui cache une cascade de défaillances silencieuses. Voici le post-mortem complet d'un incident qui m'a forcé à plonger dans les entrailles de la JVM — et à ne plus jamais confondre heap et direct memory.

1. La pipeline ELK sous les projecteurs

Avant de comprendre pourquoi ça casse, encore faut-il comprendre comment ça tourne. Dans la majorité des architectures de logging centralisé, les données transitent via trois composants en série.

PIPELINE ELK — ARCHITECTURE Applications / Pods K8s Filebeat Agent de collecte de logs Beats :5044 Logstash Parse · Filtre · Enrichit HTTP/ES Elasticsearch Indexe & stocke Kibana Dashboards & alertes Flux de données Données en transit (temps réel) Source applicative (logs fichiers) © Cyber Expert — cyberexpert.fr

Architecture standard : Filebeat tail les fichiers de logs et les ship vers Logstash via le protocole Beats (port 5044)

Chaque maillon a un rôle précis. Filebeat est un agent léger qui lit les fichiers de logs et les envoie en temps réel. Il gère lui-même la position de lecture (offset) pour ne rien perdre. Logstash est le cerveau de transformation — le plus gourmand en ressources, et le premier à tomber sous pression.


2. Chronologie de l'incident

1
Premier signal : le manager

« On n'a plus de logs, plus de dashboards de latence. » Aucune alerte automatique. Le premier signal est humain.

2
Filebeat ne publie plus rien

Les logs Filebeat montrent connection reset by peer. Filebeat essaie d'envoyer, Logstash coupe la connexion.

3
OOM Logstash détecté

Le pod K8s est tombé, emportant toute la pipeline avec lui.

4
Fausse piste : augmenter la heap

Passage à -Xms4g -Xmx4g. Logstash refuse de démarrer : la K8s limit est dépassée. Ajustement à 3G, le pod redémarre... puis retombe.

5
Le vrai coupable : la Direct Memory

L'OOM vient des buffers off-heap utilisés par Netty. Fix : ajouter -XX:MaxDirectMemorySize=2g explicitement.

{"log.level":"error","message":"failed to publish events: write tcp ... connection reset by peer","service.name":"filebeat"}

java.lang.OutOfMemoryError: Cannot reserve 16777216 bytes of direct buffer memory
(allocated: 1057035404, limit: 1073741824)

3. Anatomie de la JVM — heap vs direct memory

C'est ici que se joue la vraie compréhension de l'incident. La JVM gère deux espaces mémoire fondamentalement différents, avec des paramètres indépendants.

JVM MEMORY — HEAP VS DIRECT MEMORY PROCESSUS JVM — LOGSTASH HEAP MEMORY Gérée par le Garbage Collector Young Gen Nouveaux objets Events Logstash Old Gen Objets persistants Pipelines actives Metaspace Classes Java · JRuby · Plugins -Xms2g / -Xmx2g OOM → "Java heap space" DIRECT MEMORY Off-heap · Hors contrôle du GC Netty Buffers Beats Input · HTTP Connexions réseau Zero-copy I/O MMap · ByteBuffers Transferts natifs File Channels Persistent Queues · Dead Letter Queue -XX:MaxDirectMemorySize=2g OOM → "direct buffer memory" VS © Cyber Expert — cyberexpert.fr

Les deux espaces mémoire coexistent dans le même processus JVM mais obéissent à des règles complètement différentes

Point clé : augmenter -Xmx n'a strictement aucun effet sur la direct memory. Les deux paramètres sont indépendants — c'est pourquoi notre premier fix n'a servi à rien.

Tableau comparatif

TypeUsage dans LogstashParamètre JVMMessage OOM
HeapObjets Java, events, pipelines, plugins JRuby-Xms / -XmxJava heap space
Direct MemoryBuffers réseau Netty, MMap, ByteBuffers, persistent queues-XX:MaxDirectMemorySizedirect buffer memory

4. Le piège Kubernetes — budgétiser la mémoire totale

Dans Kubernetes, la memory limit d'un pod s'applique à la mémoire totale du processus. Cela englobe heap + direct memory + metaspace + threads stacks + overhead JVM.

BUDGET MÉMOIRE K8S — POD LOGSTASH Kubernetes — Container Memory Limit 6 Gi — englobe TOUTE la mémoire du processus JVM Heap 2 Go -Xms2g / -Xmx2g Events · Pipelines · JRuby Géré par le GC + Direct Memory 2 Go -XX:MaxDirectMemorySize=2g Netty · MMap · ByteBuffers Off-heap · Hors GC + Overhead JVM ~1.5 Go Metaspace · Thread stacks JIT · GC overhead Internals JVM ✓ 2G + 2G + 1.5G = 5.5G < 6G limit Configuration correcte — marge suffisante ✗ 4G heap + 2G direct = 6G = limit OOM killer au moindre pic de charge © Cyber Expert — cyberexpert.fr
Règle empirique : laissez toujours 20-30% de marge entre (heap + direct memory) et la limit K8s. La JVM a besoin d'espace pour les threads, le metaspace et son overhead interne.

5. Le fix final — configuration correcte

La solution n'est pas de maximiser la heap. C'est d'allouer explicitement les deux espaces mémoire de façon cohérente avec les limites K8s.

# LS_JAVA_OPTS — variable d'environnement Logstash
- name: LS_JAVA_OPTS
  value: >-
    -Xms2g
    -Xmx2g
    -XX:MaxDirectMemorySize=2g

# Ressources K8s du pod Logstash
resources:
  limits:
    memory: "6Gi"    # heap 2G + direct 2G + ~1.5G overhead JVM
  requests:
    memory: "4Gi"
Résultat : heap 2 Go + direct memory 2 Go + ~1.5 Go overhead = 5.5 Go total < 6 Go limit K8s. Plus d'OOM, pipeline stable, logs qui reviennent.

6. Alerting proactif — surveiller les gardiens

L'incident a mis en lumière une absence totale de monitoring sur la chaîne de logging. On surveille les applications — mais qui surveille les gardiens ?

COUVERTURE ALERTING POST-INCIDENT Filebeat Agent de collecte Logstash Pipeline de traitement Elasticsearch Index & stockage Kibana Dashboards Erreurs publish connection reset write timeout OOM JVM Heap space Direct memory Pod restart K8s CrashLoop Restart > 3x/h Anomalie volume docs/min < seuil attendu 0 doc depuis N minutes Notifications centralisées Slack · PagerDuty · Dashboard Kibana dédié © Cyber Expert — cyberexpert.fr

Takeaways

1. Lire le message d'erreur OOM en entier. « Java heap space » et « direct buffer memory » ne se résolvent pas de la même façon. Le diagnostic précède le fix.
2. Dimensionner la mémoire K8s globalement. La limit pod = heap + direct memory + overhead JVM. Additionner les deux avant de choisir la limit container, et laisser une marge de 20-30%.
3. Netty est vorace en direct memory. Dès que Logstash reçoit un volume élevé d'événements via Beats ou HTTP input, les buffers Netty grossissent. Surveillez avec jcmd <pid> VM.native_memory.
4. Votre pipeline de logging doit être monitorée. Si vos logs disparaissent sans alerte, vous êtes aveugle exactement quand vous en avez le plus besoin.

Des questions sur votre configuration ELK ? Une situation similaire vécue en production ? Les commentaires sont ouverts.