Por que o Docker trava com OOM Killer e como auditar limites
Descubra por que seus containers Docker sofrem com o OOM Killer repentino e aprenda a investigar consumo de memória, limites de RSS e estouro de heap no Linux.
Resumo
- O Linux encerra processos bruscamente quando o consumo de memória excede o limite configurado.
- O Resident Set Size representa a quantidade real de memória física que o container está usando.
- A falta de limites explétitos no Docker Compose expõe o nó hospedeiro a quedas sistêmicas.
- Ferramentas nativas como o dmesg ajudam a confirmar o código de saída 137 gerado pelo kernel.
- Ajustar a coleta de lixo em linguagens gerenciadas evita o estouro de heap e quedas inesperadas.
O mistério do container que desaparece sem deixar rastro
Quem trabalha com virtualização leve e sistemas distribuídos já passou por aquele momento frustrante em que um serviço simplesmente para de responder. Você olha para a tela, executa um comando de status e percebe que o container sumiu, deixando para trás apenas um código de erro misterioso. Na prática, isso significa que o sistema operacional precisou tomar uma decisão drástica para salvar a integridade da máquina inteira. Quando a memória RAM acaba ou atinge o teto estipulado, entra em ação o OOM Killer, um mecanismo de proteção do núcleo do Linux que escolhe e abate processos gulosos.
Para entender por que isso acontece, vale a pena olhar para o conceito de Resident Set Size, ou RSS. Em termos simples, o RSS é a quantidade real de memória física que um programa está ocupando no momento, sem contar o que foi guardado temporariamente no disco em arquivos de troca. Se você quiser se aprofundar em diagnósticos de infraestrutura, vale a pena conferir nosso artigo sobre como auditar limites de CPU e memória em containers travando, que complementa esta análise de estabilidade.
Como o kernel do Linux calcula e aplica o limite de memória
O Docker utiliza recursos nativos do kernel do Linux conhecidos como cgroups, ou grupos de controle. Pense nos cgroups como cercas virtuais que dizem exatamente quanta energia, processamento e memória um determinado grupo de processos pode consumir. Quando você inicia um container sem definir restrições, ele herda a capacidade total da máquina física, o que pode esgotar os recursos do servidor e derrubar outras aplicações essenciais.
Quando configuramos um limite estrito de memória usando a diretiva memory no Docker Compose, o kernel cria uma cerca rígida. Se o consumo do container ultrapassa essa cerca — somando a memória que o programa usa para rodar e os buffers do sistema —, o mecanismo de proteção não hesita. Ele dispara o OOM Killer interno daquele cgroup, interrompendo imediatamente o processo principal com o temido código de saída 137. Para diagnosticar com precisão esse comportamento, consulte também o guia sobre como auditar o OOM Killer com o comando dmesg, essencial para auditorias rápidas.
Investigando o estouro de heap e o consumo oculto
Muitas vezes, a culpa pelo encerramento abrupto não é do Docker em si, mas sim de como a linguagem de programação gerencia seus próprios espaços de trabalho. Em ambientes como Node.js, Python ou Java, o programa reserva um grande bloco de memória interna chamado heap para guardar objetos e variáveis dinâmicas. Se a aplicação cria dados em excesso mais rápido do que o coletor de lixo consegue descartar, o heap incha descontroladamente até estourar o limite imposto pelo container.
Na prática, isso gera uma pegadinha visual: o painel de monitoramento mostra que o container está consumindo pouca memória, mas o kernel do Linux enxerga um pico instantâneo de RSS que cruza a linha de corte. Para diagnosticar cenários específicos em aplicações JavaScript, veja as dicas em como achar vazamentos de memória no heap do Node.js usando snapshots, permitindo identificar quais objetos estão sujando a memória.
Passo a passo para monitorar e configurar limites seguros
Configurar políticas defensivas evita que uma falha de software derrube o ambiente de produção inteiro. Para garantir que seus serviços operem dentro de margens seguras e previsíveis, siga esta rotina de verificação e ajuste prático no seu ambiente:
- Inspecione o histórico de encerramentos do sistema executando o comando para verificar se houve intervenção do kernel.
dmesg -T | grep -i oom - Verifique os logs de encerramento do container específico para confirmar se o código de saída retornado foi exatamente 137.
docker inspect --format='{{.State.ExitCode}}' nome_do_container - Defina limites claros de memória e uso de troca no arquivo de orquestração para conter vazamentos antes que afetem o servidor.
services: web: image: minha-aplicacao deploy: resources: limits: memory: 512M reservations: memory: 256M
Conclusão e boas práticas para evitar surpresas em produção
Gerenciar a memória de containers exige vigilância constante e o uso correto de ferramentas de observabilidade. Definir limites claros no Docker, monitorar o comportamento do RSS e entender como o coletor de lixo da sua linguagem favorita se comporta são passos fundamentais para manter a estabilidade. Com essas precauções, você transforma falhas silenciosas e intermitentes em incidentes previsíveis e fáceis de resolver.
Em última análise, um sistema resiliente não é aquele que nunca falha, mas sim aquele que isola os problemas antes que eles afetem a experiência dos usuários. Ajustar os parâmetros de infraestrutura e revisar periodicamente o consumo de heap garante que sua arquitetura continue crescendo de forma sustentável e segura.