Événements OOM-killer sur Proxmox : pourquoi il tue des processus et comment définir des limites sensées

Cet article vous a aidé ?

L’hôte Proxmox fonctionnait « bien » pendant des mois. Puis un mardi : les VM se mettent en pause, l’interface web répond lentement, ssh prend une éternité, et les logs ressemblent à une scène de crime.
Quelque chose a été touché. Ce n’était pas le disque. Ce n’était pas le réseau. C’était la mémoire.

Quand l’OOM-killer de Linux débarque sur un hôte de virtualisation, il ne fait pas de cinéma. Il réalise la dernière chose responsable qu’il peut faire :
tuer un processus pour que le noyau puisse continuer à respirer. Votre travail est d’arrêter de l’inviter.

Ce qu’est réellement un événement OOM (et ce qu’il n’est pas)

Sur Linux, « out of memory » ne signifie pas « la RAM est à 100% ». Cela signifie que le noyau n’a pas pu satisfaire une requête d’allocation mémoire
sans enfreindre ses propres règles, et qu’il n’a pas pu récupérer suffisamment de mémoire assez rapidement pour rester stable.
À ce stade, Linux a une courte liste d’options mauvaises. La moins mauvaise est de tuer quelque chose.

Sur Proxmox, c’est particulièrement épicé car l’hôte est à la fois hyperviseur et plateforme de charges :
il exécute des processus QEMU (un par VM), des processus de containers LXC, des démons de stockage, des agents de supervision et la pile de gestion Proxmox.
Si l’hôte perd le mauvais processus, l’hyperviseur peut survivre mais vos VM peuvent se mettre en pause ou planter, et votre histoire HA devient une danse interprétative.

Les priorités du noyau en cas de pression mémoire

Linux essaie de récupérer la mémoire dans cet ordre approximatif : libérer le cache de pages, récupérer la mémoire anonyme (swap), compacter la mémoire, et enfin
invoquer l’OOM-killer. Sur un hôte avec swap désactivé et overcommit agressif, vous sautez la moitié de l’échelle et allez directement à « tuer un processus ».
Ce saut est ce que vous observez comme un événement OOM.

OOM-killer vs systemd-oomd vs limites cgroup

Trois mécanismes différents « quelqu’un a tué mon processus » sont souvent confondus :

  • OOM-killer du noyau : global, dernier recours, choisit une victime selon un score de « gravité ».
  • OOM cgroup : un cgroup de container/VM atteint sa limite mémoire ; le noyau tue à l’intérieur de ce cgroup, pas globalement.
  • systemd-oomd : un démon en espace utilisateur qui tue plus tôt selon des signaux de pression (PSI). Il peut être salvateur ou gênant.

Ils laissent des empreintes différentes dans les logs, et la correction dépend de celui qui a déclenché l’action.
Si vous ne savez pas lequel a agi, vous allez « réparer » en ajoutant de la RAM en espérant que ça suffise. Ça marche jusqu’à ce que ça ne marche plus.

Voici la vérité sèche : si vous traitez l’OOM-killer comme un bug, il vous rendra visite à nouveau. Traitez-le comme un détecteur de fumée.
Le détecteur de fumée n’est pas votre problème ; ce sont vos habitudes en cuisine.

Une citation qui tient la route en exploitation (idée paraphrasée) : John Allspaw : la fiabilité vient du fait d’assumer la façon dont les systèmes échouent et de concevoir autour de cette réalité.

Blague #1 : L’OOM-killer, c’est comme une réduction de personnel en période de crise budgétaire — rapide, injuste, et tout le monde affirme que c’était « basé sur des métriques ».

Faits intéressants et un peu d’histoire

Ce ne sont pas des anecdotes pour le plaisir. Chaque fait explique pourquoi votre hôte Proxmox se comporte comme il le fait quand la mémoire devient rare.

  1. Linux a un OOM-killer depuis des décennies, parce que le noyau ne peut pas simplement « retourner NULL » dans beaucoup de chemins critiques sans risquer la corruption.
  2. Les premiers systèmes Linux dépendaient fortement du swap ; les flottes modernes désactivent souvent le swap par politique, ce qui rend les événements OOM plus brutaux et moins tolérants.
  3. Les cgroups ont changé la donne : au lieu que toute la machine tombe, la mémoire peut être contrainte par service ou container — et les kills se produisent localement.
  4. Le score de « gravité » prend en compte l’utilisation mémoire et des ajusteurs (comme oom_score_adj), ce qui explique pourquoi un gros processus QEMU est souvent la victime.
  5. ARC de ZFS n’« avale » pas la mémoire ; c’est un cache conçu pour grandir et rétrécir. Mais mal configuré, il peut contribuer à la pression.
  6. Le ballooning existe parce que l’overcommit existe : c’est un moyen de récupérer la mémoire invité sous pression, mais cela n’aide que si les invités coopèrent et ont des pages récupérables.
  7. PSI (pressure stall information) est une fonctionnalité Linux relativement moderne qui mesure le temps passé bloqué par pression de ressource ; elle a permis des tueurs proactifs comme systemd-oomd.
  8. THP (Transparent Huge Pages) peut augmenter la latence d’allocation et provoquer des problèmes de fragmentation sous pression, ce qui transforme parfois « lent » en « OOM ».
  9. Le cache de fichiers n’est pas de la mémoire « gratuite » ni de la mémoire « utilisée » au même titre ; Linux le récupérera, mais pas toujours assez vite pour des allocateurs en rafale.

Comment la mémoire est consommée sur un hôte Proxmox

Trois réservoirs qui comptent

Pensez à la mémoire de l’hôte comme trois réservoirs qui se disputent l’espace :

  • Mémoire des invités : RSS de QEMU pour les VM, plus l’utilisation mémoire des containers. C’est le poste le plus volumineux.
  • Services de l’hôte : pveproxy, pvedaemon, corosync, ceph (si vous le faites tourner), supervision, sauvegardes, et cet « agent minuscule » qui mange silencieusement 1–2 Go.
  • Cache et noyau : cache de pages, slab, ZFS ARC, métadonnées et allocations noyau. Cela peut être énorme et c’est censé l’être — jusqu’à ce que ça ne le soit plus.

VM : la mémoire QEMU n’est pas juste « la RAM de la VM »

Quand vous attribuez 16 Go à une VM, le processus QEMU mappe typiquement cette mémoire. Avec KVM, une grande partie est soutenue par la RAM de l’hôte.
Ballooning, KSM et swap peuvent modifier la forme, mais la règle opérationnelle simple est :
la RAM attribuée à la VM constitue une réservation sur votre hôte à moins que vous n’ayez un plan d’overcommit prouvé.

Aussi : les sauvegardes, les snapshots et les I/O intensifs peuvent créer des pics transitoires de mémoire dans QEMU et le noyau. Si vous ne prévoyez que l’état stable,
vous aurez des événements OOM pendant les « fenêtres de maintenance ennuyeuses » à 02:00. C’est quand personne ne regarde et que tout s’écrit.

Containers : les limites sont vos amies, jusqu’à ce qu’elles soient des mensonges

LXC sur Proxmox utilise les cgroups. Si vous définissez une limite mémoire, le container ne peut pas la dépasser. Parfait.
Mais si vous la mettez trop basse, le container aura un OOM interne, ce qui ressemble à « mon appli est morte au hasard ».
Si vous la mettez trop haute, vous n’avez en réalité pas limité grand-chose.

ZFS : l’ARC est une fonctionnalité de performance, pas un buffet gratuit

Proxmox aime ZFS, et ZFS adore la RAM. L’ARC grandira pour améliorer les performances de lecture et la mise en cache des métadonnées.
Il se réduira sous pression mémoire, mais pas toujours à la vitesse nécessaire lorsqu’une charge alloue soudainement quelques gigaoctets.
L’ARC peut être réglé. Faites-le délibérément. Ne le faites pas parce qu’un post de forum vous a dit « mettez-le à 50% ».

Swap : pas un péché, un coupe-circuit

Le swap sur un hôte Proxmox n’est pas destiné à tout faire tourner depuis le disque. Il sert à survivre aux pics et à donner le temps au noyau de récupérer.
Une petite quantité de swap (ou zram) peut transformer un OOM dur en incident lent que vous pouvez atténuer. Oui, le swap est moche. Le downtime aussi.

Blague #2 : Désactiver le swap pour « éviter la lenteur », c’est comme enlever les airbags de votre voiture pour gagner du poids — techniquement plus rapide, médicalement discutable.

Feuille de diagnostic rapide

Vous ne gagnez pas un incident OOM en lisant chaque ligne de log. Vous gagnez en prouvant quel mécanisme a tué quoi, et pourquoi la pression mémoire est devenue irrécupérable.
Voici l’ordre qui vous donne des réponses rapidement.

1) Confirmer ce qui a tué le processus

  • OOM-killer global du noyau ?
  • OOM de limite mémoire cgroup à l’intérieur d’un container/service ?
  • systemd-oomd a-t-il tué de manière préemptive ?

2) Identifier la victime et l’agresseur

La victime est ce qui a été tué. L’agresseur est ce qui a causé la pression.
Parfois ce sont les mêmes. Souvent non : une VM alloue, une autre VM est tuée parce qu’elle semblait « plus grosse ».

3) Déterminer s’il s’agit de capacité ou d’un pic

Les problèmes de capacité montrent une mémoire anonyme soutenue élevée et une utilisation du swap (si swap présent). Les problèmes de pics montrent des blocages soudains, un taux d’allocation élevé,
et des reclaim/compaction fréquents. Votre correction diffère : capacité signifie revoir le budget ; pics signifient lisser, limiter ou ajouter un tampon.

4) Vérifier rapidement les coupables au niveau hôte

  • ARC ZFS trop grand ou lent à se réduire ?
  • Augmentation du slab du noyau (ex : réseau, métadonnées ZFS, inode/dentry) due à une fuite ou un motif de charge ?
  • Jobs de sauvegarde provoquant des allocations en rafale ?
  • Ballooning mal configuré ou inefficace ?

5) Décider : limiter, déplacer ou ajouter

Vos trois leviers sont (1) définir des limites pour qu’un seul locataire ne puisse pas brûler l’immeuble, (2) redistribuer les charges de travail, et (3) ajouter de la RAM/swap.
« Ajouter de la RAM » n’est pas faux, c’est juste que ce n’est pas un plan si vous continuez à surcommettre.

Tâches pratiques : commandes, sorties et décisions

Ces tâches sont ordonnées depuis « confirmer l’événement » jusqu’à « poser des garde-fous » puis « prouver que vous avez résolu le problème ».
Chacune inclut un extrait réaliste de sortie et la décision associée.

Task 1: Find OOM-killer events in the kernel log

cr0x@server:~$ journalctl -k -g 'Out of memory\|oom-killer\|Killed process' -n 200
Dec 26 10:41:12 pve kernel: oom-killer: constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0
Dec 26 10:41:12 pve kernel: Out of memory: Killed process 22134 (qemu-system-x86) total-vm:28764564kB, anon-rss:15632240kB, file-rss:12428kB, shmem-rss:0kB, UID:0 pgtables:34912kB oom_score_adj:0
Dec 26 10:41:12 pve kernel: Killed process 22134 (qemu-system-x86) total-vm:28764564kB, anon-rss:15632240kB, file-rss:12428kB, shmem-rss:0kB

Ce que cela signifie : Il s’agit d’un kill OOM global du noyau (constraint=CONSTRAINT_NONE). Un processus QEMU est mort ; une VM a probablement planté.

Décision : Traitez cela comme une exhaustion mémoire de l’hôte, pas comme une limite par container. Passez aux vérifications de capacité hôte et d’overcommit.

Task 2: Determine whether it was a cgroup memory limit OOM

cr0x@server:~$ journalctl -k -g 'Memory cgroup out of memory' -n 50
Dec 26 10:13:07 pve kernel: Memory cgroup out of memory: Killed process 18872 (node) total-vm:1876540kB, anon-rss:824112kB, file-rss:1220kB, shmem-rss:0kB, UID:1000 pgtables:3420kB oom_score_adj:0
Dec 26 10:13:07 pve kernel: Tasks in /lxc.payload.104.slice killed as a result of limit of /lxc.payload.104.slice

Ce que cela signifie : C’est local à un cgroup (ici, un container LXC). L’hôte n’était pas nécessairement en manque de mémoire.

Décision : Augmentez la limite du container, corrigez la mémoire de l’appli ou ajoutez du swap dans le container si approprié. Ne « réparez » pas ça en réglant l’ARC de l’hôte.

Task 3: Check whether systemd-oomd killed something

cr0x@server:~$ journalctl -u systemd-oomd -n 100
Dec 26 10:20:55 pve systemd-oomd[782]: Killed /system.slice/pveproxy.service due to memory pressure (memory pressure 78.21%).
Dec 26 10:20:55 pve systemd-oomd[782]: system.slice: memory pressure relieved.

Ce que cela signifie : Le tueur en espace utilisateur a agi tôt en se basant sur des signaux de pression.

Décision : Soit ajustez les politiques d’oomd, soit exemptiez les services critiques ; enquêtez quand même sur la raison de la forte pression.

Task 4: See current memory layout (including cache and swap)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi        54Gi       1.2Gi       1.1Gi       6.9Gi       2.8Gi
Swap:           8.0Gi       6.4Gi       1.6Gi

Ce que cela signifie : La mémoire disponible est faible ; le swap est fortement utilisé. Vous êtes en pression soutenue.

Décision : Il s’agit probablement d’un problème de capacité ou d’une charge qui s’emballe. Envisagez de migrer des VM, d’augmenter la RAM et de revoir le dimensionnement par invité. Ne vous contentez pas de redémarrer les services.

Task 5: Check pressure stall information (PSI) for memory

cr0x@server:~$ cat /proc/pressure/memory
some avg10=12.34 avg60=9.88 avg300=4.21 total=9349221
full avg10=3.21 avg60=2.10 avg300=0.88 total=1883211

Ce que cela signifie : « some » indique le temps pendant lequel les tâches ont été bloquées sur le reclaim mémoire ; « full » indique les blocages système-entier.

Décision : Si « full » est non négligeable en charge normale, vous avez besoin d’un tampon (swap/zram), moins d’overcommit ou une redistribution des charges.

Task 6: Identify top memory consumers quickly

cr0x@server:~$ ps -eo pid,comm,rss,vsz,oom_score_adj --sort=-rss | head -n 12
  PID COMMAND           RSS    VSZ OOM_SCORE_ADJ
22134 qemu-system-x86 15632240 28764564 0
 9921 qemu-system-x86  8241100 16544320 0
18772 pveproxy           612332  1054320 0
 1443 pvedaemon          244120   612044 0

Ce que cela signifie : Les processus QEMU dominent. C’est normal, mais cela signifie que votre calcul d’overcommit doit être correct.

Décision : Mappez les PIDs aux VMID, puis décidez quelles machines invitées sont surdimensionnées ou mal comportées.

Task 7: Map QEMU PID to VMID

cr0x@server:~$ pgrep -a qemu-system-x86_64 | head -n 3
22134 /usr/bin/kvm -id 101 -name vm101,debug-threads=on -m 16384 ...
9921 /usr/bin/kvm -id 102 -name vm102,debug-threads=on -m 8192 ...

Ce que cela signifie : Le PID 22134 est la VM 101 avec 16 Go attribués.

Décision : Inspectez la charge de la VM 101, les réglages de balloon et si la RAM attribuée est justifiée.

Task 8: Inspect VM config for ballooning and memory settings

cr0x@server:~$ qm config 101
agent: 1
balloon: 4096
boot: order=scsi0;net0
cores: 8
memory: 16384
name: vm101
ostype: l26
scsi0: rpool:vm-101-disk-0,size=200G

Ce que cela signifie : La VM a 16 Go max, ballon cible 4 Go. Cela permet de récupérer jusqu’à ~12 Go si l’invité coopère.

Décision : Si la pression hôte est fréquente, le ballooning peut aider, mais seulement si le guest a le driver/agent et peut libérer de la mémoire. Sinon, ne comptez pas sur le ballooning pour sauver l’hôte.

Task 9: Check whether ballooning is actually active (virtio-balloon)

cr0x@server:~$ qm monitor 101 --cmd 'info balloon'
balloon: actual=16384

Ce que cela signifie : L’invité retient actuellement les 16 Go complets ; le ballooning ne récupère pas.

Décision : Si vous attendiez une réduction, vérifiez les drivers/agent côté invité et si l’invité utilise réellement la RAM. Sinon, cessez de compter sur le ballooning pour préserver l’hôte.

Task 10: Check LXC container memory limit and current usage

cr0x@server:~$ pct config 104 | egrep 'memory|swap|cores'
cores: 4
memory: 2048
swap: 512
cr0x@server:~$ cat /sys/fs/cgroup/lxc.payload.104.slice/memory.current
1938479104

Ce que cela signifie : La limite du container est 2 Go ; il utilise ~1.8 Go. Proche du plafond.

Décision : Si il a OOM à l’intérieur du container, augmentez la limite ou corrigez la charge. Si il ne devrait jamais dépasser 2 Go, gardez la limite et ajustez l’appli.

Task 11: See cgroup memory events (OOM counts)

cr0x@server:~$ cat /sys/fs/cgroup/lxc.payload.104.slice/memory.events
low 0
high 0
max 3
oom 3
oom_kill 3

Ce que cela signifie : Le cgroup a atteint la valeur max et a tué des tâches 3 fois.

Décision : Ce n’est pas un problème d’hôte OOM. Augmentez la mémoire du container et/ou enquêtez sur la croissance mémoire des processus internes.

Task 12: Check swap status and what kind of swap you have

cr0x@server:~$ swapon --show
NAME      TYPE SIZE USED PRIO
/dev/sda3 partition 8G  6.4G -2

Ce que cela signifie : Il y a du swap et il est utilisé. Bien : ça donne de l’air. Mal : vous pouvez être en thrashing.

Décision : Si le swap est constamment élevé et la latence mauvaise, réduisez l’overcommit et redimensionnez les invités. Si le swap est nul et que vous avez un OOM, ajoutez un petit device swap.

Task 13: Check ZFS ARC size and limits (if using ZFS)

cr0x@server:~$ arcstat -f time,arcsz,arc_max,hit%,miss% 1 1
    time  arcsz     arc_max   hit%  miss%
10:44:10   28.4G      48.0G    92     8

Ce que cela signifie : L’ARC est à 28.4 Go, autorisé jusqu’à 48 Go. C’est beaucoup sur un hôte de 62 Go déjà tendu.

Décision : Envisagez de limiter l’ARC. Mais seulement après avoir confirmé que la pression mémoire est réelle et pas un pic temporaire, et en comprenant votre charge de stockage.

Task 14: Check kernel overcommit settings

cr0x@server:~$ sysctl vm.overcommit_memory vm.overcommit_ratio
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

Ce que cela signifie : Heuristique d’overcommit par défaut. Beaucoup de bases de données et JVM peuvent mal se comporter sous pression quoi qu’il arrive.

Décision : Ne « réparez » pas les OOM en basculant aveuglément les réglages d’overcommit. Utilisez des limites et du capacity planning d’abord ; puis ajustez si vous comprenez les risques.

Task 15: See if the kernel is spending time compacting memory (fragmentation pain)

cr0x@server:~$ grep -E 'compact|pgscan|pgsteal' /proc/vmstat | head
compact_migrate_scanned 184921
compact_free_scanned 773212
compact_isolated 82944
pgscan_kswapd 29133211
pgsteal_kswapd 28911220

Ce que cela signifie : Une forte activité de scan/steal et de compaction peut indiquer un reclaim intense et de la fragmentation.

Décision : Attendez-vous à de la latence. Ajoutez un tampon (swap), réduisez les grosses allocations, examinez THP et réduisez l’overcommit.

Task 16: Verify if Transparent Huge Pages is enabled

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

Ce que cela signifie : THP est réglé sur always. Pour certains hôtes de virtualisation et certains mixes DB/VM, cela augmente la pression de compaction.

Décision : Envisagez de passer à madvise si vous observez des problèmes de reclaim/compaction et pouvez valider l’impact sur les performances.

Task 17: Confirm which guests are configured and how much memory you’ve promised

cr0x@server:~$ qm list
 VMID NAME                 STATUS     MEM(MB)    BOOTDISK(GB) PID
  101 vm101                running    16384             200.00 22134
  102 vm102                running     8192             100.00  9921
  103 vm103                running     4096              80.00  7744
cr0x@server:~$ awk '/^memory:/{sum+=$2} END{print sum " MB assigned to VMs"}' /etc/pve/qemu-server/*.conf
28672 MB assigned to VMs

Ce que cela signifie : Vous pouvez quantifier la mémoire « promise » aux VM. C’est l’étape zéro du calcul d’overcommit.

Décision : Comparez la mémoire promise plus les services hôte plus les besoins ARC/cache avec la RAM physique. Si vous êtes proche de la limite, vous avez besoin de limites et de marge.

Task 18: Inspect host memory cgroup and systemd slices (who is protected)

cr0x@server:~$ systemctl show -p MemoryMax -p MemoryHigh -p ManagedOOMMemoryPressure system.slice
MemoryMax=infinity
MemoryHigh=infinity
ManagedOOMMemoryPressure=auto

Ce que cela signifie : Pas de plafonds mémoire au niveau slice ; oomd peut gérer la pression automatiquement.

Décision : Si oomd tue des services Proxmox importants, vous devrez peut-être ajuster le comportement d’oomd ou protéger les unités critiques avec OOMScoreAdjust et des politiques.

Définir des limites sensées : VMs, containers, hôte et ZFS

La règle unique que vous devriez suivre

Laissez de la marge sur l’hôte. De la vraie marge, pas théorique. Sur un nœud Proxmox, réserver 15–25% de la RAM pour l’hôte
est un point de départ sensé quand vous exécutez ZFS et un mélange d’invités. Si vous exécutez Ceph sur le même nœud (certaines personnes le font), réservez davantage.

Si vous ne pouvez pas vous permettre cette marge, vous ne pouvez pas vous permettre la densité de charge. Ce n’est pas philosophique. C’est physique.

Dimensionnement mémoire des VM : arrêtez de la traiter comme une liste de souhaits

Pour les VM KVM, vous avez trois schémas courants :

  • Allocation statique (pas de ballooning) : la plus prévisible. Idéale pour les charges importantes.
  • Ballooning avec un minimum réaliste : acceptable pour des flottes générales, pas acceptable comme seul mécanisme de sécurité.
  • Overcommit agressif : uniquement si vous pouvez le prouver avec des métriques, et vous acceptez des effondrements de performance occasionnels sous pression.

Politique VM recommandée (opinionnée)

  • Bases de données de production : pas de ballooning, mémoire dédiée, pas d’overcommit hôte au-delà de niveaux modérés.
  • Serveurs applicatifs généraux : ballooning autorisé, mais fixez le minimum du ballon au moins à ce dont l’application a besoin en pic.
  • Agents de build, postes dev : ballooning et plafonds serrés acceptables ; ils doivent être bon marché et légèrement gênants.

Limites LXC : faites-le, mais avec intention

Les containers partagent le noyau de l’hôte. Cela les rend efficaces et rend aussi les mauvaises limites particulièrement douloureuses car le noyau les applique strictement.
Si un container héberge une appli Java avec un heap mal configuré, une limite de 2 Go signifie que le noyau finira par la tuer.
C’est « fonctionner comme conçu ». Votre conception est juste mauvaise.

Pour LXC, un baseline simple :

  • Définissez la mémoire à ce dont la charge a besoin au pic normal.
  • Définissez le swap à une petite valeur (comme 25–50% de la mémoire) si la charge est en rafale, sauf si vous avez des exigences strictes de latence.
  • Suivez les compteurs OOM via memory.events. Si vous voyez oom_kill augmenter, vous êtes sous-provisionné ou en fuite mémoire.

Swap de l’hôte : ennuyeux et efficace

Pour un nœud Proxmox, un device swap modeste est généralement la bonne option. Pas parce que vous voulez swapper. Parce que vous voulez la survivabilité.
4–16 Go est une plage courante selon la taille du nœud et la volatilité des charges. Si vous avez des exigences ultra-faibles de latence, envisagez zram ou acceptez le risque.

Swappiness et pourquoi vous ne devez pas en faire une obsession

Les gens mettent vm.swappiness=1 comme un badge d’honneur. Sur un hôte de virtualisation, vous voulez que le noyau privilégie la RAM,
mais vous voulez aussi qu’il utilise le swap comme soupape de pression. Des valeurs autour de 10–30 sont souvent raisonnables.
Le bon nombre est celui qui correspond à la tolérance de votre charge et à votre budget d’incident.

ZFS ARC : limitez-le quand l’hôte est d’abord hyperviseur

Si votre nœud Proxmox est d’abord serveur de stockage et ensuite hyperviseur, vous pouvez laisser l’ARC croître.
Si c’est d’abord un hyperviseur, l’ARC ne devrait pas prendre la moitié de la machine à moins d’avoir un gros budget RAM.

Une approche pratique :

  • Mesurez l’ARC sous charge typique. Ne réglez pas en fonction d’une heure calme.
  • Décidez d’abord de la marge hôte (par exemple 20%).
  • Fixez un plafond ARC pour que « VMs + containers + services hôte + ARC + tampon » tienne confortablement.

Comment définir un maximum ARC ZFS (exemple)

Sur Debian/Proxmox, on définit généralement une option de module. Exemple : plafonner l’ARC à 16 Gio.

cr0x@server:~$ echo "options zfs zfs_arc_max=17179869184" | sudo tee /etc/modprobe.d/zfs-arc.conf
options zfs zfs_arc_max=17179869184
cr0x@server:~$ update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.12-5-pve

Ce que cela signifie : Le max ARC s’appliquera après reboot (ou rechargement du module, ce que vous ne faites généralement pas en production sauf si vous aimez l’adrénaline).

Décision : Rebootez en fenêtre de maintenance, puis validez le comportement de l’ARC et les performances VM. Si la latence de lecture augmente, vous avez trop bridé.

Protéger l’hôte des invités (et d’eux-mêmes)

Deux protections pratiques :

  • Ne laissez pas l’hôte tourner à 95% de mémoire « disponible » en charge normale. Si « available » est régulièrement sous quelques Gio, vous êtes déjà dans l’incident.
  • Assurez-vous que la couche de gestion Proxmox est difficile à tuer. Si pveproxy meurt, la récupération devient plus lente et risquée, surtout en cluster HA.

Réglage du score OOM : utilisez-le avec parcimonie, mais protégez les services critiques

Linux utilise oom_score_adj pour biaiser les décisions OOM. Vous pouvez rendre les services critiques moins susceptibles d’être tués.
Ne rendez pas tout « intuable » sinon vous voudrez que le noyau panique ou tue quelque chose de vraiment essentiel.

cr0x@server:~$ systemctl edit pveproxy.service
[Service]
OOMScoreAdjust=-800
cr0x@server:~$ systemctl daemon-reload
cr0x@server:~$ systemctl restart pveproxy.service

Ce que cela signifie : pveproxy est moins susceptible d’être choisi comme victime.

Décision : Protégez les services de gestion et de clustering. Ne protégez pas les gouffres mémoire.

Trois mini-récits d’entreprise du terrain

1) L’incident causé par une mauvaise hypothèse : « la mémoire disponible est de la mémoire libre »

Une entreprise de taille moyenne faisait tourner un cluster Proxmox pour des applications internes : ticketing, runners CI, quelques bases de données et quelques machines d’analytics « temporaires ».
Les graphiques du nœud montraient la RAM à 90–95% utilisée la plupart du temps. Personne ne paniquait parce que « Linux utilise la mémoire pour le cache ».
Vrai. Aussi incomplet. La métrique clé était available, pas used.

Une nouvelle VM a été déployée pour un appliance vendor. Elle avait un gros service Java et l’habitude d’allouer de la mémoire en rafales pendant l’ingestion de logs.
Lors de la première ingestion, l’hôte est passé par du reclaim, puis du swap, puis le noyau a dû choisir : tuer quelque chose.
Il a tué le processus QEMU d’une autre VM car il avait le plus grand RSS. Cette VM était la base de données du ticketing.

Le postmortem a été désordonné parce que la VM vendor a été blâmée (« c’est la nouvelle chose »), mais elle ne se comportait pas mal en tant que telle.
L’hôte fonctionnait avec trop peu de marge. Le cache de pages n’était pas le méchant ; c’était le canari.
La mauvaise hypothèse était que grande utilisation « used » est acceptable tant que c’est du cache. Le cache est récupérable, mais la récupération n’est pas gratuite, et sous charge en rafale elle n’est pas rapide.

La correction n’était pas exotique : réserver de la marge pour l’hôte, limiter l’ARC, ajouter une petite partition swap et appliquer des limites mémoire raisonnables sur les containers.
Ils ont aussi cessé de prétendre que le ballooning les sauverait quand les invités utilisaient légitimement leur RAM. Les OOM ont cessé, et les mystères de 2h du matin aussi.

2) L’optimisation qui s’est retournée contre eux : « swap off pour les performances »

Un autre atelier faisait tourner des services sensibles à la latence et a décidé que le swap était l’ennemi. Un consultant avait recommandé de désactiver le swap des années auparavant sur des bare-metal DB.
Quelqu’un a appliqué la même recommandation aux nœuds Proxmox. Ça paraissait plausible. C’était aussi hors contexte.

Pendant un temps, tout semblait plus rapide. Moins de swap signifiait moins de stalls surprises en fonctionnement normal. Victoire.
Puis ils ont ajouté une routine de sauvegarde nocturne qui faisait des exports basés sur snapshot, de la compression et de l’encryption.
Le nœud connaissait des pics d’utilisation mémoire, le reclaim montait, et puis — pas de swap disponible — l’OOM frappait.
Il tuait QEMU, ce qui équivaut à couper les freins d’une voiture pour gagner du poids.

L’« optimisation performance » avait transformé un pic survivable en un crash dur. Pire, elle rendait le motif d’incident imprévisible :
parfois l’hôte survivait, parfois il tuait une autre VM, parfois il tuait un démon hôte et compliquait le dépannage.
Désactiver le swap n’était pas intrinsèquement diabolique ; le faire sans stratégie tampon l’était.

Le compromis final était ennuyeux et efficace : réactiver le swap, mais avec un swappiness conservateur, et monitorer PSI.
Ils ont aussi déplacé la charge de sauvegarde vers un nœud dédié avec plus de marge mémoire. Les services sensibles à la latence sont restés satisfaits, et la roulette nocturne a pris fin.

3) La pratique ennuyeuse mais correcte qui a sauvé la mise : « limites et marge non négociables »

Une troisième équipe opérant Proxmox pour des charges mixtes multi-départements n’était pas particulièrement experte ; elle était extraordinairement disciplinée.
Chaque demande de VM avait un budget mémoire et une justification. Les containers avaient des limites mémoire et swap explicites.
Et chaque nœud avait une cible de « réserve hôte » fixe traitée comme une taxe de capacité, pas comme une suggestion optionnelle.

Un trimestre, un vendor a poussé une mise à jour qui a introduit une fuite mémoire dans un agent tournant dans plusieurs containers.
La fuite était lente et polie — jusqu’à ce qu’elle ne le soit plus. Les containers ont commencé à atteindre leurs limites cgroup et à se faire tuer.
C’était ennuyeux, mais contenu. L’hôte est resté stable. Pas d’événements OOM globaux, pas de crash hyperviseur, pas de défaillance en cascade.

Leur monitoring a attrapé l’augmentation des compteurs oom_kill par container, et l’équipe a rollbacké la version de l’agent.
Quelques services ont redémarré, les clients ont vu des petites perturbations, et l’incident s’est terminé dans l’heure.
La pratique « ennuyeuse » — limites appliquées et marge — a transformé un outage potentiel en un bug applicatif gérable.

Si vous construisez la fiabilité, voici à quoi ressemble la victoire : moins d’incidents dramatiques, plus de petites pannes contenues, et un diagnostic rapide.
Pas glamour. Très efficace.

Erreurs courantes : symptômes → cause racine → correctif

1) Symptom: “Random VM crashes; host stays up”

Cause racine : L’OOM-killer global du noyau a tué un processus QEMU ; la VM est morte.

Fix : Réduire l’overcommit, ajouter de la marge pour l’hôte, vérifier la stratégie swap et envisager le capping ARC. Confirmer via journalctl -k.

2) Symptom: “Containers restart; host memory looks fine”

Cause racine : OOM de limite mémoire cgroup à l’intérieur du container ; kill local.

Fix : Augmenter la mémoire/swap du container, ajuster l’application (limites heap), ou imposer un meilleur dimensionnement. Vérifier via memory.events.

3) Symptom: “Proxmox web UI dies during pressure; ssh still works”

Cause racine : systemd-oomd ou OOM noyau a tué pveproxy/pvedaemon car ils étaient tuables et avaient un RSS non négligeable.

Fix : Protéger les services critiques avec OOMScoreAdjust ; réduire les déclencheurs de pression ; ajuster oomd si activé.

4) Symptom: “OOM happens during backups or replication jobs”

Cause racine : Pics mémoire transitoires dus à la compression/encryption, opérations de snapshot et comportement du cache noyau.

Fix : Limiter la concurrence, déplacer les jobs de sauvegarde hors du nœud, ajouter un tampon swap, réserver plus de marge.

5) Symptom: “No swap, no warning, instant OOM under burst”

Cause racine : Swap désactivé ; le reclaim ne suit pas.

Fix : Ajouter un swap modeste ou zram ; régler le swappiness ; utiliser PSI pour détecter la pression avant l’OOM.

6) Symptom: “ZFS ARC ‘eats’ memory; OOM after heavy reads”

Cause racine : L’ARC croît, puis les allocations invitées montent ; le délai de rétraction de l’ARC contribue à la pression.

Fix : Plafonner l’ARC selon la charge mesurée ; garantir la réserve hôte ; valider latence et ratio de hit après changement.

7) Symptom: “OOM with plenty of free memory shown earlier”

Cause racine : Problèmes de fragmentation/compaction (THP, grosses allocations) ou taux d’allocation anonyme soudain.

Fix : Envisagez THP=madvice, ajoutez du swap, réduisez les patterns de grosses allocations et évitez de surcharger trop les nœuds.

8) Symptom: “Fix was to add RAM; OOM returns months later”

Cause racine : Pas de limites, pas de budget ; la consommation a grandi pour occuper la capacité.

Fix : Implémentez des limites par locataire, des cibles de marge, et des revues périodiques de redimensionnement. Ajoutez du monitoring pour la mémoire promise vs utilisée.

Listes de vérification / plan pas à pas

Checklist immédiate d’incident (premiers 15 minutes)

  1. Confirmer le tueur : noyau OOM vs cgroup OOM vs systemd-oomd (journalctl).
  2. Identifier ce qui est mort (PID, commande, VMID/slice container).
  3. Capturer l’état courant : free -h, swapon --show, PSI (/proc/pressure/memory).
  4. Lister les plus gros consommateurs (ps --sort=-rss) et mapper les PIDs QEMU aux VMIDs.
  5. Si l’hôte thrash : réduisez la charge immédiatement (migrer/arrêter les invités non critiques) plutôt que de redémarrer des démons.

Checklist de stabilisation (24 heures suivantes)

  1. Fixer une cible de marge hôte (commencez à 20% RAM pour des charges mixtes).
  2. Assurer l’existence d’un swap ; définir un swappiness raisonnable (éviter les extrêmes).
  3. Auditer les allocations invitées : mémoire promise vs besoin réel ; supprimer la « RAM de confort ».
  4. Pour les containers : définir mémoire et swap ; suivre memory.events pour les compteurs OOM.
  5. Pour les VM : décider où le ballooning est acceptable ; vérifier l’efficacité du driver balloon dans ces invités.
  6. Si vous utilisez ZFS : mesurer l’ARC et définir un plafond qui respecte la marge hyperviseur.

Checklist de durcissement (prochaine itération)

  1. Ajouter des alertes sur : mémoire disponible hôte faible, tendance swap, PSI full avg60, événements OOM noyau, compteurs OOM cgroup.
  2. Protéger les services Proxmox critiques avec OOMScoreAdjust et vérifier les politiques systemd-oomd.
  3. Réduire la concurrence planifiée : sauvegardes, réplications, scrubs et jobs batch lourds.
  4. Documenter une politique d’overcommit : ce qui est permis, ce qui ne l’est pas, et comment c’est mesuré.
  5. Faire un test de charge contrôlé : simuler des allocations en rafale et confirmer que l’hôte dégrade de façon graduelle plutôt que de tuer QEMU.

FAQ

1) Pourquoi Linux tue-t-il un processus « au hasard » au lieu de celui qui a causé le problème ?

Ce n’est pas aléatoire. Le noyau calcule un score de gravité basé sur l’utilisation mémoire et d’autres facteurs. L’« agresseur » peut être plus petit que la « victime » la plus grosse.
C’est pourquoi les limites par locataire valent tellement : elles gardent le rayon d’impact local.

2) Est-ce normal que Proxmox tourne à 90% d’utilisation mémoire ?

« Used » est un nombre trompeur car il inclut le cache. Ce qui compte, c’est available, la tendance du swap et les signaux de pression.
Un nœud peut sembler « plein » et être sain, ou sembler « correct » et être à un pic de l’OOM.

3) Dois-je désactiver le swap sur Proxmox ?

Généralement non. Un petit swap est une marge de sécurité. Si vous le désactivez, vous choisissez des kills durs plutôt que des ralentissements.
Si vous avez des exigences strictes de latence, envisagez une autre stratégie tampon (comme zram) et acceptez le risque explicitement.

4) Est-ce que l’ARC ZFS cause des OOM ?

L’ARC est conçu pour rétrécir, mais il peut quand même contribuer à la pression si le système est déjà surcommit ou si les pics de charge vont plus vite que la rétraction de l’ARC.
Plafonner l’ARC est raisonnable sur les nœuds hyperviseurs.

5) Le ballooning VM empêche-t-il l’OOM ?

Cela peut aider, mais ce n’est pas une garantie. Le ballooning nécessite la coopération du guest et de la mémoire récupérable à l’intérieur du guest.
Si les invités utilisent légitimement leur RAM, le ballooning ne peut pas créer de la mémoire libre magiquement.

6) Comment savoir si un OOM s’est produit à l’intérieur d’un container vs sur l’hôte ?

Les messages OOM de container/cgroup mentionnent « Memory cgroup out of memory » et réfèrent à un chemin de cgroup (comme /lxc.payload.104.slice).
Les messages OOM globaux hôte ressemblent à « oom-killer: constraint=CONSTRAINT_NONE » et peuvent tuer QEMU ou des démons hôte.

7) Quelle est la façon la plus sûre de réduire rapidement le risque OOM ?

Ajoutez un tampon swap modeste, appliquez des limites de container, arrêtez l’overcommit extrême et assurez une marge hôte.
Si vous utilisez ZFS, plafonnez l’ARC seulement après mesure. Puis validez avec PSI et le comportement pendant les fenêtres de maintenance.

8) Si je mets OOMScoreAdjust très bas pour tout, est-ce que j’arrêterai les kills OOM ?

Vous changerez juste qui meurt — ou pousserez le noyau vers des modes d’échec pires. Protégez un petit ensemble de services hôtes critiques.
Laissez tout le reste tuable, et réglez la vraie source de pression avec capacité et limites.

9) Pourquoi les OOM arrivent-ils souvent pendant les sauvegardes, scrubs ou réplications ?

Ces opérations augmentent l’I/O, l’agitation des métadonnées et l’activité de compression/encryption. Le cache noyau et les couches filesystem deviennent occupés,
et QEMU peut allouer des buffers transitoires. Si vous avez dimensionné seulement pour l’état stable, la maintenance devient votre charge de pointe.

10) Quel est un ratio d’overcommit « sensé » pour la mémoire VM ?

Il n’y a pas de nombre universel. Commencez conservateur : ne dépassez pas la RAM physique moins la réserve hôte avec des allocations « garanties » pour les VM critiques.
Si vous surcommettez, faites-le sur des invités basse-priorité, avec ballooning là où approprié, et avec du monitoring qui prouve que c’est sûr.

Conclusion : quoi faire ensuite

Les événements OOM-killer sur Proxmox sont rarement mystérieux. Ce sont la facture des décisions mémoire que vous avez prises il y a des mois :
overcommit sans limites, le swap comme tabou moral, et « on règlera ZFS plus tard ».
Le noyau encaisse.

Faites ceci ensuite, dans cet ordre :

  1. Classifiez l’événement (OOM noyau vs OOM cgroup vs systemd-oomd) en utilisant les requêtes de logs ci-dessus.
  2. Reconstruisez votre budget : réserve hôte, mémoire attribuée aux invités, tampon swap, cible ARC ZFS.
  3. Appliquez des limites sur les containers et adoptez une politique de dimensionnement VM correspondant à la criticité des charges.
  4. Ajoutez de l’observabilité : alertes sur PSI, mémoire disponible, tendance swap et compteurs OOM.
  5. Validez sous charge de maintenance, pas seulement durant les heures calmes.

L’objectif n’est pas de « ne jamais OOM ». L’objectif est de rendre la pression mémoire prévisible, contenue et ennuyeuse. En production, ennuyeux est un compliment.

← Précédent
Pourquoi les jeux veulent un CPU et le rendu en veut un autre
Suivant →
Le point de montage ZFS : le piège de montage qui fait « disparaître » les jeux de données

Laisser un commentaire