Back-End

27 jun, 2016

Java e o limite de memória nos containers: LXC, Docker e OpenVZ

Publicidade

Recentemente, eu encontrei um artigo informativo escrito por Matt Williams sobre Java em Docker e suas restrições de memória. O autor traz à tona um tema interessante escondido sobre a questão com os limites de memória que as pessoas podem enfrentar durante um exercício de container.

A alta taxa de compartilhamentos e likes mostra que esse tema ressoa entre os desenvolvedores Java.

markus

Então, eu gostaria de analisar a questão mais profundamente e descobrir possíveis soluções.

Problema

Matt descreve sua “jornada” com um comportamento padrão de heap JVM em um container Docker. Ele descobriu que os limites de RAM não são exibidos corretamente dentro de um container. Como resultado, qualquer aplicativo Java ou outro vê a quantidade total de recursos de RAM alocados para toda a máquina host, e o JVM não pode indicar quantos recursos foram fornecidos ao recipiente pai que está sendo executado . Isso leva a OutOfMemoryError devido ao comportamento incorreto do heap JVM em um container.

Fabio Kung, da Heroku, descreveu profundamente as principais razões desse problema em seu recente artigo “Memory inside Linux containers. Or why don’t free and top work in a Linux container?”.

A maioria das ferramentas do Linux que fornece métricas de recursos do sistema foi criada antes ainda de o cgroups existir (por exemplo .: free and top, ambos do procps). Eles normalmente leem métricas de memória do sistema de arquivos proc filesystem: /proc/meminfo,/proc/vmstat, /proc/PID/smaps e outros.

Infelizmente, /proc /meminfo, /proc /vmstat e amigos não são containerized. O que significa que eles não são cgroup-aware. Eles sempre exibem números de memória do sistema host (máquina física ou virtual) como um todo, o que é inútil para recipientes Linux modernos (Heroku, Docker etc.). Processos dentro de um container não podem invocar livremente em cima de outros para determinar a quantidade de memória que têm de trabalhar; eles estão sujeitos a limites impostos por seus cgroups e não podem usar toda a memória disponível no sistema host.

O autor destaca a importância da visibilidade dos limites de memória reais. Eles permitem otimizar aplicativos e solucionar problemas dentro de containers: vazamentos de memória, utilização de swap, degradação do desempenho etc. Além disso, alguns casos de uso dependem de escala vertical para otimização de uso de recursos dentro dos containers, alterando o número de workers, processos ou threads automaticamente. A escala vertical geralmente depende de quanta memória está disponível para um container específico, para que os limites possam estar visíveis no interior do recipiente.

Solução

A iniciativa de ppen containers trabalha em melhorias runC para implementar um sistema de arquivos userspace override de arquivos /proc. E LXC cria um sistema de arquivos lxcfs que permite que containers tenham sistemas de arquivos cgroup virtualizados e views virtualizadas de arquivos /proc. Portanto, esse problema está no foco dos mantenedores de container. E eu acredito que as melhorias mencionadas podem ajudar a resolver o problema no nível mais baixo.

Nós também enfrentamos o mesmo problema na Jelastic e já o resolvemos para os nossos clientes, por isso gostaria de mostrar como ele funciona.

Primeiro de tudo, vamos para o assistente Jelastic, escolher um provedor de serviços para uma conta de teste e criar um container Java Docker com os limites de memória pré-definidos – por exemplo, 8 cloudlets, que é igual a 1 GB RAM.

image01

Vá para o portão Jelastic SSH (1), selecione o ambiente de teste antes criar (2), e escolha o container (3). Agora estamos dentro e podemos verificar a memória disponível com a ferramenta gratuita (4).

image041

Como podemos ver, o limite de memória é igual a 1 GB definido antes. Vamos verificar a ferramenta top.

image031

Tudo funciona corretamente. E para verificação dupla, repetimos o teste de Matt relacionado com a questão do comportamento heurístico do Java descrito em seu artigo.

image021

Como esperado, temos MaxHeapSize = 268435546 (~ 256MiB), que é igual a 1/4 da RAM no container de acordo com o comportamento padrão do heap JVM.

Qual é o segredo da nossa solução? É claro, é uma combinação adequada de “ingredientes”. Em nosso caso, é um mix de tecnologias OpenVZ e Docker que dá mais controle em termos de segurança e isolamento, assim como nos permite jogar com características desejadas como containers live migration e containers hibernation. Abaixo, você pode ver um esquema de alto nível de um container Docker Jelastic.

image05

Em OpenVZ, cada container tem uma view virtualizada do pseudo-filesystem  /proc. Em particular, /proc /meminfo dentro do container é uma versão “bespoke”, mostrando informações por container, não a partir do host. Então, quando ferramentas do tipo top and free são executadas dentro de um container, elas mostram a utilização da RAM e do swap com os limites específicos a esse container particular.

Vale a pena notar que o swap dentro dos containers não é um swap real, mas um virtual (assim, toda a tecnologia é chamada VSwap). A ideia principal é que uma vez habilitado um container com VSwap sobre seu limite de RAM configurado, um pouco de sua memória vai para o chamado cache de swap. Sem swap real ocorrendo fora, ou seja, não há nenhum I/O desnecessário, a menos que haja uma escassez de RAM global (não por container). Além disso, o container que utiliza VSwap é penalizado por uso excessivo de RAM por um bit slowing down, então, do interior do container, ele sente como se o swap real estivesse acontecendo. Essa tecnologia resulta no controle sobre a memória por container e uso de swap.

Essa implementação permite rodar Java e outros runtimes, sem a necessidade de adaptar as aplicações para Jelastic PaaS. Mas se você não estiver usando Jelastic, uma solução possível será sempre especificar tamanhos de heap de JVM e não depender de heurísticas, de acordo com as dicas do Matt. Para o resto das linguagens, é necessária uma pesquisa mais profunda. Por favor, deixe-nos saber se você pode compartilhar sua experiência nessa direção e teremos o prazer de estender o artigo.

***

Ruslan Synytsky faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://blog.jelastic.com/2016/05/03/java-and-memory-limits-in-containers-lxc-docker-and-openvz/