Retour au cours

infra / linux-bash

Gestion mémoire avancée : overcommit et OOM killer

Leçon 301 exercice

Explication

Ce que vous allez apprendre

  • Comprendre le mécanisme d'overcommit mémoire du noyau Linux
  • Comprendre comment et pourquoi l'OOM killer choisit un processus à sacrifier
  • Protéger un processus critique (comme sshd) avec oom_score_adj
  • Choisir le bon mode d'overcommit selon le contexte (0, 1 ou 2)
  • Limiter la mémoire par groupe de processus avec les cgroups v2 (memory.max, memory.high)

Dans quel contexte ?

Un service critique a été arrêté cette nuit sans explication apparente dans ses propres logs. dmesg -T | grep -i "killed process" révèle que c'est le noyau lui-même qui l'a tué par manque de mémoire — pas un crash applicatif classique.

D'abord, un comportement du noyau qui surprend

Le noyau Linux autorise, par défaut, les programmes à réserver plus de mémoire virtuelle qu'il n'existe réellement de RAM physique sur la machine. Ça paraît risqué, mais c'est un pari statistique : la plupart des programmes réservent plus qu'ils n'utilisent réellement — ce mécanisme s'appelle l'overcommit.

Une fois ce mécanisme compris, il reste une question : que se passe-t-il si le pari échoue

Si tous les programmes utilisaient réellement toute la mémoire qu'ils ont réservée en même temps, il n'y en aurait pas assez. C'est là qu'intervient l'OOM (Out Of Memory) killer : quand la mémoire réellement disponible est épuisée, le noyau choisit un processus à tuer pour en libérer, en se basant sur un score (oom_score).

Voici pourquoi cette décision automatique peut être dangereuse

Le noyau pourrait très bien choisir de tuer sshd, le service qui te permet justement de te connecter pour réparer le problème, plutôt qu'un processus secondaire. cat /proc/<PID>/oom_score montre le score actuel d'un processus, et echo -1000 | sudo tee /proc/<PID>/oom_score_adj le protège explicitement.

Piège classique

Sans protection explicite, le noyau peut choisir de tuer sshd — le service qui te permettrait justement de te reconnecter pour réparer le problème. Protège les processus critiques avec oom_score_adj réglé à -1000.

Maintenant, un réglage complémentaire : à quel moment l'overcommit refuse-t-il

sysctl vm.overcommit_memory accepte trois valeurs : 0 (heuristique, le défaut), 1 (toujours accepter, risque maximal), 2 (strict, refuse dès qu'une limite calculée via vm.overcommit_ratio est dépassée). Le mode 2 évite les déclenchements OOM surprises en production, au prix d'un refus plus fréquent des allocations mémoire.

ValeurComportementRisque
0 (défaut)Heuristique, refuse les allocations "déraisonnables"Modéré
1Toujours accepter l'allocationOOM plus fréquent, plus tard
2 (strict)Refuse dès qu'une limite calculée est dépasséeRefus d'allocation plus fréquent, mais prévisible

Il reste un dernier outil, plus fin, hérité de Docker

Les cgroups v2 limitent la mémoire par groupe de processus avec memory.max (limite dure, déclenche un OOM local au cgroup) et memory.high (limite souple, ralentit sans tuer). C'est exactement le mécanisme que Docker utilise en interne pour isoler la consommation mémoire d'un conteneur.

Le piège à connaître

Ignorer un OOM passé en pensant que le service a "juste planté" fait rater la vraie cause : dmesg -T | grep -i "killed process" révèle immédiatement si le noyau a lui-même tué le processus par manque de mémoire. Maintenant que tu comprends la mémoire au niveau noyau, la prochaine leçon élargit ce réglage fin à l'ensemble des paramètres système avec sysctl.

Commandes & code

Gestion mémoire avancée : overcommit et OOM killer

bash
# --- Overcommit : le noyau peut allouer PLUS de mémoire virtuelle que de RAM physique existante ---
cat /proc/sys/vm/overcommit_memory
# 0 = heuristique (défaut) : refuse les allocations "manifestement" déraisonnables
# 1 = toujours accepter : jamais de refus au malloc(), risque max de déclenchement OOM plus tard
# 2 = strict : refuse dès que overcommit_ratio est dépassé, aucune surprise en cours d'exécution

sudo sysctl -w vm.overcommit_memory=2
sudo sysctl -w vm.overcommit_ratio=80    # avec le mode 2 : limite = swap + RAM * 80%

cat /proc/meminfo | grep -i commit
# CommitLimit    -> mémoire max allouable selon la politique d'overcommit actuelle
# Committed_AS   -> mémoire virtuelle actuellement promise à tous les process
bash
# --- OOM killer : le noyau tue un process quand la mémoire est réellement épuisée ---
# Chaque process a un "oom_score" : plus il est élevé, plus il est une cible probable

cat /proc/<PID>/oom_score               # score calculé (RAM utilisée, priorité, ancienneté...)
cat /proc/<PID>/oom_score_adj             # ajustement manuel : -1000 à +1000

# Protéger un process critique (ex: sshd) de l'OOM killer
echo -1000 | sudo tee /proc/$(pgrep -x sshd | head -1)/oom_score_adj

# Rendre un process WORKER délibérément la première cible sacrificielle
echo 900 | sudo tee /proc/<PID_worker>/oom_score_adj

# Service systemd : équivalent déclaratif, persiste au redémarrage du service
# Dans [Service] d'un fichier .service :
# OOMScoreAdjust=-500

# --- Observer un OOM après coup ---
dmesg -T | grep -i "killed process"       # -T = timestamps humains
journalctl -k | grep -i "out of memory"
dmesg -T | grep -A5 "invoked oom-killer"    # contexte complet de la décision du noyau

# --- cgroups v2 : limiter et observer la mémoire PAR groupe de process (base de Docker/systemd) ---
cat /sys/fs/cgroup/system.slice/memory.max          # limite dure du slice systemd
cat /sys/fs/cgroup/system.slice/memory.current         # consommation actuelle
cat /sys/fs/cgroup/system.slice/memory.events           # compteur d'événements, dont "oom" et "oom_kill"

# memory.high (soft) vs memory.max (hard) : high ralentit (reclaim agressif), max déclenche l'OOM killer LOCAL au cgroup
echo "400M" | sudo tee /sys/fs/cgroup/mon-app.slice/memory.high
echo "512M" | sudo tee /sys/fs/cgroup/mon-app.slice/memory.max

# --- Provoquer un OOM contrôlé pour valider une configuration (test uniquement) ---
sudo systemd-run --scope -p MemoryMax=100M \
  bash -c 'a=(); while true; do a+=($(head -c 1M /dev/zero | tr "\0" "x")); done'

# --- Tuning du comportement d'écriture disque, souvent lié à des pics mémoire (pages "dirty") ---
sysctl vm.dirty_ratio               # % de RAM en pages "sales" avant écriture forcée bloquante
sysctl vm.dirty_background_ratio      # % à partir duquel le noyau écrit en arrière-plan
sudo sysctl -w vm.dirty_background_ratio=5
sudo sysctl -w vm.dirty_ratio=10

Résumé

  • L'overcommit permet d'allouer plus de mémoire virtuelle que de RAM réelle ; le mode 2 (strict) évite les OOM surprises en prod.
  • oom_score_adj (-1000 à +1000) protège ou sacrifie explicitement un process avant que le kernel ne choisisse lui-même.
  • Les cgroups v2 (memory.max, memory.high, memory.events) isolent l'impact d'un OOM à UN groupe de process, exactement ce qu'utilise Docker en interne.

Exercices pratiques

1 disponible
1

Mission : un service critique tué sans explication

Objectif : Diagnostiquer un OOM kill, protéger un processus critique et choisir le bon mode d'overcommit.

Contexte

Un service critique a été arrêté cette nuit sans aucune trace d'erreur dans ses propres logs applicatifs. Tu dois vérifier si le noyau lui-même en est responsable, protéger ce qui doit l'être, et régler l'overcommit pour éviter que ça se reproduise sans prévenir.

Résoudre l’exercice →