Quand Logstash perd la mémoire — anatomie d'un OOM en production
« 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.
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
« On n'a plus de logs, plus de dashboards de latence. » Aucune alerte automatique. Le premier signal est humain.
Les logs Filebeat montrent connection reset by peer. Filebeat essaie d'envoyer, Logstash coupe la connexion.
Le pod K8s est tombé, emportant toute la pipeline avec lui.
Passage à -Xms4g -Xmx4g. Logstash refuse de démarrer : la K8s limit est dépassée. Ajustement à 3G, le pod redémarre... puis retombe.
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.
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
| Type | Usage dans Logstash | Paramètre JVM | Message OOM |
|---|---|---|---|
| Heap | Objets Java, events, pipelines, plugins JRuby | -Xms / -Xmx | Java heap space |
| Direct Memory | Buffers réseau Netty, MMap, ByteBuffers, persistent queues | -XX:MaxDirectMemorySize | direct 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.
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 ?
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.
© Cyber Expert — cyberexpert.fr