Eventos OOM-killer en Proxmox: por qué mata procesos y cómo establecer límites sensatos

¿Te fue útil?

El host Proxmox estuvo “bien” durante meses. Y luego, un martes: las VMs se pausan, la interfaz se queda lenta, ssh tarda una eternidad y los logs parecen la escena de un crimen.
Algo fue derribado. No fue el disco. No fue la red. Fue la memoria.

Cuando el OOM-killer de Linux aparece en un host de virtualización, no está dramatizando. Está haciendo lo último responsable que puede:
matar un proceso para que el kernel pueda seguir respirando. Tu trabajo es dejar de invitarlo.

Qué es realmente un evento OOM (y qué no es)

En Linux, “sin memoria” no significa “la RAM llegó al 100%”. Significa que el kernel no pudo satisfacer una solicitud de asignación de memoria
sin violar sus propias reglas, y no pudo recuperar suficiente memoria lo bastante rápido para mantenerse estable.
En ese punto, Linux tiene una lista corta de opciones malas. La menos mala es matar algo.

En Proxmox esto es especialmente delicado porque el host es a la vez hipervisor y plataforma de carga de trabajo:
ejecuta procesos QEMU (uno por VM), procesos de contenedores LXC, demonios de almacenamiento, agentes de monitorización y la pila de gestión de Proxmox.
Si el host pierde el proceso equivocado, el hipervisor puede sobrevivir pero tus VMs pueden pausarse o colapsar, y tu historia de HA se vuelve danza interpretativa.

Las prioridades del kernel durante la presión de memoria

Linux intenta recuperar memoria en este orden aproximado: liberar page cache, recuperar memoria anónima (swap), compactar memoria y finalmente
invocar al OOM-killer. En un host con swap deshabilitado y overcommit agresivo, te saltas la mitad de la escalera y saltas directamente a “matar un proceso”.
Ese salto es lo que ves como un evento OOM.

OOM-killer vs systemd-oomd vs límites de cgroup

Tres mecanismos distintos de “alguien mató mi proceso” se mezclan rutinariamente:

  • Kernel OOM-killer: global, último recurso, elige una víctima en base a un puntaje de “maldad”.
  • cgroup OOM: un cgroup de un contenedor/VM alcanza su límite de memoria; el kernel mata dentro de ese cgroup, no de forma global.
  • systemd-oomd: un daemon en espacio de usuario que mata antes basándose en señales de presión (PSI). Puede ser un salvavidas o una molestia.

Dejan diferentes huellas en los logs, y la solución depende de cuál de ellos actuó.
Si no sabes cuál actuó, vas a “arreglarlo” añadiendo RAM y esperando. Eso funciona hasta que deja de hacerlo.

Aquí está la verdad cruda: si tratas al OOM-killer como un bug, seguirás recibiendo visitas sorpresa. Trátalo como una alarma de humo.
La alarma de humo no es tu problema; tus hábitos en la cocina lo son.

Una cita aplicable en ops (idea parafraseada): John Allspaw: la fiabilidad viene de asumir cómo fallan los sistemas y diseñar alrededor de esa realidad.

Broma #1: El OOM-killer es como un despido por recorte de presupuesto—rápido, injusto y todos insisten en que fue “basado en métricas”.

Datos interesantes y un poco de historia

Esto no es trivia por amor a la trivia. Cada hecho explica por qué tu host Proxmox se comporta como lo hace cuando la memoria se aprieta.

  1. Linux tiene un OOM-killer desde hace décadas, porque el kernel no puede simplemente “devolver NULL” en muchos caminos críticos de asignación sin arriesgar corrupción.
  2. Los primeros sistemas Linux dependían mucho del swap; las flotas modernas a menudo deshabilitan swap por política, lo que hace los eventos OOM más abruptos e insensibles.
  3. Los cgroups cambiaron el juego: en lugar de que toda la máquina caiga, la memoria puede restringirse por servicio o contenedor—y las muertes ocurren localmente.
  4. El puntaje de “maldad” considera uso de memoria y ajustadores (como oom_score_adj), por eso un gran proceso QEMU suele ser la víctima.
  5. ZFS ARC no está “robando” memoria; es una caché diseñada para crecer y reducirse. Pero aún puede contribuir a la presión cuando está mal ajustado.
  6. El ballooning existe porque el overcommit existe: es una forma de recuperar memoria del huésped bajo presión, pero solo ayuda si los huéspedes cooperan y tienen páginas recuperables.
  7. PSI (pressure stall information) es una característica relativamente moderna de Linux que mide el tiempo detenido por presión de recursos; habilitó killers proactivos como systemd-oomd.
  8. THP (Transparent Huge Pages) puede aumentar la latencia de asignación y problemas de fragmentación bajo presión, lo que a veces convierte “lento” en “OOM”.
  9. La file cache no es “memoria gratis” ni es “memoria usada” de la misma manera; Linux la reclamará, pero no siempre lo suficientemente rápido para allocators con picos.

Cómo se consume la memoria en un host Proxmox

Tres cubos que importan

Piensa en la memoria del host como tres cubos que compiten entre sí:

  • Memoria de los huéspedes: RSS de QEMU para VMs, más uso de memoria de contenedores. Este es el grande.
  • Servicios del host: pveproxy, pvedaemon, corosync, ceph (si lo ejecutas), monitorización, backups y algún “agente pequeño” que silenciosamente se come 1–2 GB.
  • Cache y kernel: page cache, slab, ZFS ARC, metadata y asignaciones del kernel. Esto puede ser enorme y se supone que debe ser grande—hasta que no lo es.

VMs: la memoria de QEMU no es solo “la RAM de la VM”

Cuando asignas 16 GB a una VM, el proceso QEMU típicamente mapea esa memoria. Con KVM, gran parte está respaldada por la RAM del host.
Ballooning, KSM y swap pueden cambiar la forma, pero la regla operativa simple es:
La RAM asignada a una VM es una reserva contra tu host a menos que tengas un plan de overcommit probado.

Además: backups, snapshots y I/O intenso pueden crear picos transitorios de memoria en QEMU y el kernel. Si presupuestas solo para estado estable,
tendrás eventos OOM durante “ventanas de mantenimiento aburridas” a las 02:00. Ahí es cuando nadie vigila y todo escribe.

Contenedores: los límites son amigos tuyos, hasta que son mentiras

LXC en Proxmox usa cgroups. Si fijas un límite de memoria, el contenedor no puede sobrepasarlo. Genial.
Pero si lo pones demasiado bajo, el contenedor hará OOM internamente, lo que parece “mi app murió aleatoriamente”.
Si lo pones demasiado alto, realmente no limitaste nada.

ZFS: ARC es una característica de rendimiento, no un bufé gratis

Proxmox ama ZFS, y ZFS ama la RAM. ARC crecerá para mejorar el rendimiento de lectura y cacheo de metadata.
Se reducirá con la presión de memoria, pero no siempre a la velocidad que necesitas cuando una carga de trabajo asigna repentinamente unos gigabytes.
ARC se puede ajustar. Hazlo deliberadamente. No lo hagas porque un post en un foro dijo “ponlo al 50%”.

Swap: no es un pecado, es un interruptor de circuito

Swap en un host Proxmox no se trata de ejecutar todo desde disco. Se trata de sobrevivir picos y darle tiempo al kernel para recuperar.
Una pequeña cantidad de swap (o zram) puede convertir un OOM duro en un incidente lento que puedes mitigar. Sí, hacer swap es feo. El downtime también lo es.

Broma #2: Deshabilitar swap para “evitar lentitud” es como quitar los airbags de tu coche para ahorrar peso—técnicamente más rápido, médicamente cuestionable.

Guion rápido de diagnóstico

No ganas un incidente OOM leyendo cada línea del log. Ganas demostrando qué mecanismo mató qué, y por qué la presión de memoria se volvió irrecuperable.
Este es el orden que te da respuestas rápido.

1) Confirma qué mató el proceso

  • ¿Kernel global OOM-killer?
  • ¿OOM por límite de cgroup dentro de un contenedor/servicio?
  • ¿systemd-oomd mató preventivamente?

2) Identifica la víctima y el agresor

La víctima es lo que fue matado. El agresor es lo que causó la presión.
A veces son lo mismo. A menudo no: una VM puede asignar, pero otra VM es la que muere porque parecía “más gorda”.

3) Decide si es capacidad o pico

Los problemas de capacidad muestran memoria anon sostenida alta y uso de swap (si existe). Los problemas de pico muestran bloqueos repentinos, alta tasa de asignación
y repetidas reclamaciones/compactación. Tu arreglo difiere: capacidad significa re-presupuestar; picos significan alisar, limitar o añadir un buffer.

4) Revisa rápido los culpables a nivel de host

  • ¿ZFS ARC demasiado grande o lento para reducir?
  • ¿Crecimiento de slab del kernel (por ejemplo, red, metadata ZFS, inode/dentry) por fuga o patrón de carga?
  • ¿Tareas de backup causando asignaciones por ráfaga?
  • ¿Ballooning mal configurado o ineficaz?

5) Decide: limitar, mover o añadir

Tus tres palancas son (1) establecer límites para que un solo inquilino no pueda quemar el edificio, (2) redistribuir cargas de trabajo, y (3) añadir RAM/swap.
“Simplemente añade RAM” no está mal, solo que no es un plan si sigues overcommitendo.

Tareas prácticas: comandos, salidas y decisiones

Estas tareas están ordenadas desde “confirmar el evento” hasta “establecer guardarraíles” y “demostrar que lo arreglaste”.
Cada una incluye un fragmento realista de salida y qué decisión tomar a partir de ella.

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

Qué significa: Esto es un kill global del kernel (constraint=CONSTRAINT_NONE). Un proceso QEMU murió; probablemente una VM se bloqueó.

Decisión: Trátalo como agotamiento de memoria del host, no como un límite por contenedor. Avanza a las comprobaciones de capacidad y overcommit del host.

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

Qué significa: Esto es local a un cgroup (aquí, un contenedor LXC). El host no estaba necesariamente sin memoria.

Decisión: Aumenta el límite del contenedor, arregla la memoria de la app o añade swap dentro del contenedor si procede. No “arregles” ajustando ARC del host.

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.

Qué significa: El killer en espacio de usuario actuó temprano basado en señales de presión.

Decisión: Ajusta las políticas de oomd o exime servicios críticos; aún así investiga por qué la presión llegó tan alta.

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

Qué significa: La memoria disponible es baja; el swap está muy usado. Estás en presión sostenida.

Decisión: Probablemente es capacidad o una carga descontrolada. Considera migrar VMs, aumentar RAM y revisar el dimensionamiento por huésped. No solo reinicies cosas.

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

Qué significa: “some” indica tiempo en que tareas estuvieron bloqueadas por reclaim de memoria; “full” indica bloqueos a nivel de sistema.

Decisión: Si full no es trivial bajo carga normal, necesitas buffer (swap/zram), menos overcommit o redistribución de carga.

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

Qué significa: Los procesos QEMU dominan. Eso es normal, pero significa que tu cálculo de overcommit debe ser correcto.

Decisión: Mapea los PIDs a VMIDs, luego decide qué huéspedes están sobredimensionados o se comportan mal.

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 ...

Qué significa: El PID 22134 es la VM 101 con 16 GB asignados.

Decisión: Inspecciona la carga de VM 101, la configuración de balloon y si la RAM asignada está justificada.

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

Qué significa: La VM tiene 16 GB máximo, balloon target 4 GB. Esto permite recuperar ~12 GB si el guest coopera.

Decisión: Si la presión del host es frecuente, el ballooning puede ayudar, pero solo si el guest tiene el driver/agent y puede liberar memoria. Si no, deja de confiar en ballooning para salvar el host.

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

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

Qué significa: El guest está reteniendo los 16 GB completos; el ballooning no está recuperando.

Decisión: Si esperabas que se redujera, verifica drivers/agent del guest y si el guest realmente está usando la RAM. Si no, deja de depender del ballooning para salvar el host.

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

Qué significa: El límite del contenedor es 2 GB; está usando ~1.8 GB. Cerca del techo.

Decisión: Si hace OOM dentro del contenedor, sube el límite o arregla la carga. Si nunca debería exceder 2 GB, deja el límite y ajusta la app.

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

Qué significa: El cgroup alcanzó max y mató tareas 3 veces.

Decisión: Esto no es un problema de OOM del host. Aumenta la memoria del contenedor y/o investiga el crecimiento de procesos dentro del contenedor.

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

Qué significa: Hay swap y se está usando. Bien: da margen de respiración. Mal: podrías estar thrashing.

Decisión: Si el swap está constantemente alto y la latencia es mala, reduce overcommit y ajusta los huéspedes. Si swap es cero y tuviste OOM, añade un dispositivo de swap pequeño.

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

Qué significa: ARC está en 28.4 GB, permitido hasta 48 GB. Eso es mucho en un host de 62 GB que ya está ajustado.

Decisión: Considera limitar ARC. Pero solo después de confirmar que la presión de memoria es real y no un pico temporal, y entender tu carga de almacenamiento.

Task 14: Check kernel overcommit settings

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

Qué significa: Heurística de overcommit por defecto. Muchas bases de datos y JVMs pueden comportarse mal bajo presión igualmente.

Decisión: No “arregles OOM” cambiando las opciones de overcommit a ciegas. Usa límites y planificación de capacidad primero; luego afina si entiendes el riesgo.

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

Qué significa: Alta actividad de scan/steal y compactación puede indicar reclaim fuerte y fragmentación.

Decisión: Espera latencia. Añade buffer (swap), reduce asignaciones enormes, revisa THP y reduce el overcommit.

Task 16: Verify if Transparent Huge Pages is enabled

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

Qué significa: THP está en always. Para algunos hosts de virtualización y ciertas mezclas DB/VM, esto incrementa la presión de compactación.

Decisión: Considera cambiar a madvise si observas problemas de reclaim/compactación y puedes validar el impacto en rendimiento.

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

Qué significa: Puedes cuantificar la memoria “prometida” a VMs. Esto es el paso cero en las cuentas de overcommit.

Decisión: Compara la memoria prometida más los servicios del host más las necesidades de ARC/cache contra la RAM física. Si estás cerca del límite, necesitas límites y margen.

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

Qué significa: No hay topes de memoria a nivel de slice; oomd puede gestionar la presión automáticamente.

Decisión: Si oomd mata servicios importantes de Proxmox, quizá necesites ajustar el comportamiento de oomd o proteger unidades críticas con OOMScoreAdjust y políticas.

Establecer límites sensatos: VMs, contenedores, host y ZFS

La única regla que realmente deberías seguir

Deja margen en el host. Margen real, no teórico. En un nodo Proxmox, reservar 15–25% de la RAM para el host
es un punto de partida sensato cuando ejecutas ZFS y una mezcla de huéspedes. Si ejecutas Ceph en el mismo nodo (la gente lo hace), reserva más.

Si no puedes permitirte ese margen, no puedes permitirte la densidad de carga. No es filosófico. Es físico.

Dimensionamiento de memoria para VMs: deja de tratarlo como una lista de deseos

Para VMs KVM, tienes tres patrones comunes:

  • Asignación estática (sin ballooning): más predecible. Mejor para cargas críticas.
  • Ballooning con un mínimo realista: bien para flotas de propósito general, no bien como único mecanismo de seguridad.
  • Overcommit agresivo: solo si puedes probarlo con métricas, y aceptas colapsos de rendimiento ocasionales bajo presión.

Política recomendada para VMs (opinión)

  • Bases de datos de producción: sin ballooning, memoria dedicada, no overcommit del host más allá de niveles suaves.
  • Servidores de aplicaciones generales: ballooning permitido, pero fija el mínimo del balloon por encima de lo que la app necesita en pico.
  • Agentes de compilación, máquinas dev: ballooning y límites más estrictos aceptables; deben ser baratos y algo molestos.

Límites para contenedores LXC: hazlo, pero con intención

Los contenedores comparten el kernel del host. Eso los hace eficientes y también hace que los malos límites sean especialmente dolorosos porque el kernel los aplicará firmemente.
Si un contenedor aloja una app Java con un heap mal configurado, un límite de 2 GB significa que el kernel finalmente la matará.
Eso es “funcionar como está diseñado”. Tu diseño es simplemente malo.

Para LXC, una base sencilla:

  • Fija memory a lo que la carga necesita en el pico normal.
  • Fija swap a un valor pequeño (como 25–50% de la memoria) si la carga es con picos, salvo que tengas requisitos estrictos de latencia.
  • Monitorea contadores OOM vía memory.events. Si ves oom_kill incrementarse, estás infra-provisionado o hay fuga.

Swap en el host: aburrido y efectivo

Para un nodo Proxmox, un dispositivo de swap modesto suele ser la decisión correcta. No porque quieras hacer swap. Porque quieres supervivencia.
4–16 GB es un rango común dependiendo del tamaño del nodo y la volatilidad de las cargas. Si tienes requisitos de latencia ultra bajos, considera zram o acepta el riesgo.

Swappiness y por qué no deberías obsesionarte

La gente pone vm.swappiness=1 como si fuera una insignia de honor. En un host de virtualización, quieres que el kernel prefiera RAM,
pero también quieres que use swap como válvula de alivio. Valores alrededor de 10–30 suelen ser razonables.
El número correcto es el que coincida con la tolerancia de tu carga y tu presupuesto de incidentes.

ZFS ARC: capéalo cuando el host es un hipervisor

Si tu nodo Proxmox es primero servidor de almacenamiento y luego hipervisor, puedes dejar que ARC crezca.
Si es hipervisor primero, ARC no debería tomar la mitad de la máquina a menos que tengas un gran presupuesto de RAM.

Un enfoque práctico:

  • Mide ARC bajo carga típica. No afines basándote en una hora tranquila.
  • Decide el margen del host primero (por ejemplo 20%).
  • Capea ARC para que “VMs + contenedores + servicios del host + ARC + buffer” quepan cómodamente.

Cómo establecer un ZFS ARC max (ejemplo)

En Debian/Proxmox, normalmente se establece una opción de módulo. Ejemplo: limitar ARC a 16 GiB.

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

Qué significa: El máximo de ARC se aplicará tras un reboot (o recarga del módulo, que generalmente no haces en un nodo de producción a menos que disfrutes la adrenalina).

Decisión: Reinicia en una ventana de mantenimiento y luego valida el comportamiento de ARC y el rendimiento de las VMs. Si la latencia de lectura aumenta, limitaste demasiado.

Proteger el host de los huéspedes (y de sí mismo)

Dos protecciones prácticas:

  • No dejes que el host funcione al 95% de “available” bajo carga normal. Si “available” está regularmente por debajo de unos pocos GB, ya estás en el incidente.
  • Asegura que el plano de gestión de Proxmox sea difícil de matar. Si pveproxy muere, la recuperación se vuelve más lenta y riesgosa, especialmente con clusters HA.

Ajuste del OOM score: úsalo con moderación, pero úsalo para servicios críticos

Linux usa oom_score_adj para sesgar decisiones OOM. Puedes hacer que servicios críticos sean menos propensos a ser matados.
No hagas que todo sea “inmortal” a menos que quieras que el kernel haga panic o mate algo realmente esencial.

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

Qué significa: pveproxy es menos probable de ser seleccionado como víctima.

Decisión: Protege servicios de gestión y clustering. No protejas a los grandes consumidores de memoria.

Tres microhistorias corporativas desde el campo

1) El incidente causado por una suposición equivocada: “la memoria disponible es memoria libre”

Una empresa mediana ejecutaba un cluster Proxmox para apps internas: ticketing, CI runners, un par de bases de datos y algunas máquinas analíticas “temporales”.
Los gráficos del nodo mostraban RAM al 90–95% la mayor parte del tiempo. Nadie entró en pánico porque “Linux usa memoria para caché.”
Verdadero. También incompleto. La métrica clave era available, no used.

Se desplegó una nueva VM para un appliance de un proveedor. Tenía un gran servicio Java y la costumbre de asignar memoria en ráfagas durante ingestión de logs.
Durante la primera ingestión, el host cayó en reclaim, luego swap, luego el kernel tuvo que elegir: matar algo.
Mató el proceso QEMU de otra VM porque tenía el RSS más grande. Esa VM resultó ser la base de datos de ticketing.

El postmortem fue desordenado porque culparon a la VM del proveedor (“es lo nuevo”), pero en realidad no se estaba “portando mal” según su propia definición.
El host estaba funcionando con muy poco margen. El page cache no era el villano; era la canaria.
La suposición equivocada fue que mucha memoria “usada” está bien mientras sea caché. La caché es recuperable, pero recuperarla no es gratis, y bajo carga en ráfaga no es rápida.

La solución no fue exótica: reservar margen para el host, limitar ARC, añadir una pequeña partición de swap y aplicar límites razonables a contenedores.
También dejaron de fingir que el ballooning los salvaría cuando los huéspedes usaban legítimamente su RAM. Los OOM se detuvieron, y también los misterios de las 2 a.m.

2) La optimización que salió mal: “swap off por rendimiento”

Otra empresa ejecutaba servicios sensibles a la latencia y decidió que el swap era el enemigo. Un consultor había recomendado deshabilitar swap años antes en servidores bare-metal de bases de datos.
Alguien aplicó la misma recomendación a los nodos Proxmox. Sonaba plausible. También estaba fuera de contexto.

Durante un tiempo, todo se sintió más rápido. Menos swap significó menos stalls sorpresa en operación normal. Victoria.
Luego añadieron una rutina de backup nocturna que hacía exportes basados en snapshot, compresión y algo de cifrado.
El nodo tenía picos de uso de memoria, el reclaim se disparaba y entonces—sin swap disponible—llegaba el OOM.
Mató QEMU, que es el equivalente en hipervisor a cortar las líneas de freno para reducir peso del coche.

La “optimización de rendimiento” convirtió un pico sobrevivible en un crash duro. Peor aún, volvió el patrón de incidentes impredecible:
a veces el host vivía, a veces mataba otra VM, a veces mataba un demonio del host y complicaba el troubleshooting.
Deshabilitar swap no era inherentemente malo; hacerlo sin una estrategia de buffer sí lo fue.

El compromiso eventual fue aburrido y efectivo: reactivar swap, pero con swappiness conservador, y monitorizar PSI.
También movieron la carga de backups a un nodo dedicado con más margen de memoria. Los servicios sensibles a la latencia quedaron felices, y terminó la ruleta nocturna.

3) La práctica aburrida pero correcta que salvó el día: “límites y margen son innegociables”

Un tercer equipo ejecutaba Proxmox para cargas mixtas entre varios departamentos. No eran especialmente diestros; eran inusualmente disciplinados.
Cada petición de VM tenía un presupuesto de memoria y una justificación. Los contenedores tenían límites explícitos de memoria y swap.
Y cada nodo tenía un “reserva del host” fijo tratado como un impuesto de capacidad, no como una sugerencia opcional.

Un trimestre, un proveedor lanzó una actualización que introdujo una fuga de memoria en un agente que corría en múltiples contenedores.
La fuga fue lenta y educada—hasta que dejó de serlo. Los contenedores empezaron a alcanzar sus límites de cgroup y a morir.
Molesto, pero contenido. El host se mantuvo estable. No hubo OOM globales, ni crash del hipervisor, ni fallos en cascada.

Su monitorización captó el aumento de contadores oom_kill por contenedor, y el equipo revirtió la versión del agente.
Unos cuantos servicios se reiniciaron, los clientes vieron pequeños destellos y el incidente terminó en la misma hora que empezó.
La práctica “aburrida”—límites aplicados y margen—convirtió un potencial outage a nivel host en un bug de aplicación manejable.

Si estás construyendo fiabilidad, esto es a lo que se parece ganar: menos incidentes dramáticos, más fallos pequeños y contenidos, y diagnóstico rápido.
No es glamuroso. Muy efectivo.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: “VMs se caen aleatoriamente; el host sigue en pie”

Causa raíz: El kernel global OOM-killer mató un proceso QEMU; la VM murió.

Solución: Reduce overcommit, añade margen al host, verifica la estrategia de swap y considera capear ARC. Confirma vía journalctl -k.

2) Síntoma: “Contenedores se reinician; la memoria del host parece bien”

Causa raíz: OOM por límite de cgroup dentro del contenedor; kill local.

Solución: Aumenta memoria/swap del contenedor, afina la app (límites de heap) o aplica mejor dimensionamiento. Verifica con memory.events.

3) Síntoma: “La UI web de Proxmox muere durante presión; ssh sigue funcionando”

Causa raíz: systemd-oomd o el kernel mató pveproxy/pvedaemon porque eran killables y tenían RSS no trivial.

Solución: Protege servicios críticos con OOMScoreAdjust; reduce disparadores de presión; ajusta oomd si está habilitado.

4) Síntoma: “OOM ocurre durante backups o replicaciones”

Causa raíz: Picos transitorios de memoria por compresión/cifrado, operaciones de snapshot y comportamiento de cache del kernel.

Solución: Limita concurrencia programada, mueve backups fuera del nodo, añade buffer de swap y reserva más margen.

5) Síntoma: “No hay swap, sin advertencia, OOM instantáneo en ráfaga”

Causa raíz: Swap deshabilitado; reclaim no puede mantenerse al día.

Solución: Añade swap o zram modestos; ajusta swappiness; usa PSI para detectar presión antes del OOM.

6) Síntoma: “ZFS ARC ‘se come’ la memoria; OOM tras lecturas intensas”

Causa raíz: ARC crece y luego las asignaciones de huéspedes se disparan; la lentitud de reducción de ARC contribuye a la presión.

Solución: Capa ARC basado en la carga medida; asegura margen del host; valida latencia y hit ratio tras el cambio.

7) Síntoma: “OOM con bastante memoria libre mostrada antes”

Causa raíz: Problemas de fragmentación/compactación (THP, asignaciones enormes) o tasa repentina de asignación anónima.

Solución: Considera THP=madvice, añade swap, reduce patrones de asignaciones enormes y evita sobrecargar demasiado el nodo.

8) Síntoma: “La solución fue añadir RAM; el OOM vuelve meses después”

Causa raíz: No hay límites, no hay presupuesto; el consumo crece hasta alcanzar la capacidad.

Solución: Implementa límites por inquilino, objetivos de margen y revisiones periódicas de right-sizing. Añade monitorización para asignado vs usado.

Listas de verificación / plan paso a paso

Checklist inmediato del incidente (primeros 15 minutos)

  1. Confirma el killer: kernel OOM vs cgroup OOM vs systemd-oomd (journalctl queries).
  2. Identifica qué murió (PID, comando, VMID/slice del contenedor).
  3. Captura el estado actual: free -h, swapon --show, PSI (/proc/pressure/memory).
  4. Lista los mayores consumidores (ps --sort=-rss) y mapea PIDs QEMU a VMIDs.
  5. Si el host está thrashing: reduce carga inmediatamente (migra/apaga huéspedes no críticos) en lugar de reiniciar daemons.

Checklist de estabilización (próximas 24 horas)

  1. Fija un objetivo de margen para el host (empieza en 20% RAM para cargas mixtas).
  2. Asegura que exista algo de swap; fija un swappiness razonable (evita extremos).
  3. Audita asignaciones de huéspedes: memoria asignada vs necesidad real; elimina “RAM de confort”.
  4. Para contenedores: establece memory y swap; rastrea memory.events para conteos OOM.
  5. Para VMs: decide dónde es aceptable el ballooning; verifica la efectividad del driver en esos guests.
  6. Si usas ZFS: mide ARC y pon un tope que respete el margen del hipervisor.

Checklist de hardening (siguiente sprint)

  1. Añade alertas sobre: memoria disponible baja en host, tendencia de uso de swap, PSI full avg60, eventos kernel OOM, conteos OOM en cgroups.
  2. Protege servicios críticos de Proxmox con OOMScoreAdjust y verifica políticas de systemd-oomd.
  3. Reduce concurrencia programada: backups, replicación, scrubs y trabajos batch intensivos.
  4. Documenta una política de overcommit: qué está permitido, qué no y cómo se mide.
  5. Ejecuta una prueba de carga controlada: simula picos de asignación y confirma que el host degrada gradualmente en lugar de matar QEMU.

FAQ

1) ¿Por qué Linux mata un proceso “aleatorio” en vez del que causó el problema?

No es aleatorio. El kernel calcula un puntaje de maldad basado en uso de memoria y otros factores. El “agresor” puede ser más pequeño que la “víctima más gorda”.
Por eso los límites por inquilino son tan valiosos: mantienen el radio de impacto local.

2) ¿Es normal que Proxmox funcione al 90% de uso de memoria?

“Used” es un número engañoso porque incluye caché. Lo que importa es available, la tendencia de swap y las señales de presión.
Un nodo puede verse “lleno” y estar sano, o verse “bien” y estar a un pico de OOM.

3) ¿Debería deshabilitar swap en Proxmox?

Normalmente no. Un swap pequeño es un buffer de seguridad. Si lo deshabilitas, eliges kills duros en lugar de ralentizaciones.
Si tienes requisitos estrictos de latencia, considera una estrategia de buffer alternativa (como zram) y acepta el riesgo explícitamente.

4) ¿ZFS ARC causa OOM?

ARC está diseñado para reducirse, pero aún puede contribuir a la presión si el sistema ya está overcommit o si los picos de carga son más rápidos de lo que ARC puede reducir.
Capping ARC es razonable en nodos cuyo rol principal es hipervisor.

5) ¿El ballooning de VM previene OOM?

Puede ayudar, pero no garantiza nada. Ballooning requiere cooperación del guest y memoria recuperable dentro del guest.
Si los guests están usando legítimamente su RAM, el ballooning no puede crear memoria gratis.

6) ¿Cómo sé si un OOM ocurrió dentro de un contenedor o en el host?

Los mensajes de OOM por contenedor/cgroup mencionan “Memory cgroup out of memory” y refieren una ruta de cgroup (como /lxc.payload.104.slice).
Los mensajes OOM globales del host se ven como “oom-killer: constraint=CONSTRAINT_NONE” y pueden matar QEMU o demonios del host.

7) ¿Cuál es la forma más segura de reducir riesgo de OOM rápidamente?

Añade un buffer de swap modesto, aplica límites a contenedores, detén el overcommit extremo y asegura margen para el host.
Si usas ZFS, limita ARC solo después de medir. Luego valida con PSI y el comportamiento durante ventanas de mantenimiento.

8) Si ajusto OOMScoreAdjust muy bajo para todo, ¿detendré los kills OOM?

Solo cambiarás quién muere—o empujarás al kernel a modos de fallo peores. Protege un pequeño conjunto de servicios críticos.
Deja el resto matable, y arregla la fuente real de presión con capacidad y límites.

9) ¿Por qué los OOM suelen ocurrir durante backups, scrubs o replicación?

Esas operaciones aumentan I/O, churn de metadata y actividad de compresión/cifrado. La caché del kernel y capas de filesystem se ponen ocupadas,
y QEMU puede asignar buffers transitorios. Si dimensionaste solo para estado estable, el mantenimiento se convierte en tu pico.

10) ¿Cuál es una ratio de overcommit “sensata” para memoria de VMs?

No hay un número universal. Empieza conservador: no excedas la RAM física menos la reserva del host con asignaciones “garantizadas” para VMs críticas.
Si overcommiteas, hazlo en huéspedes de baja prioridad, con ballooning donde corresponda y con monitorización que pruebe que es seguro.

Conclusión: pasos prácticos siguientes

Los eventos OOM-killer en Proxmox rara vez son misteriosos. Son la factura por decisiones de memoria que tomaste hace meses:
overcommit sin límites, ver el swap como un fallo moral y “ajustaremos ZFS después”.
El kernel cobra.

Haz lo siguiente, en este orden:

  1. Clasifica el evento (kernel OOM vs cgroup OOM vs systemd-oomd) usando las consultas de logs arriba.
  2. Reconstruye tu presupuesto: reserva del host, memoria asignada a huéspedes, buffer de swap, objetivo de ZFS ARC.
  3. Aplica límites en contenedores y adopta una política de dimensionamiento de VMs acorde a la criticidad de la carga.
  4. Añade observabilidad: alertas en PSI, memoria disponible, tendencia de swap y contadores OOM.
  5. Valida bajo carga de mantenimiento, no solo durante horas tranquilas.

El objetivo no es “nunca OOMear”. El objetivo es hacer la presión de memoria predecible, contenida y aburrida. En producción, aburrido es un cumplido.

← Anterior
Por qué los juegos quieren una CPU y el renderizado otra
Siguiente →
Punto de montaje ZFS: La trampa de montaje que hace ‘desaparecer’ los datasets

Deja un comentario