Почему приложения в контейнере LXC с ограниченной памятью записывают большие файлы на диск, уничтожаемые OOM?

10

РЕДАКТИРОВАТЬ 2: Эта проблема, кажется, также существует в 3.8.0-25-generic # 37-Ubuntu SMP

РЕДАКТИРОВАТЬ: я изменил вопрос из первоначального заголовка «Почему диспетчер Linux Out of Memory запускается при записи в файл с dd?» чтобы лучше отразить, что меня беспокоит общая проблема, описанная ниже:

Я сталкиваюсь с неприятным сценарием, когда OOM killer жестко уничтожает процессы в моем контейнере LXC, когда я пишу файл с размером, превышающим ограничение памяти (установлено на 300 МБ). Эта проблема не возникает, когда я запускаю приложение на виртуальной машине Xen (EC2 t1.micro), которая на самом деле имеет только 512 МБ ОЗУ, поэтому, похоже, существует некоторая проблема с буферизацией файла в отношении ограничения памяти контейнеров.

В качестве простого примера я могу продемонстрировать, как большой файл, написанный dd, может вызвать проблемы. Опять же, эта проблема мучает все приложения. Я ищу, чтобы решить общую проблему кеша приложения становится слишком большим; Я понимаю, как я могу заставить "дд" работать.

Сценарий:

У меня есть контейнер LXC, где memory.limit_in_bytes установлен на 300 МБ.

Я пытаюсь создать файл размером ~ 500 МБ следующим образом:

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

Примерно в 20% случаев менеджер OOM в Linux запускается этой командой, и процесс завершается. Излишне говорить, что это крайне непреднамеренное поведение; dd предназначен для имитации фактической «полезной» записи файла программой, выполняющейся внутри контейнера.

Детали: Хотя файловые кеши становятся большими (260 МБ), rss и карта файлов, похоже, остаются довольно низкими. Вот пример того, как может выглядеть memory.stat во время записи:

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

Вот паста из dmesg, где OOM вызвал убийство. Я не слишком знаком с различиями между типами памяти; Одна вещь, которая выделяется, это то, что, хотя «Node 0 Normal» очень низок, есть много свободной памяти Node 0 DMA32. Может кто-нибудь объяснить, почему запись файла вызывает OOM? Как я могу предотвратить это?

Журнал:

[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

Я работаю на Linux ip-10-8-139-98 3.2.0-29-virtual # 46-Ubuntu SMP Пт 27 июля 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux на Amazon EC2.

UsAaR33
источник
1
В качестве краткого изложения для всех, кто его читает, это ошибка ядра Linux
UsAaR33

Ответы:

13

Изменить: я оставлю свой оригинальный ответ ниже, но я постараюсь объяснить, что здесь происходит, и предоставить общее решение для вас.

Редактировать 2: Предоставляется еще один вариант.

Проблема, с которой вы столкнулись, связана с тем, как ядро ​​управляет вводом / выводом. Когда вы делаете запись в вашу файловую систему, эта запись не сразу записывается на диск; это было бы невероятно неэффективно. Вместо этого записи кэшируются в области памяти, называемой кэшем страниц, и периодически записываются частями на диск. «Грязный» раздел вашего журнала описывает размер этого кэша страниц, который еще не был записан на диск:

dirty:123816kB

Так что же очищает этот грязный кеш? Почему он не делает свою работу?

Flush в Linux отвечает за запись грязных страниц на диск. Это демон, который периодически просыпается, чтобы определить, требуются ли записи на диск, и, если это так, выполняет их. Если вы парень типа С, начните здесь . Флеш невероятно эффективен; он отлично справляется с записью файлов на диск при необходимости. И это работает именно так, как это должно быть.

Flush запускается за пределами вашего контейнера LXC, поскольку у вашего контейнера LXC нет собственного ядра. Контейнеры LXC существуют в виде конструкции вокруг cgroups , которая является функцией ядра Linux, которая допускает лучшие ограничения и изоляцию групп процессов, но не своего собственного ядра или демона flush.

Поскольку у вашего LXC лимит памяти меньше, чем у ядра, то происходят странные вещи. Flush предполагает, что у него есть полная память хоста для записи в кеш. Программа в вашем LXC начинает записывать большой файл, она буферизует ... буферизирует ... и в конечном итоге достигает жесткого ограничения и начинает вызывать менеджер OOM. Это не сбой какого-либо конкретного компонента; это ожидаемое поведение. Что-то вроде. Подобные вещи должны обрабатываться cgroups, но не похоже, что это так.

Это полностью объясняет поведение, которое вы видите между размерами экземпляров. Вы начнете сбрасывать на диск гораздо раньше на микроэкземпляре (с 512 МБ ОЗУ) по сравнению с большим экземпляром

Хорошо, это имеет смысл. Но это бесполезно. Мне все еще нужно написать мне файл с большой задницей.

Ну, flush не знает о вашем лимите LXC. Так что вместо исправления ядра здесь есть несколько вариантов вещей, которые вы можете попробовать настроить:

/proc/sys/vm/dirty_expire_centiseconds

Это контролирует, как долго страница может храниться в грязном кэше и записываться на диск. По умолчанию это 30 секунд; попытайтесь установить это ниже, чтобы начать выдвигать это быстрее.

/proc/sys/vm/dirty_background_ratio

Этот параметр определяет, какой процент активной очистки памяти может заполняться до того, как он начнет форсировать запись. Здесь есть некоторая путаница, которая приводит к точному определению общей суммы , но самое простое объяснение - просто посмотреть на вашу общую память. По умолчанию это 10% (в некоторых дистрибутивах это 5%). Установите это ниже; это заставит записывать на диск раньше и может помешать вашему LXC выйти за пределы.

Разве я не могу немного поковыряться с файловой системой?

Ну, да. Но убедитесь, что вы проверили это ... вы можете повлиять на производительность. На ваших монтировках в / etc / fstab, где вы будете писать это, добавьте опцию ' sync ' монтирования.

Оригинальный ответ:

Попробуйте уменьшить размер блока, используемый DD:

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

За один раз можно записать только один сектор (512 байт на старых жестких дисках, 4096 на новых). Если DD отправляет записи на диск быстрее, чем диск может их принять, он начнет кэшировать записи в памяти. Вот почему ваш файловый кеш растет.

alexphilipp
источник
Я должен отметить, что если я запускаю аналогичные тесты в python, где я вручную сбрасываю объект файла, ошибка все равно происходит с аналогичной вероятностью. Кеш, конечно, растет, но это нужно очистить, а не убиваемый процесс.
UsAaR33
1
Я бы все равно попробовал. Я обнаружил, что форсирование fsync () с Python не всегда делает то, что вы ожидаете.
Александр
1
@ UsAaR33 Получите более быстрый диск.
Тинк
1
@ UsAaR33 Приложение будет писать как можно быстрее; он ожидает, что ядро ​​будет обрабатывать ввод-вывод. Я не использовал контейнер LXC раньше, но на первый взгляд кажется, что он не предоставляет свое собственное ядро ​​в создаваемом им chroot? Если это так, ядро ​​обеспечивает ввод-вывод, предполагая, что оно имеет полную доступную память хост-системы. Он понятия не имеет, что вы ограничиваете его до 300 МБ. Как только этот предел достигнут, ООМ начинает убивать процессы.
Александр
1
@ UsAaR33: плохие настройки приводят к плохим результатам. Одной части системы говорят, что в качестве кеша можно использовать много памяти, а другой части системы - уничтожать процессы, если кеш слишком большой. Зачем ждать диска, когда доступно много оперативной памяти? И если доступно много оперативной памяти, почему бы не позволить ей использовать ее?
Дэвид Шварц
3

Ваш файл записывает в / tmp? Если это так, он может быть не в реальной файловой системе, а на диске. Таким образом, когда вы пишете в него, все больше и больше памяти отнимается для удовлетворения потребностей файла. В конце концов, вам не хватает памяти + пространство подкачки, и ваша производительность падает до такой степени, что вы разочаровываетесь.

Якорь,
источник
Он записывает в $ HOME, который находится на монтировании AUFS, которое запускает запись на базовый диск. (EC2 EBS)
UsAaR33
2

если вы не записываете на RAM-диск, вы можете избежать кэширования, используя oflag = direct

dd if=/dev/zero of=test2 bs=100k oflag=direct count=5010
Кевин Паркер
источник
direct вызывает ошибку «Неверный аргумент», но использование oflag = dsync работает.
UsAaR33
Мне очень жаль, если это не сработало для вас, в соответствии с man-страницей «прямое использование прямого ввода-вывода для данных»
Кевин Паркер