Por que os aplicativos em um contêiner LXC com memória limitada gravam arquivos grandes no disco sendo mortos pelo OOM?

10

EDIT2: Esse problema parece existir também no 3.8.0-25-generic # 37-Ubuntu SMP

EDIT: Modifiquei a pergunta do título original de "Por que o gerenciador de falta de memória do Linux seria acionado gravando em um arquivo com dd?" para refletir melhor que estou preocupado com o problema geral descrito abaixo:

Estou enfrentando um cenário problemático em que o assassino de OOM é difícil de matar no meu contêiner LXC quando escrevo um arquivo com tamanho que excede a limitação de memória (definido como 300 MB). O problema não ocorre quando executo o aplicativo em uma máquina virtual Xen (uma EC2 t1.micro) que na verdade possui apenas 512 MB de RAM; portanto, parece haver algum problema com o buffer do arquivo respeitando o limite de memória dos contêineres.

Como um exemplo simples, posso demonstrar como um arquivo grande escrito por dd causará problemas. Novamente, esse problema afeta todos os aplicativos. Estou procurando resolver o problema geral do cache do aplicativo ficar muito grande; Entendo como posso fazer o "dd" funcionar.

Cenário:

Eu tenho um contêiner LXC em que memory.limit_in_bytes está definido como 300 MB.

Tento dd um arquivo de ~ 500 MB da seguinte maneira:

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

Aproximadamente 20% do tempo, o gerenciador de Linux OOM é acionado por este comando e um processo é interrompido. Escusado será dizer que este é um comportamento altamente não intencional; O dd tem como objetivo simular uma gravação "útil" real de arquivo por um programa em execução no contêiner.

Detalhes: enquanto os caches de arquivos ficam grandes (260 MB), o rss e o mapa de arquivos parecem bastante baixos. Aqui está um exemplo de como o memory.stat pode parecer durante a gravação:

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

Aqui está um colar do dmesg onde o OOM provocou uma morte. Não estou muito familiarizado com as distinções entre os tipos de memória; Uma coisa que se destaca é que, embora o "Nó 0 Normal" seja muito baixo, há bastante memória livre no Nó 0 DMA32. Alguém pode explicar por que uma gravação de arquivo está causando o OOM? Como evito que isso aconteça?

O 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

Estou executando no Linux ip-10-8-139-98 3.2.0-29-virtual # 46-Ubuntu SMP Fri Jul 27 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU / Linux no Amazon EC2.

UsAaR33
fonte
1
Como um rápido resumo de todos os que lê-lo, este é um bug do kernel do linux
UsAaR33

Respostas:

13

Edit: Manterei minha resposta original abaixo, mas tentarei explicar o que está acontecendo aqui e fornecer uma solução geral para você.

Editar 2: Forneceu outra opção.

O problema que você está enfrentando aqui tem a ver com a forma como o kernel gerencia E / S. Quando você escreve no seu sistema de arquivos, essa gravação não é imediatamente confirmada no disco; isso seria incrivelmente ineficiente. Em vez disso, as gravações são armazenadas em cache em uma área da memória conhecida como cache da página e periodicamente gravadas em pedaços no disco. A seção "suja" do seu log descreve o tamanho desse cache de página que ainda não foi gravado no disco:

dirty:123816kB

Então, o que esvazia esse cache sujo? Por que não está fazendo seu trabalho?

'Flush' no Linux é responsável por gravar páginas sujas no disco. É um daemon que é ativado periodicamente para determinar se as gravações no disco são necessárias e, se houver, as executa. Se você é do tipo C, comece aqui . Flush é incrivelmente eficiente; ele faz um ótimo trabalho de liberar coisas para o disco quando necessário. E está funcionando exatamente como deveria.

A liberação é executada fora do seu contêiner LXC, pois seu contêiner LXC não possui seu próprio kernel. Os contêineres LXC existem como uma construção em torno de cgroups , que é um recurso do kernel Linux que permite melhores limitações e isolamento de grupos de processos, mas não seu próprio kernel ou daemon de liberação.

Como o seu LXC tem um limite de memória menor que a memória disponível pelo kernel, coisas estranhas acontecem. O Flush pressupõe que ele possui a memória completa do host para armazenar em cache as gravações. Um programa no seu LXC começa a gravar um arquivo grande, armazena em buffer ... armazena em buffer ... e, eventualmente, atinge seu limite rígido e começa a chamar o gerenciador de OOM. Isso não é uma falha de nenhum componente específico; é o comportamento esperado. Mais ou menos. Esse tipo de coisa deve ser tratado pelo cgroups, mas não parece ser.

Isso explica completamente o comportamento que você vê entre tamanhos de instância. Você começará a liberar o disco muito mais cedo na micro instância (com 512 MB de RAM) vs em uma instância grande

Ok, isso faz sentido. Mas é inútil. Eu ainda preciso me escrever um arquivo grande.

Bem, a descarga não está ciente do seu limite LXC. Então, em vez de corrigir o kernel, existem algumas opções aqui para coisas que você pode tentar ajustar:

/proc/sys/vm/dirty_expire_centiseconds

Isso controla quanto tempo uma página pode ser mantida no cache sujo e gravada no disco. Por padrão, são 30 segundos; tente abaixá-lo para começar a empurrá-lo mais rápido.

/proc/sys/vm/dirty_background_ratio

Isso controla qual porcentagem de liberação da memória ativa pode ser preenchida antes de começar a forçar gravações. Há um pouco de mexerica que separa o total exato aqui, mas a explicação mais fácil é apenas olhar para a sua memória total. Por padrão, é 10% (em algumas distribuições é 5%). Defina isso mais baixo; forçará as gravações no disco mais cedo e poderá impedir que o seu LXC fique fora dos limites.

Não posso simplesmente estragar um pouco o sistema de arquivos?

Bem, sim. Mas certifique-se de testar isso. Você pode afetar o desempenho. Em suas montagens no / etc / fstab onde você escreverá isso, adicione a opção de montagem ' sync '.

Resposta original:

Tente reduzir o tamanho do bloco usado pelo DD:

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

Você pode escrever apenas um setor por vez (512 bytes em HDDs mais antigos, 4096 em mais recentes). Se o DD estiver enviando gravações para o disco mais rapidamente do que o disco pode aceitá-las, ele começará a armazenar em cache as gravações na memória. É por isso que o cache do arquivo está crescendo.

alexphilipp
fonte
Devo observar que, se eu executar testes semelhantes em python, onde libero manualmente o objeto de arquivo, o erro ainda ocorrerá com probabilidade semelhante. O cache cresce, é claro, mas isso deveria estar sendo eliminado, seria de se pensar, em vez de o processo ser morto.
UsAaR33
1
Eu daria uma chance de qualquer maneira. Descobri que forçar um fsync () com Python nem sempre faz o que você espera.
alexphilipp
1
@ UsAaR33 Obtenha um disco mais rápido.
tink
1
@ UsAaR33 Uma aplicação irá escrever o mais rapidamente possível; espera que o kernel lide com E / S. Eu não usei um contêiner LXC antes, mas, de relance superficial, parece que ele não fornece seu próprio kernel no chroot que ele cria? Se for esse o caso, o kernel está fornecendo E / S com a suposição de que possui a memória completa do sistema host disponível. Não tem idéia de que você classifique o limite para 300 MB. Quando esse limite é atingido, o OOM começa a matar processos.
alexphilipp
1
@ UsAaR33: Configurações ruins causam resultados ruins. Uma parte do sistema é informada de que muita memória pode ser usada como cache, outra parte do sistema é informada para interromper os processos se o cache for muito grande. Por que ele deveria esperar pelo disco quando há bastante RAM disponível? E se houver bastante RAM disponível, por que não deixá-lo usá-lo?
David Schwartz
3

Seu arquivo está gravando em / tmp? Nesse caso, pode não estar em um sistema de arquivos real, mas estar em disco. Assim, à medida que você escreve, cada vez mais memória é retirada para atender às necessidades do arquivo. Eventualmente, você fica sem memória + troca de espaço e seu desempenho se deteriora ao ponto de frustração total.

mdpc
fonte
Ele está gravando em $ HOME, que está em uma montagem AUFS que aciona gravações no disco subjacente. (EC2 EBS)
UsAaR33
2

a menos que esteja gravando no disco RAM, você pode evitar o armazenamento em cache usando oflag = direct

dd if=/dev/zero of=test2 bs=100k oflag=direct count=5010
Kevin Parker
fonte
direct causa um erro "Argumento inválido", mas usar oflag = dsync funciona.
UsAaR33
Lamento se isso não funcionou para você, como por página homem "uso direto Direct I / O para os dados"
Kevin Parker