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 nice
ou /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 OutOfMemoryError
e 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 .