Dlaczego aplikacje w kontenerze LXC o ograniczonej pamięci zapisujące duże pliki na dysku są zabijane przez OOM?


10

EDYCJA 2: Wydaje się, że ten problem istnieje również pod SMP 3.8.0-25-generic # 37-Ubuntu

EDYCJA: Zmodyfikowałem pytanie z pierwotnego tytułu „Dlaczego menedżer Linux Out of Memory miałby być wyzwalany przez zapis do pliku z dd?” aby lepiej odzwierciedlić, że martwię się o ogólny problem opisany poniżej:

Mam do czynienia z kłopotliwym scenariuszem, w którym zabójca OOM mocno zabija procesy w moim kontenerze LXC, gdy piszę plik o rozmiarze przekraczającym ograniczenie pamięci (ustawione na 300 MB). Problem nie występuje, gdy uruchamiam aplikację na maszynie wirtualnej Xen (EC2 t1.micro), która faktycznie ma tylko 512 MB pamięci RAM, więc wydaje się, że jest jakiś problem z buforowaniem plików z uwzględnieniem limitu pamięci kontenerów.

Jako prosty przykład mogę wykazać, w jaki sposób duży plik napisany przez dd spowoduje problemy. Ponownie ten problem plaguje wszystkie aplikacje. Szukam rozwiązania ogólnego problemu zbyt dużej pamięci podręcznej aplikacji; Rozumiem, jak mogę sprawić, by „dd” działało.

Scenariusz:

Mam kontener LXC, w którym memory.limit_in_bytes jest ustawiony na 300 MB.

Próbuję dodać plik ~ 500 MB w następujący sposób:

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

Mniej więcej w 20% przypadków menedżer Linux OOM jest uruchamiany przez to polecenie i proces zostaje zabity. Nie trzeba dodawać, że jest to bardzo niezamierzone zachowanie; dd służy do symulacji rzeczywistego „przydatnego” zapisu pliku przez program działający w kontenerze.

Szczegóły: Podczas gdy pamięci podręczne plików stają się duże (260 MB), rss i mapa plików wydają się być dość niskie. Oto przykład, jak może wyglądać memory.stat podczas zapisu:

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

Oto pasta z dmesg, w której OOM spowodował zabójstwo. Nie znam zbytnio różnic między rodzajami pamięci; jedną rzeczą, która się wyróżnia, jest to, że chociaż „Node 0 Normal” jest bardzo niski, jest dużo wolnej pamięci Node 0 DMA32. Czy ktoś może wyjaśnić, dlaczego zapis pliku powoduje OOM? Jak temu zapobiec?

Dziennik:

[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

Pracuję na systemie Linux ip-10-8-139-98 3.2.0-29-virtual # 46-Ubuntu SMP Pt 27 lipca 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux na Amazon EC2.


1
Jako krótkie podsumowanie dla wszystkich, którzy go czytają, jest to błąd jądra systemu Linux
UsAaR33,

Odpowiedzi:


13

Edycja: zachowam moją oryginalną odpowiedź poniżej, ale postaram się wyjaśnić, co się tutaj dzieje i przedstawię ogólne rozwiązanie.

Edycja 2: Podano inną opcję.

Problem, który tu trafiasz, dotyczy sposobu, w jaki jądro zarządza I / O. Podczas zapisu w systemie plików zapis nie jest natychmiast zapisywany na dysku; byłoby to niezwykle nieefektywne. Zamiast tego zapisy są buforowane w obszarze pamięci zwanym pamięcią podręczną strony i okresowo zapisywane w częściach na dysk. Sekcja „brudna” dziennika opisuje rozmiar tej pamięci podręcznej strony, która nie została jeszcze zapisana na dysku:

dirty:123816kB

Co więc opróżnia tę brudną pamięć podręczną? Dlaczego to nie działa?

„Flush” w systemie Linux jest odpowiedzialny za zapisywanie brudnych stron na dysku. Jest to demon, który budzi się okresowo, aby ustalić, czy wymagane są zapisy na dysk, a jeśli tak, to je wykonuje. Jeśli jesteś facetem typu C, zacznij tutaj . Spłukiwanie jest niezwykle wydajne; robi świetną robotę opróżniając pliki na dysk w razie potrzeby. I działa dokładnie tak, jak powinno.

Opróżnianie działa poza kontenerem LXC, ponieważ kontener LXC nie ma własnego jądra. Kontenery LXC istnieją jako konstrukcja wokół cgroups , która jest cechą jądra Linuksa, która pozwala na lepsze ograniczenia i izolację grup procesów, ale nie ma własnego jądra ani demona flush.

Ponieważ twój LXC ma limit pamięci niższy niż pamięć dostępna dla jądra, dzieją się dziwne rzeczy. Flush zakłada, że ​​ma pełną pamięć hosta do zapisywania w pamięci podręcznej. Program w twoim LXC zaczyna pisać duży plik, buforuje ... buforuje ... i ostatecznie osiąga swój twardy limit i zaczyna dzwonić do menedżera OOM. To nie jest awaria żadnego konkretnego komponentu; to oczekiwane zachowanie. Rodzaj. Tego rodzaju sprawy powinny być obsługiwane przez grupy, ale nie wydaje się, że tak jest.

To całkowicie wyjaśnia zachowanie widoczne między rozmiarami instancji. Opróżnianie dysku rozpocznie się znacznie wcześniej w przypadku mikro instancji (z 512 MB pamięci RAM) w porównaniu do dużej instancji

Ok, to ma sens. Ale to bezużyteczne. Nadal muszę napisać do mnie duży plik.

Cóż, kolor nie jest świadomy twojego limitu LXC. Zamiast łatać jądro, istnieje kilka opcji, które możesz spróbować ulepszyć:

/proc/sys/vm/dirty_expire_centiseconds

Kontroluje, jak długo strona może być przechowywana w brudnej pamięci podręcznej i zapisywana na dysku. Domyślnie jest to 30 sekund; spróbuj obniżyć, aby szybciej wypychać.

/proc/sys/vm/dirty_background_ratio

Kontroluje, jaki procent aktywnego opróżnienia pamięci może się zapełnić, zanim zacznie wymuszać zapisy. Jest trochę majstrowania, które polega na ustaleniu dokładnej sumy tutaj, ale najłatwiejszym wyjaśnieniem jest po prostu spojrzenie na całkowitą pamięć. Domyślnie jest to 10% (w niektórych dystrybucjach to 5%). Ustaw to niżej; wymusi wcześniejsze zapisywanie na dysku i może uniemożliwić LXC wyczerpanie limitów.

Czy nie mogę po prostu trochę wkręcić systemu plików?

No tak. Ale upewnij się, że to przetestowałeś. Możesz wpłynąć na wydajność. Na swoich montowaniach w / etc / fstab, do których będziesz to pisać, dodaj opcję montowania „ synchronizuj ”.

Oryginalna odpowiedź:

Spróbuj zmniejszyć rozmiar bloku używany przez DD:

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

Możesz zapisać tylko jeden sektor na raz (512 bajtów na starszych dyskach twardych, 4096 na nowszych). Jeśli DD wypycha zapisy na dysk szybciej niż dysk może je zaakceptować, rozpocznie buforowanie zapisów w pamięci. Dlatego pamięć podręczna plików rośnie.


Powinienem zauważyć, że jeśli uruchomię podobne testy w pythonie, gdzie ręcznie opróżnię obiekt pliku, błąd nadal występuje z podobnym prawdopodobieństwem. Pamięć podręczna oczywiście rośnie, ale należy ją oczyścić, niż można by sądzić, że proces zostanie zabity.
UsAaR33

1
I tak dałbym mu szansę. Przekonałem się, że wymuszanie fsync () w Pythonie nie zawsze robi to, czego oczekujesz.
alexphilipp

1
@ UsAaR33 Uzyskaj szybszy dysk.
zadzwonić

1
@ UsAaR33 Aplikacja napisze jak najszybciej; oczekuje, że jądro poradzi sobie z IO. Nie korzystałem wcześniej z kontenera LXC, ale na pierwszy rzut oka wygląda na to, że nie zapewnia własnego jądra w tworzonym chroocie? W takim przypadku jądro zapewnia IO założenie, że ma pełną pamięć systemu hosta. Nie ma pojęcia, że ​​oceniasz to ograniczenie do 300 MB. Po osiągnięciu tego limitu OOM rozpoczyna procesy zabijania.
alexphilipp

1
@ UsAaR33: Złe ustawienia powodują złe wyniki. Mówi się, że jedna część systemu może służyć jako pamięć podręczna, a inna część systemu ma zabijać procesy, jeśli pamięć podręczna jest zbyt duża. Dlaczego powinien czekać na dysk, gdy jest dużo dostępnej pamięci RAM? A jeśli jest dużo dostępnej pamięci RAM, dlaczego jej nie wykorzystać?
David Schwartz

3

Czy twój plik zapisuje do / tmp? Jeśli tak, może nie być w rzeczywistym systemie plików, ale rezyduje na dysku. Tak więc, gdy piszesz do niego, coraz więcej pamięci jest odbierane zgodnie z potrzebami pliku. W końcu kończy Ci się pamięć i przestrzeń wymiany, a wydajność spada do poziomu całkowitej frustracji.


Zapisuje do $ HOME, który jest na wierzchowcu AUFS, który wyzwala zapis na dysku bazowym. (EC2 EBS)
UsAaR33

2

chyba że piszesz na dysk RAM, możesz uniknąć buforowania, używając oflag = direct

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

direct powoduje błąd „Nieprawidłowy argument”, ale użycie oflag = dsync działa.
UsAaR33

przepraszam, jeśli to nie zadziałało, jak na stronie podręcznika „bezpośrednie użycie bezpośredniego wejścia / wyjścia dla danych”
Kevin Parker,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.