OOMkiller no Docker é mais difícil do que você pensa

Olá de novo. Antecipando o início do curso, "Java Developer" preparou uma tradução de outro pequeno material.




Recentemente, um dos usuários do Plumbr APM teve um problema estranho com a parada de emergência do contêiner do docker com o código 137. A configuração era simples com vários contêineres e máquinas virtuais aninhados, semelhante a uma boneca aninhada:

  • servidor de ferro próprio com o Ubuntu;
  • muitos contêineres docker com o Ubuntu dentro;
  • Java Virtual Machine dentro dos contêineres da janela de encaixe.

Durante a investigação do problema, encontramos a documentação do Docker sobre esse tópico. Ficou claro que o motivo era uma parada manual do contêiner ou falta de memória e a subsequente intervenção do oomkiller (Out-Of-Memory Killer).

Observamos o syslog e vemos que, de fato, oomkiller foi chamado :

[138805.608851] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
[138805.608887] [<ffffffff8116d20e>] oom_kill_process+0x24e/0x3b0
[138805.608916] Task in /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71 killed as a result of limit of /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71
[138805.608902] [<ffffffff8116da84>] pagefault_out_of_memory+0x14/0x90
[138805.608918] memory: usage 3140120kB, limit 3145728kB, failcnt 616038
[138805.608940] memory+swap: usage 6291456kB, limit 6291456kB, failcnt 2837
[138805.609043] Memory cgroup out of memory: Kill process 20611 (java) score 1068 or sacrifice child

Como você pode ver, o processo java atingiu um limite de 3145728 KB (cerca de 3 GB), o que causou a parada do contêiner. Isso é estranho, pois o docker foi lançado com um limite de 4 GB (no arquivo docker-compose).

Você provavelmente sabe que a JVM também impõe suas limitações no uso da memória. Embora a própria janela de encaixe foi definida para um limite de 4 GB, a JVM foi lançado com a opção Xmx=3GB. Isso pode ser ainda mais confuso, mas lembre-se de que a JVM pode usar mais memória do que o especificado em -Xmx (consulte o artigo sobre como analisar o uso de memória na JVM ).

Até entendermos o que está acontecendo. O Docker deve permitir o uso de 4 GB. Então, por que o OOMkiller funcionou em 3 GB? Uma busca adicional por informações nos levou ao fato de que há outra limitação de memória no sistema operacional, que foi implantada no hardware.

Agradeça ao cgroups (grupos de controle). cgroups é o mecanismo do kernel Linux para restringir, controlar e contabilizar o uso de recursos por grupos de processos. Comparado a outras soluções (equipe niceou /etc/security/limits.conf), os cgroups oferecem mais flexibilidade, pois podem trabalhar com (sub) conjuntos de processos.

Em nossa situação, o cgroups limitou o uso de memória a 3 GB (via memory.limit_in_bytes). Temos algum progresso!

Um estudo da memória e eventos do GC usando Plumbr mostrou que na maioria das vezes a JVM usava cerca de 700 MB. A exceção ocorreu apenas imediatamente antes da parada, quando houve um aumento na alocação de memória. Ele foi seguido por uma longa pausa no GC. Parece que aconteceu o seguinte:

  • O código Java em execução na JVM está tentando obter muita memória.
  • A JVM, tendo verificado que o limite Xmx de 3 GB ainda está longe, pede para alocar memória para o sistema operacional.
  • O Docker também verifica e vê que seu limite de 4 GB também não foi atingido.
  • O kernel do SO verifica o limite de 3 GB do cgroup e mata o contêiner.
  • A JVM para com o contêiner antes de poder processar seu próprio OutOfMemoryError.

Entendendo isso, configuramos todas as limitações de 2,5 GB para docker e 1,5 para java. Depois disso, a JVM poderia manipular OutOfMemoryErrore lançar uma exceção OutOfMemoryError. Isso permitiu ao Plumbr fazer sua mágica - obter um instantâneo de memória com os despejos correspondentes da pilha e mostrar que havia uma consulta no banco de dados, que em uma determinada situação tentou carregar quase todo o banco de dados.

achados


Mesmo em uma situação tão simples, havia três limitações de memória:

  • JVM via parâmetro -Xmx
  • Docker através das opções no arquivo docker-compose
  • Sistema operacional através do parâmetro memory.limit_in_bytes cgroups

Portanto, ao encontrar um assassino de OOM, você deve prestar atenção a todas as limitações de memória envolvidas.

Outra conclusão para os desenvolvedores de docker. Parece que não faz sentido permitir a execução de tais "bonecos aninhados", nos quais o limite de memória do contêiner fechado é maior que o limite de cgroups. Uma simples verificação disso ao iniciar o contêiner com o aviso apropriado pode economizar centenas de horas de depuração para seus usuários.

Isso é tudo. Estamos esperando por você no curso .

All Articles