¿Por qué la OOM mata las aplicaciones en un contenedor LXC con memoria limitada que escribe archivos grandes en el disco?

10

EDIT2: este problema parece existir también en 3.8.0-25-generic # 37-Ubuntu SMP

EDITAR: Modifiqué la pregunta del título original de "¿Por qué se activaría el administrador de Linux Out of Memory al escribir en un archivo con dd?" para reflejar mejor que estoy preocupado por el problema general que se describe a continuación:

Me encuentro con un escenario problemático en el que el asesino de OOM está matando procesos en mi contenedor LXC cuando escribo un archivo con un tamaño que excede la limitación de memoria (establecido en 300 MB). El problema no ocurre cuando ejecuto la aplicación en una máquina virtual Xen (una EC2 t1.micro) que en realidad solo tiene 512 MB de RAM, por lo que parece haber algún problema con el almacenamiento en búfer del archivo respetando el límite de memoria de los contenedores.

Como un simple ejemplo, puedo demostrar cómo un archivo grande escrito por dd causará problemas. Nuevamente, este problema afecta a todas las aplicaciones. Estoy buscando resolver el problema general de que el caché de la aplicación se hace demasiado grande; Entiendo cómo puedo hacer que el "dd" funcione.

Guión:

Tengo un contenedor LXC donde memory.limit_in_bytes está configurado en 300 MB.

Intento crear un archivo de ~ 500 MB de la siguiente manera:

dd if=/dev/zero of=test2 bs=100k count=5010

Aproximadamente el 20% del tiempo, el comando OOM de Linux se activa mediante este comando y se elimina un proceso. No hace falta decir que este es un comportamiento muy involuntario; dd está destinado a simular una escritura de archivo "útil" real por un programa que se ejecuta dentro del contenedor.

Detalles: Si bien las cachés de archivos se hacen grandes (260 MB), rss y el mapa de archivos parecen mantenerse bastante bajos. Aquí hay un ejemplo de cómo se verá memory.stat durante la escritura:

cache 278667264
rss 20971520
mapped_file 24576
pgpgin 138147
pgpgout 64993
swap 0
pgfault 55054
pgmajfault 2
inactive_anon 10637312
active_anon 10342400
inactive_file 278339584
active_file 319488
unevictable 0
hierarchical_memory_limit 300003328
hierarchical_memsw_limit 300003328
total_cache 278667264
total_rss 20971520
total_mapped_file 24576
total_pgpgin 138147
total_pgpgout 64993
total_swap 0
total_pgfault 55054
total_pgmajfault 2
total_inactive_anon 10637312
total_active_anon 10342400
total_inactive_file 278339584
total_active_file 319488
total_unevictable 0

Aquí hay una pasta de dmesg donde la OOM provocó una muerte. No estoy muy familiarizado con las distinciones entre los tipos de memoria; Una cosa que destaca es que si bien "Nodo 0 Normal" es muy bajo, hay mucha memoria libre de Nodo 0 DMA32. ¿Alguien puede explicar por qué una escritura de archivo está causando el OOM? ¿Cómo evito que esto suceda?

El registro:

[1801523.686755] Task in /lxc/c-7 killed as a result of limit of /lxc/c-7
[1801523.686758] memory: usage 292972kB, limit 292972kB, failcnt 39580
[1801523.686760] memory+swap: usage 292972kB, limit 292972kB, failcnt 0
[1801523.686762] Mem-Info:
[1801523.686764] Node 0 DMA per-cpu:
[1801523.686767] CPU    0: hi:    0, btch:   1 usd:   0
[1801523.686769] CPU    1: hi:    0, btch:   1 usd:   0
[1801523.686771] CPU    2: hi:    0, btch:   1 usd:   0
[1801523.686773] CPU    3: hi:    0, btch:   1 usd:   0
[1801523.686775] CPU    4: hi:    0, btch:   1 usd:   0
[1801523.686778] CPU    5: hi:    0, btch:   1 usd:   0
[1801523.686780] CPU    6: hi:    0, btch:   1 usd:   0
[1801523.686782] CPU    7: hi:    0, btch:   1 usd:   0
[1801523.686783] Node 0 DMA32 per-cpu:
[1801523.686786] CPU    0: hi:  186, btch:  31 usd: 158
[1801523.686788] CPU    1: hi:  186, btch:  31 usd: 114
[1801523.686790] CPU    2: hi:  186, btch:  31 usd: 133
[1801523.686792] CPU    3: hi:  186, btch:  31 usd:  69
[1801523.686794] CPU    4: hi:  186, btch:  31 usd:  70
[1801523.686796] CPU    5: hi:  186, btch:  31 usd: 131
[1801523.686798] CPU    6: hi:  186, btch:  31 usd: 169
[1801523.686800] CPU    7: hi:  186, btch:  31 usd:  30
[1801523.686802] Node 0 Normal per-cpu:
[1801523.686804] CPU    0: hi:  186, btch:  31 usd: 162
[1801523.686806] CPU    1: hi:  186, btch:  31 usd: 184
[1801523.686809] CPU    2: hi:  186, btch:  31 usd:  99
[1801523.686811] CPU    3: hi:  186, btch:  31 usd:  82
[1801523.686813] CPU    4: hi:  186, btch:  31 usd:  90
[1801523.686815] CPU    5: hi:  186, btch:  31 usd:  99
[1801523.686817] CPU    6: hi:  186, btch:  31 usd: 157
[1801523.686819] CPU    7: hi:  186, btch:  31 usd: 138
[1801523.686824] active_anon:60439 inactive_anon:28841 isolated_anon:0
[1801523.686825]  active_file:110417 inactive_file:907078 isolated_file:64
[1801523.686827]  unevictable:0 dirty:164722 writeback:1652 unstable:0
[1801523.686828]  free:445909 slab_reclaimable:176594
slab_unreclaimable:14754
[1801523.686829]  mapped:4753 shmem:66 pagetables:3600 bounce:0
[1801523.686831] Node 0 DMA free:7904kB min:8kB low:8kB high:12kB
active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB
unevictable:0kB isolated(anon):0kB isolated(file):0kB present:7648kB
mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB
slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB
unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0
all_unreclaimable? no
[1801523.686841] lowmem_reserve[]: 0 4016 7048 7048
[1801523.686845] Node 0 DMA32 free:1770072kB min:6116kB low:7644kB
high:9172kB active_anon:22312kB inactive_anon:12128kB active_file:4988kB
inactive_file:2190136kB unevictable:0kB isolated(anon):0kB
isolated(file):256kB present:4112640kB mlocked:0kB dirty:535072kB
writeback:6452kB mapped:4kB shmem:4kB slab_reclaimable:72888kB
slab_unreclaimable:1100kB kernel_stack:120kB pagetables:832kB unstable:0kB
bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
[1801523.686855] lowmem_reserve[]: 0 0 3031 3031
[1801523.686859] Node 0 Normal free:5660kB min:4616kB low:5768kB
high:6924kB active_anon:219444kB inactive_anon:103236kB
active_file:436680kB inactive_file:1438176kB unevictable:0kB
isolated(anon):0kB isolated(file):0kB present:3104640kB mlocked:0kB
dirty:123816kB writeback:156kB mapped:19008kB shmem:260kB
slab_reclaimable:633488kB slab_unreclaimable:57916kB kernel_stack:2800kB
pagetables:13568kB unstable:0kB bounce:0kB writeback_tmp:0kB
pages_scanned:0 all_unreclaimable? no
[1801523.686869] lowmem_reserve[]: 0 0 0 0
[1801523.686873] Node 0 DMA: 2*4kB 3*8kB 0*16kB 2*32kB 4*64kB 3*128kB
2*256kB 1*512kB 2*1024kB 2*2048kB 0*4096kB = 7904kB
[1801523.686883] Node 0 DMA32: 129*4kB 87*8kB 86*16kB 89*32kB 87*64kB
65*128kB 12*256kB 5*512kB 2*1024kB 13*2048kB 419*4096kB = 1769852kB
[1801523.686893] Node 0 Normal: 477*4kB 23*8kB 1*16kB 5*32kB 0*64kB 3*128kB
3*256kB 1*512kB 0*1024kB 1*2048kB 0*4096kB = 5980kB
[1801523.686903] 1017542 total pagecache pages
[1801523.686905] 0 pages in swap cache
[1801523.686907] Swap cache stats: add 0, delete 0, find 0/0
[1801523.686908] Free swap  = 1048572kB
[1801523.686910] Total swap = 1048572kB
[1801523.722319] 1837040 pages RAM
[1801523.722322] 58337 pages reserved
[1801523.722323] 972948 pages shared
[1801523.722324] 406948 pages non-shared
[1801523.722326] [ pid ]   uid  tgid total_vm      rss cpu oom_adj
oom_score_adj name
[1801523.722396] [31266]     0 31266     6404      511   6       0
    0 init
[1801523.722445] [32489]     0 32489    12370      688   7     -17
-1000 sshd
[1801523.722460] [32511]   101 32511    10513      325   0       0
    0 rsyslogd
[1801523.722495] [32625]     0 32625    17706      838   2       0
    0 sshd
[1801523.722522] [32652]   103 32652     5900      176   0       0
    0 dbus-daemon
[1801523.722583] [  526]     0   526     1553      168   5       0
    0 getty
[1801523.722587] [  530]     0   530     1553      168   1       0
    0 getty
[1801523.722593] [  537]  2007   537    17706      423   5       0
    0 sshd
[1801523.722629] [  538]  2007   538    16974     5191   1       0
    0 python
[1801523.722650] [  877]  2007   877     2106      157   7       0
    0 dd
[1801523.722657] Memory cgroup out of memory: Kill process 538 (python)
score 71 or sacrifice child
[1801523.722674] Killed process 538 (python) total-vm:67896kB,
anon-rss:17464kB, file-rss:3300kB

Me estoy ejecutando en Linux ip-10-8-139-98 3.2.0-29-virtual # 46-Ubuntu SMP vie 27 de julio 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux en Amazon EC2.

UsAaR33
fuente
1
Como un resumen rápido para todos los que lo leen, este es un error del kernel de Linux
UsAaR33

Respuestas:

13

Editar: mantendré mi respuesta original a continuación, pero intentaré explicar lo que está sucediendo aquí y proporcionarle una solución general.

Edición 2: Proporcionó otra opción.

El problema que está enfrentando aquí tiene que ver con cómo el núcleo gestiona las E / S. Cuando realiza una escritura en su sistema de archivos, esa escritura no se confirma inmediatamente en el disco; eso sería increíblemente ineficiente. En cambio, las escrituras se almacenan en caché en un área de memoria denominada caché de página y se escriben periódicamente en fragmentos en el disco. La sección "sucia" de su registro describe el tamaño de este caché de página que aún no se ha escrito en el disco:

dirty:123816kB

Entonces, ¿qué vacía esta caché sucia? ¿Por qué no está haciendo su trabajo?

'Flush' en Linux es responsable de escribir páginas sucias en el disco. Es un demonio que se activa periódicamente para determinar si se requieren escrituras en el disco y, de ser así, las realiza. Si eres un tipo C, comienza aquí . Flush es increíblemente eficiente; hace un gran trabajo al descargar cosas al disco cuando es necesario. Y está funcionando exactamente como se supone que debe hacerlo.

Flush se ejecuta fuera de su contenedor LXC, ya que su contenedor LXC no tiene su propio núcleo. Los contenedores LXC existen como una construcción alrededor de cgroups , que es una característica del kernel de Linux que permite mejores limitaciones y aislamiento de los grupos de procesos, pero no su propio kernel o demonio de descarga.

Como su LXC tiene un límite de memoria inferior a la memoria que tiene disponible el kernel, suceden cosas extrañas. Flush asume que tiene la memoria completa del host para guardar en caché las escrituras. Un programa en su LXC comienza a escribir un archivo grande, almacena en memoria intermedia ... almacena en memoria intermedia ... y finalmente alcanza su límite difícil, y comienza a llamar al administrador de OOM. Esto no es una falla de ningún componente en particular; Es comportamiento esperado. Mas o menos. Este tipo de cosas debería ser manejado por cgroups, pero no parece que lo sea.

Esto explica completamente el comportamiento que ves entre los tamaños de instancia. Comenzará a descargarse en el disco mucho antes en la microinstancia (con 512 MB de RAM) frente a una instancia grande

Ok, eso tiene sentido. Pero es inútil. Todavía necesito escribirme un archivo grande.

Bueno, flush no conoce tu límite de LXC. Entonces, en lugar de parchear el kernel, hay algunas opciones aquí para cosas que puede intentar ajustar:

/proc/sys/vm/dirty_expire_centiseconds

Esto controla cuánto tiempo puede mantenerse una página en el caché sucio y escribirse en el disco. Por defecto son 30 segundos; intente configurarlo más bajo para comenzar a empujarlo más rápido.

/proc/sys/vm/dirty_background_ratio

Esto controla qué porcentaje de vaciado de memoria activa se puede llenar antes de que comience a forzar escrituras. Hay un poco de violín que implica clasificar el total exacto aquí, pero la explicación más fácil es simplemente mirar su memoria total. Por defecto es 10% (en algunas distribuciones es 5%). Establezca esto más bajo; forzará las escrituras en el disco antes y puede evitar que su LXC se quede sin límites.

¿No puedo jugar un poco con el sistema de archivos?

Bueno sí. Pero asegúrese de probar esto ... podría afectar el rendimiento. En sus montajes en / etc / fstab donde escribirá esto, agregue la opción de montaje ' sincronizar '.

Respuesta original:

Intente reducir el tamaño de bloque utilizado por DD:

dd if=/dev/zero of=test2 bs=512 count=1024000

Solo puede escribir un sector a la vez (512 bytes en discos duros más antiguos, 4096 en los más nuevos). Si DD está empujando las escrituras al disco más rápido de lo que el disco puede aceptarlas, comenzará a almacenar en caché las escrituras en la memoria. Es por eso que su caché de archivos está creciendo.

alexphilipp
fuente
Debo tener en cuenta que si ejecuto pruebas similares en python donde elimino manualmente el objeto del archivo, el error aún ocurre con una probabilidad similar. El caché crece, por supuesto, pero eso debería ser purgado, uno pensaría en lugar de matar el proceso.
UsAaR33
1
Le daría una oportunidad de todos modos. Descubrí que forzar un fsync () con Python no siempre hace lo que esperas.
alexphilipp
1
@ UsAaR33 Obtenga un disco más rápido.
Tink
1
@ UsAaR33 Una aplicación escribirá tan rápido como pueda; espera que el núcleo maneje IO. No he usado un contenedor LXC antes, pero a simple vista parece que no proporciona su propio núcleo en el chroot que crea. Si ese es el caso, el núcleo proporciona a IO la suposición de que tiene disponible la memoria completa del sistema host. No tiene idea de que su calificación se limita a 300 MB. Una vez que se alcanza ese límite, OOM comienza a matar procesos.
alexphilipp
1
@ UsAaR33: la configuración incorrecta causa malos resultados. Se le dice a una parte del sistema que se puede usar mucha memoria como caché, a otra parte del sistema se le dice que elimine procesos si el caché es demasiado grande. ¿Por qué debería esperar al disco cuando hay suficiente RAM disponible? Y si hay suficiente RAM disponible, ¿por qué no dejar que la use?
David Schwartz
3

¿Su archivo está escribiendo en / tmp? Si es así, podría no estar en un sistema de archivos real sino residente en el disco. Por lo tanto, a medida que escribe, se le quita más y más memoria para satisfacer las necesidades del archivo. Eventualmente, se queda sin memoria + espacio de intercambio y su rendimiento se deteriora hasta el punto de la frustración total.

mdpc
fuente
Está escribiendo en $ HOME, que está en un montaje AUFS que desencadena escrituras en el disco subyacente. (EC2 EBS)
UsAaR33
2

a menos que esté escribiendo en el disco RAM, puede evitar el almacenamiento en caché utilizando oflag = direct

dd if=/dev/zero of=test2 bs=100k oflag=direct count=5010
Kevin Parker
fuente
direct provoca un error de "argumento no válido", pero el uso de oflag = dsync funciona.
UsAaR33
lo siento si no funcionó para usted, según la página de manual "uso directo de E / S directa para datos"
Kevin Parker