Maîtrise de la politique OOM : Ajuster la réponse de Systemd aux événements de manque de mémoire
Apprenez à contrôler le comportement du tueur OOM (Out-of-Memory) de Linux avec systemd. Ce guide explore les directives `OOMScoreAdjust` et `OOMPolicy` pour protéger les services critiques en influençant quels processus sont terminés lors de conditions de faible mémoire. Maîtrisez le réglage OOM de systemd pour une stabilité et une résilience accrues du système.
Maîtrise de la politique OOM : Ajuster la réponse de Systemd aux événements de manque de mémoire
Les défaillances de mémoire insuffisante surviennent rarement à un moment opportun. Une importation par lots reçoit un fichier plus volumineux que d'habitude, un service fuit de la mémoire pendant la nuit, une sauvegarde chevauche un pic de trafic, ou un déploiement double le nombre de processus de travail. Lorsque Linux ne peut pas libérer suffisamment de mémoire pour une allocation, le noyau peut invoquer le tueur OOM et terminer un processus pour que la machine puisse continuer à fonctionner.
La partie inconfortable est que la victime par défaut peut ne pas être le service que vous auriez choisi. Sur un hôte partagé, vous pourriez préférer qu'un travailleur de file d'attente pouvant être relancé meure avant l'API principale. Sur un serveur de base de données, vous voudrez peut-être que SSH et la surveillance restent actifs pour pouvoir récupérer la machine. Systemd vous offre deux leviers pour ce type de décision : OOMScoreAdjust= et OOMPolicy=.
OOMScoreAdjust= influence quel processus est sélectionné. OOMPolicy= contrôle ce que fait systemd après qu'un processus du service a été tué. Ils résolvent des problèmes différents, et les mélanger conduit à de mauvais runbooks.
Ce que le noyau évalue
Chaque processus Linux a un score OOM, visible dans /proc/<pid>/oom_score. Un score plus élevé signifie que le processus est une victime OOM plus probable. Le noyau dérive ce score de l'utilisation de la mémoire et d'autres contextes, puis applique la valeur d'ajustement de /proc/<pid>/oom_score_adj.
OOMScoreAdjust= de systemd écrit cet ajustement pour les processus qu'il démarre. La plage est de -1000 à 1000.
-1000offre la protection la plus forte et désactive effectivement le kill OOM pour ce processus.- Les valeurs négatives rendent le processus moins susceptible d'être tué.
- Les valeurs positives rendent le processus plus susceptible d'être tué.
0laisse l'ajustement neutre.
L'approche la plus sûre n'est généralement pas de "protéger tout ce qui est important". Si chaque service est protégé, le noyau a moins de choix utiles lorsque l'hôte manque déjà de mémoire. Protégez un petit nombre de services et facilitez la mise à mort du travail jetable.
Pour un service API principal, un ajustement modéré est souvent suffisant :
[Service]
OOMScoreAdjust=-300
Pour un travailleur de file d'attente qui peut réessayer des tâches :
[Service]
OOMScoreAdjust=500
Ce travailleur peut mourir en premier lors d'une pression mémoire, mais c'est le but. Une tâche échouée peut retourner dans la file d'attente. Une base de données morte ou un hôte inaccessible est un incident plus important.
Ce que fait réellement OOMPolicy
OOMPolicy= ne marque pas une unité comme "critique", et ne choisit pas le premier processus à tuer. Les valeurs prises en charge sont continue, stop et kill.
continue: systemd enregistre l'événement OOM et laisse l'unité en cours d'exécution si des processus subsistent.stop: systemd enregistre l'événement et arrête proprement l'unité.kill: si un processus de l'unité est tué par OOM, les processus restants de cette unité sont tués en groupe.
Utilisez ce paramètre pour éviter les services à moitié vivants. Si un service web multi-processus perd un travailleur et continue d'accepter du trafic dans un état dégradé, continue peut masquer l'échec. OOMPolicy=kill rend l'échec évident et permet à Restart=on-failure de ramener le service dans un état propre.
[Service]
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
Pour un travail par lots avec des processus auxiliaires, stop peut être moins brutal pour les processus restants :
[Service]
OOMPolicy=stop
Le processus choisi par le noyau est déjà parti. stop n'affecte que ce que systemd fait au reste du service, donc ne comptez pas dessus comme un point de sauvegarde gracieux. Les tâches de longue durée doivent sauvegarder leur propre travail.
Un modèle de réglage pratique
Commencez par trier les services en trois groupes.
Premièrement, identifiez les services qui maintiennent l'hôte récupérable : SSH, réseau, surveillance et la charge de travail principale. Donnez uniquement aux plus importants des ajustements négatifs modestes.
Deuxièmement, identifiez les services qui peuvent être relancés : travailleurs, importateurs, générateurs de rapports, processeurs d'images, réchauffeurs de cache, assistants de développement. Donnez-leur des ajustements positifs.
Troisièmement, décidez si chaque service peut continuer à fonctionner en toute sécurité après la mort d'un processus. Si ce n'est pas le cas, utilisez OOMPolicy=kill et une politique de redémarrage.
Une configuration réaliste de travailleur pourrait ressembler à ceci :
# /etc/systemd/system/image-worker.service.d/oom.conf
[Service]
OOMScoreAdjust=500
OOMPolicy=kill
Restart=on-failure
RestartSec=10s
Un service d'application principal pourrait ressembler à ceci :
# /etc/systemd/system/api.service.d/oom.conf
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
RestartSec=5s
J'éviterais OOMScoreAdjust=-1000 sauf si vous avez testé le mode de défaillance. Si ce service protégé est celui qui fuit de la mémoire, la machine a toujours besoin d'un moyen de récupérer.
Appliquer et vérifier le changement
Utilisez des drop-ins au lieu de modifier les fichiers d'unité emballés :
sudo systemctl edit api.service
Après avoir enregistré la configuration, rechargez systemd et redémarrez le service :
sudo systemctl daemon-reload
sudo systemctl restart api.service
Vérifiez l'unité fusionnée et les valeurs vues par systemd :
systemctl cat api.service
systemctl show api.service -p OOMPolicy -p OOMScoreAdjust
Inspectez ensuite le processus en cours :
PID=$(systemctl show api.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
cat /proc/$PID/oom_score
oom_score_adj doit correspondre à votre ajustement configuré. oom_score peut changer à mesure que le processus utilise plus ou moins de mémoire.
Après un incident, vérifiez à la fois les journaux de l'unité et le journal du noyau :
journalctl -u api.service --since "1 hour ago"
journalctl -k --since "1 hour ago" | grep -i oom
Sur les systèmes qui utilisent systemd-oomd, vérifiez également :
systemctl status systemd-oomd
oomctl
La politique OOM n'est pas une planification de capacité
Le réglage OOM est une dernière ligne de défense. Vous avez toujours besoin de limites de mémoire, d'alertes et d'une marge suffisante pour les pics normaux. Pour les services avec des limites prévisibles, envisagez les contrôles de mémoire cgroup :
[Service]
MemoryHigh=1500M
MemoryMax=2G
MemoryHigh= applique une pression avant la limite stricte. MemoryMax= est un plafond. Le comportement exact dépend de la version de systemd et de la configuration cgroup, mais l'idée opérationnelle est simple : contenir un service avant qu'il ne consomme l'hôte.
Le swap mérite le même type de réflexion. Pas de swap peut transformer des pics courts en kills OOM brusques. Trop de swap lent peut maintenir l'hôte en vie tandis que la latence devient inutile. Révisez la politique OOM avec le swap, les limites de mémoire, le comportement de redémarrage et les alertes.
Exemple : Un hôte, trois services
Supposons qu'un petit hôte de production exécute une API, un cache Redis et un travailleur de rapports en arrière-plan. Le travailleur de rapports est utile, mais il peut réessayer le travail. Redis améliore la latence, mais l'application peut toujours servir certaines requêtes en allant à la base de données. L'API est le service orienté client.
Une première passe raisonnable pourrait être :
# api.service
[Service]
OOMScoreAdjust=-300
OOMPolicy=kill
Restart=on-failure
# redis.service drop-in, si cette instance Redis est uniquement un cache
[Service]
OOMScoreAdjust=0
OOMPolicy=kill
# report-worker.service
[Service]
OOMScoreAdjust=600
OOMPolicy=kill
Restart=on-failure
Cela ne garantit pas que le travailleur meure en premier dans tous les cas possibles, mais cela rend votre intention claire. Si le travailleur de rapports devient trop volumineux, il est une cible plus facile. Si l'API perd un de ses processus, systemd tue les autres et le redémarre proprement. Si Redis n'est qu'un cache, vous pouvez choisir de ne pas le protéger lourdement ; si Redis est votre magasin de données principal, vous prendriez une décision différente.
C'est pourquoi la politique OOM doit être liée au rôle du service, pas au nom du produit. "Redis" n'est pas automatiquement critique ou jetable. "Le cache que nous pouvons reconstruire" et "la seule copie de l'état de session" sont des objets opérationnels différents.
Tester sans créer de désastre
Vous n'avez pas besoin de planter un serveur de production pour savoir si les paramètres sont appliqués. Commencez par l'inspection :
systemctl show report-worker.service -p OOMScoreAdjust -p OOMPolicy
systemctl status report-worker.service
Vérifiez ensuite le processus en cours :
PID=$(systemctl show report-worker.service -p MainPID --value)
cat /proc/$PID/oom_score_adj
Pour des tests plus approfondis, utilisez un hôte de staging ou une machine virtuelle jetable avec la même version de systemd et le même mode cgroup. Exécutez un outil de pression mémoire contrôlé là-bas, pas sur une boîte de production partagée. L'objectif est de confirmer le comportement général : le travailleur est plus facile à tuer, le service principal ne reste pas à moitié vivant, et le comportement de redémarrage est visible dans le journal.
Si vous utilisez des conteneurs, testez dans la même forme que vous déployez. Un service fonctionnant directement sous systemd ne se comporte pas exactement comme un processus à l'intérieur d'un conteneur avec sa propre limite de mémoire. Le noyau peut appliquer la limite du conteneur avant que l'hôte ne soit globalement à court de mémoire. Dans ce cas, votre runtime de conteneur, Kubernetes ou les paramètres cgroup peuvent être la première couche qui décide ce qui meurt.
Lire l'incident après coup
Après un événement OOM, évitez de sauter directement à "nous avons besoin de plus de RAM". Parfois oui. Parfois un cache a oublié des TTL. Parfois un déploiement a changé la concurrence des travailleurs. Parfois l'activité de persistance ou de sauvegarde a fait grimper la mémoire copy-on-write.
Recherchez trois choses :
journalctl -k --since "2026-05-24 01:00" | grep -i oom
journalctl -u api.service --since "2026-05-24 01:00"
systemctl show api.service -p Result -p NRestarts
Le journal du noyau vous dit généralement quel processus a été tué. Le journal de l'unité vous dit comment systemd a réagi. Les compteurs de redémarrage vous disent si le service s'est rétabli proprement ou a oscillé.
Comparez ensuite le processus tué avec votre priorité prévue. Si un service protégé est mort avant un travailleur jetable, vérifiez si le travailleur fonctionnait réellement sous l'unité que vous avez réglée, si la configuration a été chargée, et si une autre limite de mémoire s'est déclenchée en premier. Si la victime choisie correspond à la politique mais que l'incident a quand même nui aux utilisateurs, votre classification des services devra peut-être changer.
Documentez la raison, pas seulement la valeur
Les paramètres OOM sont faciles à oublier car ils restent silencieux dans les drop-ins d'unité jusqu'à un mauvais jour. Laissez un court commentaire dans la configuration ou dans votre dépôt d'infrastructure expliquant la raison de l'ajustement.
[Service]
# Travailleur de file d'attente pouvant être relancé. Préférer tuer celui-ci avant api.service lors d'une pression sur l'hôte.
OOMScoreAdjust=600
OOMPolicy=kill
Ce commentaire fait gagner du temps lors d'une révision d'incident. Sans lui, quelqu'un pourrait voir un score OOM positif et le "corriger" à zéro sans réaliser qu'il s'agissait d'une décision de priorité intentionnelle.
Enregistrez également quand vous avez examiné le paramètre pour la dernière fois. Un service peut changer de rôle au fil du temps. Un travailleur qui traitait autrefois des vignettes jetables pourrait plus tard traiter des paiements, des exportations ou des tâches visibles par les clients. La politique OOM doit suivre le risque actuel, pas le but original du service.
Configurations incorrectes courantes
Une mauvaise configuration est de protéger la base de données, l'API, le travailleur, le cache, l'expéditeur de journaux et l'agent de surveillance tous à la fois. Cela semble prudent, mais cela donne moins d'options au noyau. Choisissez des priorités.
Une autre mauvaise configuration est de définir OOMPolicy=continue sur un service qui ne peut pas tolérer des processus enfants manquants. Un gestionnaire de processus, un serveur web ou un démon personnalisé peut maintenir l'unité active même après la disparition d'une partie de la charge de travail. Si votre équilibreur de charge vérifie seulement si le port est ouvert, le trafic peut continuer à affluer vers un service dégradé.
Une troisième mauvaise configuration est un ajustement positif sans comportement de relance. Si vous facilitez la mise à mort d'un service, assurez-vous que le tuer est acceptable. Pour un travailleur de file d'attente, cela signifie que les tâches sont acquittées seulement après un traitement réussi. Pour un travail par lots, cela signifie des points de contrôle. Pour un réchauffeur de cache, cela signifie que le cache peut être reconstruit plus tard.
Enfin, évitez de masquer les événements OOM avec des redémarrages automatiques seuls. Redémarrer un service qui fuit peut faire gagner du temps, mais cela peut aussi créer une boucle où la mémoire monte, le service meurt, et les utilisateurs voient des échecs périodiques. Ajoutez des alertes sur le nombre de redémarrages et la croissance de la mémoire, pas seulement sur l'état du processus.
Un runbook court
Lorsque vous réglez un vrai serveur, utilisez une liste de contrôle reproductible :
- Listez les services nécessaires à la récupération et au trafic utilisateur.
- Listez les services pouvant être relancés qui peuvent être tués en premier.
- Ajoutez des valeurs
OOMScoreAdjustpositives au travail jetable. - Ajoutez des valeurs négatives modérées uniquement aux quelques services qui méritent une protection.
- Utilisez
OOMPolicy=killpour les services qui ne doivent pas fonctionner partiellement. - Vérifiez les valeurs appliquées via
systemctl showet/proc. - Alertez sur la pression mémoire avant que les événements OOM ne se produisent.
L'objectif n'est pas de rendre les événements OOM inoffensifs. L'objectif est de les rendre compréhensibles. OOMScoreAdjust= aide à choisir la victime. OOMPolicy= aide à définir ce qui arrive au reste de l'unité. Ensemble, ils vous donnent un ordre de défaillance plus prévisible lorsque la mémoire est déjà épuisée.