Marcio Cunha

Containers reiniciando sozinhos? Como investigar o OOMKilled

Descubra por que seus containers no Kubernetes estão morrendo de repente com o erro OOMKilled e aprenda a ajustar limites de memória e caçar vazamentos.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • O erro OOMKilled acontece quando um container consome mais memória do que o limite máximo estipulado pelo Kubernetes.
  • A falta de limites claros de recursos em pods pode derrubar nós inteiros por esgotamento de memória no sistema operacional.
  • O kernel do Linux age rapidamente encerrando o processo mais pesado para salvar a integridade da máquina virtual ou física.
  • Ferramentas de observabilidade e coleta de logs evitam surpresas e ajudam a mapear gargalos antes de uma pane geral.
  • O dimensionamento correto evita desperdício de infraestrutura e garante estabilidade operacional em ambientes de produção.

O terror silencioso: o que significa o código OOMKilled

Quem gerencia aplicações modernas em nuvem mais cedo ou mais tarde esbarra em um comportamento frustrante: um container simplesmente desaparece, reinicia e deixa rastros vagos nos registros do sistema. Na grande maioria das vezes, o culpado é o temido código OOMKilled, que significa Out Of Memory Killed. Na prática, isso quer dizer que o kernel do Linux, o cérebro por trás do sistema operacional que gerencia o hardware, percebeu que a aplicação estava sugando mais memória RAM do que o permitido e a executou sumariamente para evitar que o servidor inteiro travasse.

Para entender esse mecanismo, imagine que o Kubernetes funciona como um condomínio fechado onde cada morador, que seria um container, tem uma cota rígida de espaço e mantimentos. Se um morador começa a acumular lixo e ocupar o corredor dos vizinhos, a administração do prédio intervém imediatamente e despeja o infrator. Nos servidores, o juiz e carrasco é o OOM Killer, um subsistema do núcleo do Linux que monitora o consumo de RAM segundo segundo. Quando a pressão de memória atinge níveis críticos, ele escolhe o processo que mais consome recursos proporcionalmente e o encerra enviando um sinal fatal.

Por que os limites de recursos salvam ou destroem seu cluster

Um dos erros mais comuns de quem está começando a orquestrar aplicações é deixar os campos de limits e requests em branco nos arquivos de configuração do Kubernetes. O requests diz ao agendador quanto de memória o container precisa para dar a partida, enquanto o limits define o teto absoluto que ele nunca poderá ultrapassar. Quando você omite esses valores, o container ganha carta branca para consumir toda a memória disponível no nó, o que frequentemente causa um efeito dominó que afeta outras aplicações vizinhas no mesmo servidor físico.

Em cenários corporativos, a ausência dessas travas transforma um pico repentino de tráfego em uma pane generalizada. Antes de planejar ajustes finos, vale lembrar que problemas de infraestrutura costumam vir acompanhados de outras dores operacionais, como falhas intermitentes de resolução de nomes causadas pelo CoreDNS ou discos cheios que travam o ambiente, conforme discutido ao analisar como manter o pipeline de CI/CD rodando sem faltar espaço no Docker. Definir limites claros isola o dano, garantindo que se um serviço sofrer um ataque de negação de serviço ou um vazamento interno, ele morra sozinho sem levar o resto da infraestrutura junto.

Como rastrear o momento exato da morte por memória

Quando um pod reinicia, a primeira ferramenta de investigação é o comando padrão de inspeção do Kubernetes. Ao rodar a listagem de pods, você provavelmente notará status como OOMKilled ou um código de saída 137. Esse número 137 é uma assinatura matemática que resulta da soma de 128 mais o código 9, que representa o sinal SIGKILL enviado pelo sistema operacional para encerrar a marra do processo à força. Saber decodificar esse número economiza horas de tentativa e erro na bancada de engenharia.

Para extrair detalhes adicionais do histórico recente de eventos do cluster, utilize o comando de descrição detalhada. Ele exibe o histórico de reinicializações e o motivo exato da última terminação do container. Veja como inspecionar o comportamento do seu pod diretamente no terminal:

kubectl get pods -n producao
kubectl describe pod meu-servico-web-7b99d-xyz9q -n producao

Se os logs textuais tradicionais sumirem junto com o processo, a melhor alternativa é centralizar as informações de eventos e métricas de infraestrutura antes que o container seja exterminado. Manter uma esteira de observabilidade robusta complementa a estratégia de diagnóstico, assim como a centralização de logs dispersos no cluster com Fluent Bit permite auditar o comportamento da aplicação milissegundos antes do colapso.

Identificando vazamentos de memória na raiz do código

Muitas vezes, o erro OOMKilled não é culpa de um limite baixo, mas sim de um vazamento de memória na aplicação. Isso acontece quando o código aloca espaço na RAM para processar dados, mas esquece de liberar essa referência quando a tarefa termina. Linguagens gerenciadas como Java, Node.js e Python possuem coletores de lixo automáticos, mas se houver referências globais acumulando objetos em listas ou caches infinitos, o coletor se torna inútil e a memória cresce linearmente até esbarrar no teto do Kubernetes.

Para investigar vazamentos em ambientes de desenvolvimento ou staging, utilize ferramentas de perfilamento de memória específicas da linguagem, como o heapdump no Node.js ou o pprof em aplicações escritas em Go. Monitore o comportamento do container sob carga simulada e observe se o consumo de RAM desce após picos de acesso ou se mantém uma curva ascendente constante. Se a memória nunca voltar ao patamar inicial após o encerramento das requisições, você encontrou um vazamento de código que precisa ser corrigido na raiz antes de simplesmente subir o limite de recursos no arquivo YAML.

Passo a passo para ajustar requests e limits de forma segura

Se a análise de código descartou vazamentos e a aplicação apenas exige mais fôlego para rodar confortavelmente, o próximo passo é recalibrar o manifesto do Kubernetes. Nunca chute valores aleatórios; baseie-se em métricas reais coletadas por ferramentas como Prometheus e Grafana durante os horários de pico operacional. Siga o procedimento abaixo para atualizar o dimensionamento de recursos de um deployment em produção:

  1. Consulte o consumo máximo histórico de memória do container nas últimas duas semanas usando as métricas do cluster.
  2. Edite o arquivo de manifesto YAML do seu deployment localizando a seção de recursos do container afetado.
  3. Aplique as alterações atualizadas no cluster executando o comando de atualização e valide a estabilidade do rollout.

Veja um exemplo prático de como estruturar os blocos de recursos dentro do arquivo de configuração do seu deployment no Kubernetes:

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
  limits:
    memory: "1Gi"
    cpu: "500m"

Considerações finais

Investigar falhas de OOMKilled exige paciência e uma visão sistêmica que vai desde a escrita do código até a configuração dos nós de infraestrutura. Compreender que o limite de memória não é uma punição, mas sim um contrato de estabilidade, muda a forma como projetamos aplicações resilientes para ambientes em nuvem. Monitorar tendências de consumo, aplicar ajustes baseados em dados reais e corrigir vazamentos internos garante que seus containers permaneçam firmes, entregando valor contínuo aos usuários sem sustos inesperados na madrugada.