Cómo excluir un proceso del OOM killer

El kernel Linux tiene un sistema mediante el cual cuando el sistema está a punto de quedarse sin memoria disponible empieza a matar procesos para evitar el colapso del sistema. Esta función la realiza el OOM killer (Out Of Memory Killer).

Es una buena medida para evitar que el sistema se cuelgue por falta de memoria, pero también es un problema serio cuando tenemos aplicaciones críticas en ejecución y el OOM killer tiene la posibilidad de matarlas en cualquier momento. Sabremos que un proceso ha sido parado por el OOM Killer cuando veamos en el syslog/messages el siguiente mensaje:

host kernel: Out of Memory: Killed process 11355 (usuario).

Los valores máximos y mínimos de memoria (a través del comando free o /proc/meminfo) nos son de utilidad para tener una visión a tiempo real del estado de máximos y mínimos de memoria en uso:

~# free -lm
             total       used       free     shared    buffers     cached
Mem:          2004       1750        254          0         84        848
Low:           853        614        239
High:         1150       1135         14
-/+ buffers/cache:        818       1186
Swap:         2035          0       2035

Lo bueno es que podemos configurar el OOM Killer para que actúe de una forma u otra según el proceso. Este control se aplica a nivel independiente de cada PID a través del filesystem /proc. La puntuación que asignemos al proceso indicará al OOM killer como actuar con él en caso de problemas. Básicamente, a mayor score mayor prioridad para ser matado en caso de problemas.

Esta puntuación se asigna, para kernels con versión superior a 2.6.29 manualmente a través de:

/proc/[PID]/oom_score_adj

El rango de puntuación en este caso va de -1000 a 1000, siendo «-1000» (OOM_SCORE_ADJ_MIN) el valor que indica que el proceso nunca deberá ser matado por el OOM Killer y «1000» (OOM_SCORE_ADJ_MAX) el que indica que será el primero a matar en caso de problemas.

Y para kernels con versión inferior a 2.6.29:

/proc/[PID]/oom_adj

En este caso, el rango de puntuación va de «-17», valor que indica que el proceso nunca deberá ser matado por el OOM Killer y «15″ el que indica que será el primero a matar en caso de problemas.

En ambos casos, esto sucederá cuando el sistema esté con un porcentaje muy bajo de memoria disponible.

Si por ejemplo, quisieramos evitar que OOM killer matara el proceso ssh podríamos hacerlo del siguiente modo:

~# PID=$(pgrep sshd); echo "-1000" > /proc/${PID}/oom_score_adj 
~# cat /proc/${PID}/oom_score_adj 
-1000

Podríamos (o deberíamos) automatizarlo vía cron para que en caso de que el PID de SSH cambie o tras un reinicio la política se siguiera aplicando.

Además de por proceso, al ser una funcionalidad del Kernel también podemos definir a nivel global su comportamiento. Por ejemplo, los siguientes parámetros de sysctl definen que en caso de que se genere un OOM, el sistema genere un panic y se reinicie pasados 3 segundos:

sysctl -w vm.panic_on_oom=1
sysctl -w kernel.panic=3

Hay muchos parámetros de configuración y tuning para el OOM Killer, podéis verlos a continuación. Es importante el primero, «vm.overcommit_memory«, que define si vamos a permitir que el sistema o los procesos puedan reservar más memoria de la que realmente hay disponible en el sistema.

  • 0: valor por defecto en el que se rechazan peticiones de memoria superiores a la que el sistema tiene disponible. Se puede utilizar el 100% de la memoria física.
  • 1: se permiten peticiones de reserva de memoria superiores a la capacidad total.
  • 2: no se permiten peticiones de reserva de memoria superiores a la capacidad total. En este modo, a diferencia de en el «0», podemos asignar manualmente la memoria a utilizar a partir de la swap + el overcomit_ratio
# sysctl -a | grep "vm\."
 vm.overcommit_memory = 0
 vm.panic_on_oom = 0
 vm.oom_kill_allocating_task = 0
 vm.oom_dump_tasks = 1
 vm.overcommit_ratio = 50
 vm.page-cluster = 3
 vm.dirty_background_ratio = 5
 vm.dirty_background_bytes = 0
 vm.dirty_ratio = 10
 vm.dirty_bytes = 0
 vm.dirty_writeback_centisecs = 500
 vm.dirty_expire_centisecs = 3000
 vm.nr_pdflush_threads = 0
 vm.swappiness = 60
 vm.nr_hugepages = 0
 vm.hugetlb_shm_group = 0
 vm.hugepages_treat_as_movable = 0
 vm.nr_overcommit_hugepages = 0
 vm.lowmem_reserve_ratio = 256 32 32
 vm.drop_caches = 0
 vm.extfrag_threshold = 500
 vm.min_free_kbytes = 44800
 vm.percpu_pagelist_fraction = 0
 vm.max_map_count = 65530
 vm.laptop_mode = 0
 vm.block_dump = 0
 vm.vfs_cache_pressure = 100
 vm.legacy_va_layout = 0
 vm.stat_interval = 1
 vm.mmap_min_addr = 65536
 vm.vdso_enabled = 1
 vm.highmem_is_dirtyable = 0
 vm.scan_unevictable_pages = 0
 vm.memory_failure_early_kill = 0
 vm.memory_failure_recovery = 1