Perché le applicazioni in un contenitore LXC a memoria limitata che scrivono file di grandi dimensioni su disco vengono uccise da OOM?


10

EDIT2: questo problema sembra esistere anche in 3.8.0-25-generico # 37-Ubuntu SMP

EDIT: ho modificato la domanda dal titolo originale di "Perché il gestore di memoria esaurita di Linux dovrebbe essere attivato scrivendo in un file con dd?" per riflettere meglio che sono preoccupato per il problema generale descritto di seguito:

Sto correndo in uno scenario problematico in cui il killer OOM sta eseguendo dei processi difficili nel mio contenitore LXC quando scrivo un file con dimensioni che superano il limite di memoria (impostato su 300 MB). Il problema non si verifica quando eseguo l'applicazione su una macchina virtuale Xen (un EC2 t1.micro) che in realtà ha solo 512 MB di RAM, quindi sembra che ci sia qualche problema con il buffering dei file nel rispetto del limite di memoria dei contenitori.

Come semplice esempio, posso dimostrare come un grosso file scritto da dd causi problemi. Ancora una volta, questo problema affligge tutte le applicazioni. Sto cercando di risolvere il problema generale della cache dell'applicazione che diventa troppo grande; Capisco come posso far funzionare il "dd".

Scenario:

Ho un contenitore LXC in cui memory.limit_in_bytes è impostato su 300 MB.

Tento di creare un file di ~ 500 MB come segue:

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

Circa il 20% delle volte, il gestore OOM di Linux viene attivato da questo comando e un processo viene interrotto. Inutile dire che si tratta di un comportamento altamente indesiderato; dd ha lo scopo di simulare un vero e proprio "utile" file di scrittura da un programma in esecuzione all'interno del contenitore.

Dettagli: Mentre le cache dei file diventano grandi (260 MB), rss e la mappa dei file sembrano rimanere abbastanza bassi. Ecco un esempio di come potrebbe apparire memory.stat durante la scrittura:

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

Ecco una copia di dmesg in cui l'OOM ha innescato un'uccisione. Non ho troppa familiarità con le distinzioni tra i tipi di memoria; una cosa che spicca è che mentre "Nodo 0 Normale" è molto basso, c'è molta memoria DMA32 Nodo 0 libera. Qualcuno può spiegare perché un file di scrittura sta causando l'OOM? Come posso evitare che ciò accada?

Il ceppo:

[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

Sono in esecuzione su Linux ip-10-8-139-98 3.2.0-29-virtual # 46-Ubuntu SMP ven 27 lug 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux su Amazon EC2.


1
Come breve riepilogo per tutti coloro che lo leggono, questo è un bug del kernel di Linux
UsAaR33

Risposte:


13

Modifica: terrò la mia risposta originale qui sotto, ma cercherò di spiegare cosa sta succedendo qui e fornirò una soluzione generale per te.

Modifica 2: fornita un'altra opzione.

Il problema che stai colpendo qui ha a che fare con il modo in cui il kernel gestisce l'I / O. Quando fai una scrittura sul tuo filesystem, quella scrittura non è immediatamente impegnata sul disco; sarebbe incredibilmente inefficiente. Invece, le scritture vengono memorizzate nella cache in un'area di memoria denominata cache della pagina e periodicamente scritte in blocchi sul disco. La sezione "sporca" del registro descrive le dimensioni di questa cache della pagina che non è stata ancora scritta su disco:

dirty:123816kB

Quindi cosa svuota questa cache sporca? Perché non sta facendo il suo lavoro?

'Flush' su Linux è responsabile della scrittura di pagine sporche sul disco. È un demone che si sveglia periodicamente per determinare se sono necessarie scritture su disco e, in tal caso, le esegue. Se sei un tipo C di ragazzo, inizia qui . Flush è incredibilmente efficiente; fa un ottimo lavoro scaricando roba sul disco quando necessario. E funziona esattamente come dovrebbe.

Flush viene eseguito all'esterno del contenitore LXC, poiché il contenitore LXC non ha il proprio kernel. I contenitori LXC esistono come costrutti attorno ai cgroups , che è una caratteristica del kernel Linux che consente migliori limitazioni e isolamento dei gruppi di processi, ma non il proprio kernel o demone flush.

Poiché il tuo LXC ha un limite di memoria inferiore alla memoria disponibile nel kernel, accadono cose strane. Flush presume di avere tutta la memoria dell'host in cui scrivere nella cache. Un programma nel tuo LXC inizia a scrivere un file di grandi dimensioni, buffer ... buffer ... e alla fine raggiunge il limite massimo e inizia a chiamare il gestore OOM. Questo non è un errore di alcun componente particolare; è un comportamento previsto. Tipo. Questo genere di cose dovrebbe essere gestito da cgroups, ma non sembra che sia così.

Questo spiega completamente il comportamento che vedi tra le dimensioni dell'istanza. Inizierai il flushing su disco molto prima sulla microistanza (con 512 MB di RAM) rispetto a una grande istanza

Ok, ha senso. Ma è inutile. Devo ancora scrivermi un file di big ass.

Bene, flush non è a conoscenza del tuo limite LXC. Quindi, invece di applicare patch al kernel, ci sono alcune opzioni qui per cose che puoi provare a modificare:

/proc/sys/vm/dirty_expire_centiseconds

Questo controlla per quanto tempo una pagina può essere conservata nella cache sporca e scritta sul disco. Di default sono 30 secondi; prova a impostarlo più in basso per iniziare a spingerlo più velocemente.

/proc/sys/vm/dirty_background_ratio

Questo controlla quale percentuale di svuotamento della memoria attiva può essere riempita prima che inizi a forzare le scritture. C'è un po 'di confusione che va a sistemare il totale esatto qui, ma la spiegazione più semplice è semplicemente guardare la tua memoria totale. Di default è il 10% (in alcune distro è il 5%). Imposta questo in basso; forzerà le scritture sul disco prima e potrebbe impedire all'LXC di esaurirsi.

Non posso semplicemente rovinare un po 'il filesystem?

Bene sì. Ma assicurati di provarlo .. potresti influenzare le prestazioni. Sui tuoi supporti in / etc / fstab dove scriverai questo, aggiungi l' opzione di mount ' sync '.

Risposta originale:

Prova a ridurre la dimensione del blocco utilizzata da DD:

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

È possibile scrivere solo un settore alla volta (512 byte su HDD più vecchi, 4096 su quelli più recenti). Se DD sta spingendo le scritture su disco più velocemente di quanto il disco possa accettarle, inizierà a memorizzare nella cache le scritture. Ecco perché la tua cache di file sta crescendo.


Dovrei notare che se eseguo test simili in Python in cui scarico manualmente l'oggetto file, l'errore si verifica ancora con probabilità simile. La cache cresce ovviamente, ma dovrebbe essere eliminata, si potrebbe pensare piuttosto che uccidere il processo.
UsAaR33,

1
Ci proverei comunque. Ho scoperto che forzare un fsync () con Python non fa sempre quello che ti aspetti.
alexphilipp,

1
@ UsAaR33 Ottieni disco più veloce.
Tink

1
@ UsAaR33 Un'applicazione scriverà il più rapidamente possibile; si aspetta che il kernel gestisca IO. Non ho mai usato un contenitore LXC prima, ma da una rapida occhiata sembra che non fornisca il proprio kernel nel chroot che crea? In tal caso, il kernel fornisce a IO il presupposto che disponga della memoria completa del sistema host disponibile. Non ha idea che la valutazione sia limitata a 300 MB. Una volta raggiunto quel limite, OOM inizia a uccidere i processi.
alexphilipp,

1
@ UsAaR33: impostazioni errate causano risultati negativi. A una parte del sistema viene detto che molta memoria può essere utilizzata come cache, a un'altra parte del sistema viene chiesto di interrompere i processi se la cache è troppo grande. Perché dovrebbe aspettare il disco quando c'è molta RAM disponibile? E se c'è molta RAM disponibile, perché non lasciarlo usare?
David Schwartz,

3

Il tuo file sta scrivendo su / tmp? In tal caso, potrebbe non essere su un vero filesystem ma residente su disco. Così, mentre ci scrivi, sempre più memoria viene tolta per soddisfare le esigenze del file. Alla fine, esaurisci la memoria + lo spazio di scambio e le tue prestazioni peggiorano fino al punto di essere completamente frustrate.


Sta scrivendo su $ HOME, che si trova su un mount AUFS che innesca le scritture sul disco sottostante. (EC2 EBS)
UsAaR33

2

a meno che non si stia scrivendo sul disco RAM, è possibile evitare la memorizzazione nella cache utilizzando oflag = direct

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

direct provoca un errore "Argomento non valido", ma l'utilizzo di oflag = dsync funziona.
UsAaR33

mi dispiace se non ha funzionato per te, come da pagina man "uso diretto I / O diretto per i dati"
Kevin Parker,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.