Erros de pod no Kubernetes: CrashLoopBackOff, OOMKilled, ImagePullBackOff e 7 outros
São 3h da manhã. O kubectl get pods devolve a mesma linha há vinte minutos: CrashLoopBackOff, 47 restarts, nenhuma pista na tela. O pipeline passou. O deploy foi aplicado. O cluster continua de pé.
O status daquela coluna nomeia o sintoma, nunca a causa. Dois pods em CrashLoopBackOff podem quebrar por motivos sem nenhuma relação entre si. Um por variável de ambiente ausente. Outro, por exemplo, porque a sonda de liveness derruba o container antes de ele terminar de subir.
Hoje 82% dos usuários de containers rodam Kubernetes em produção, segundo a pesquisa anual da CNCF.
Este guia percorre os dez estados de erro que mais aparecem no plantão. Para cada um: o que o Kubernetes está dizendo, a causa provável, o comando que confirma a suspeita, a correção.
O que o Kubernetes está dizendo quando o pod não sobe
Quando o pod não sobe, o Kubernetes exibe na coluna STATUS um resumo do que o kubelet encontrou. Pode ser falha de imagem, estouro de memória, agendamento sem nó disponível ou container que morre em loop. Esse texto é um campo de exibição do kubectl, não uma fase do pod.
No entanto, as fases oficiais são apenas cinco: Pending, Running, Succeeded, Failed e Unknown. O CrashLoopBackOff não aparece na API como estado do objeto. Ele é a leitura que o kubectl faz do histórico de reinícios.
Por isso a correção nunca vem do nome. Ela vem do evento, do log e do exit code que estão por trás dele. A tabela abaixo mapeia os dez estados que respondem pela maior parte dos chamados de plantão.
| Estado | O que significa | Causa mais provável | Comando que confirma |
|---|---|---|---|
| CrashLoopBackOff | O container sobe, morre e volta a subir com intervalo crescente | Erro da aplicação, configuração ausente ou liveness agressiva | kubectl logs POD --previous |
| OOMKilled | O kernel matou o processo por estourar o limite de memória | Limite baixo demais para o pico real ou vazamento na aplicação | kubectl describe pod POD no campo Last State |
| ImagePullBackOff / ErrImagePull | O nó tentou baixar a imagem do registry sem conseguir | Tag inexistente, registry privado sem credencial ou limite de pull atingido | kubectl describe pod POD nos eventos Failed to pull |
| Pending | O cluster aceitou o pod, porém nenhum nó recebeu o agendamento | Requests acima da capacidade livre, taint sem toleration ou PVC não vinculado | kubectl describe pod POD no evento FailedScheduling |
| Evicted | O kubelet expulsou o pod para liberar recurso do nó | Pressão de memória, de disco ou de PID no nó que hospedava o pod | kubectl describe pod POD no campo Reason |
| CreateContainerConfigError | O container nem chegou a ser criado pelo runtime | ConfigMap, Secret ou chave referenciada que não existe no namespace | kubectl get configmap,secret -n NS |
| RunContainerError | O runtime criou o container, mas o processo não chegou a rodar | Entrypoint sem permissão de execução ou binário fora do PATH da imagem | exit code 126 ou 127 no describe |
| Init:Error / Init:CrashLoopBackOff | Um init container falhou antes do container principal existir | Migração de banco, espera por dependência externa ou permissão no volume | kubectl logs POD -c INIT |
| ContainerCreating travado | O pod ficou preso na etapa de criação por vários minutos | Volume que não montou, plugin de rede indisponível ou pull ainda em curso | kubectl get events --sort-by=.lastTimestamp |
| NodeNotReady | O nó parou de reportar ao control plane e arrastou os pods junto | kubelet parado, disco cheio ou perda de rede no nó | kubectl describe node NODE |
Quem já mantém a coleta do cluster instrumentada chega nessa tabela com meio caminho andado. O evento já está no backend. A busca vira correlação, não comando manual repetido no terminal.
O loop de diagnóstico que serve para os dez erros
Antes de tratar cada estado, vale fixar a sequência. Ela não muda: primeiro o status, depois os eventos do pod, depois o log, por fim a linha do tempo do namespace. Dessa forma, quatro comandos resolvem a triagem inteira.
Passo 1: leia o status e a contagem de restarts
O kubectl get pods entrega três informações em uma linha: o estado atual, quantas vezes o container reiniciou, há quanto tempo o pod existe.
Um RESTARTS alto com AGE baixo indica loop de crash. Já um pod parado em Pending desde a criação aponta para agendamento, não para aplicação.
Passo 2: abra o describe e vá direto ao Last State
O describe traz o histórico que o get resume. Duas áreas concentram a resposta: o bloco Last State do container, com Reason e Exit Code da tentativa anterior; a lista Events no rodapé.
Leia o rodapé de baixo para cima, porque o evento mais recente costuma ser consequência, não causa.
Passo 3: leia o log da tentativa que falhou
Sem a flag --previous, o kubectl logs mostra o container atual, que muitas vezes acabou de subir sem ter falhado ainda. A tentativa que interessa é a anterior.
Em pod com vários containers, a flag -c escolhe qual deles responde. Ela é obrigatória quando existe sidecar. O que a aplicação registra em log na saída padrão é o que chega ali.
Passo 4: ordene os eventos do namespace
Quando o pod não deixou rastro no log, o namespace deixou. Os eventos ordenados por horário mostram a sequência real: falha de montagem de volume, expulsão por pressão de memória, agendamento recusado.
Vale lembrar que o cluster descarta eventos antigos, em geral após uma hora. Por isso, esse comando perde valor rápido.
CrashLoopBackOff: o container sobe, morre e volta a subir
O nome descreve o mecanismo com precisão. O container inicia, encerra com código diferente de zero, o kubelet reinicia. Depois de falhas seguidas entra o back-off, que espaça as tentativas.
A sequência de espera é fixa: 10s, 20s, 40s, 80s, 160s. Depois disso, ela para de crescer em 300s, conforme a documentação oficial do projeto.
Esse teto de cinco minutos explica a sensação de que o cluster desistiu. No entanto, ele não desistiu. Apenas parou de martelar o nó com um container quebrado.
As causas se dividem em quatro famílias. A primeira é erro da própria aplicação: exceção não tratada, falha de conexão com banco, dependência externa fora do ar. A segunda é configuração ausente, com variável ou arquivo que o processo espera encontrar.
A terceira é sonda de liveness agressiva, que mata o container durante uma inicialização lenta. Por fim, a quarta é memória, tratada na seção seguinte.
Repare que a segunda família e a terceira produzem sintoma idêntico à primeira. O container morre, o kubelet reinicia. Só o log da tentativa anterior separa uma da outra. Por isso a análise de causa raiz começa sempre no mesmo lugar.
Para liveness, a correção quase nunca é afrouxar o limiar. Ajuste o initialDelaySeconds ou troque a sonda por startupProbe, que segura as demais até a aplicação declarar que subiu.
OOMKilled e o exit code 137: quando a memória mata o container
Um container marcado como OOMKilled ultrapassou o limite de memória declarado no manifesto. O kernel dispara o OOM killer. O processo morre com SIGKILL, que o Kubernetes registra como exit code 137: 128 mais o sinal 9.
Existe aqui, porém, uma armadilha que custa horas de plantão. O exit code 137 aparece em duas situações diferentes. O campo Reason separa as duas.
Com OOMKilled, o problema é memória. Com Error, o container recebeu SIGTERM, ignorou o sinal, depois levou SIGKILL quando o terminationGracePeriodSeconds venceu.
A tabela abaixo cobre os códigos que mais aparecem no Last State.
| Exit code | Sinal | O que aconteceu | O que fazer |
|---|---|---|---|
| 1 | nenhum | Erro da aplicação ou exceção não tratada | Ler o log da tentativa anterior à procura do stack trace |
| 125 | nenhum | O runtime não conseguiu iniciar o container | Conferir imagem, arquitetura e log do containerd no nó |
| 126 | nenhum | O entrypoint existe, porém sem permissão de execução | Aplicar chmod +x no binário durante o build |
| 127 | nenhum | Binário não encontrado no PATH da imagem | Revisar CMD, ENTRYPOINT e a troca de imagem base |
| 137 | SIGKILL (128+9) | Limite de memória estourado ou grace period vencido | Com Reason OOMKilled, revisar o limite; com Error, revisar o encerramento |
| 139 | SIGSEGV (128+11) | Falha de segmentação em acesso inválido de memória | Reproduzir fora do cluster e checar biblioteca nativa |
| 143 | SIGTERM (128+15) | O Kubernetes encerrou o pod de forma ordenada | Tratar o sinal na aplicação e usar CMD em formato exec |
| 255 | nenhum | Erro do runtime ou da infraestrutura do nó | Checar o estado do nó antes de investigar a aplicação |
Antes de subir o limite, confira se o problema é dimensionamento. Segundo o levantamento anual sobre uso de recursos em nuvem, a maioria das cargas usa menos da metade da memória que solicita. O consumo de CPU fica abaixo de 25% do reservado.
Requests, limits e a classe de QoS
O requests define o que o scheduler reserva. O limits define onde o kernel corta. A relação entre os dois determina a classe de QoS do pod, que decide quem morre primeiro quando o nó aperta.
Pods sem requests nem limits caem na classe BestEffort. Portanto, são os primeiros a sair sob pressão. Quando os dois valores coincidem em todos os containers, o pod vira Guaranteed e sai por último.
Definir o número certo exige série histórica de consumo. É o mesmo insumo do planejamento de capacidade do ambiente.
ImagePullBackOff e ErrImagePull: o cluster não baixa a imagem
Aqui o container nem existe ainda. O kubelet pediu a imagem ao registry, recebeu recusa, passou a tentar de novo com espera crescente. O ErrImagePull é a primeira falha. O ImagePullBackOff é a espera que vem depois.
Quatro causas cobrem quase todos os casos. A mais comum é a tag: alguém promoveu uma referência que não existe no registry. Erro de digitação explica parte das ocorrências, build ainda não publicado explica o resto.
Depois vem o registry privado sem imagePullSecret no namespace correto, detalhe que quebra a cada namespace novo.
A terceira causa é limite de requisições do registry público. Ele responde com 429 Too Many Requests, o que derruba todos os pods do nó de uma vez. A quarta é arquitetura incompatível, comum em cluster com nós ARM misturados a nós x86.
O evento no describe nomeia qual das quatro é. Vale conhecer o mínimo de como uma imagem de container é construída. Assim, o erro de tag e o de arquitetura ficam legíveis no evento.
Pending: o pod existe, mas nenhum nó aceita agendá-lo
Um pod em Pending passou pela API e parou no scheduler. O evento FailedScheduling traz a explicação em texto, com a contagem de nós recusados e o motivo de cada recusa.
Os motivos se repetem. Requests de CPU ou memória acima do que sobra em qualquer nó lidera a lista. O problema piora sobretudo quando alguém copia um manifesto de outro ambiente sem revisar os valores.
Em seguida vêm taints sem toleration correspondente, além de nodeSelector ou regra de afinidade que nenhum nó satisfaz.
O quarto motivo engana bastante: PVC que não vincula. O pod fica em Pending por causa do volume, não por falta de CPU. Confira o kubectl get pvc antes de mexer em recurso.
Em último lugar, a quota de namespace fecha a lista. Quando o ResourceQuota estoura, o pod sequer chega a ser criado. O erro aparece no controlador do Deployment, não no pod.
Evicted: o nó expulsou o pod para se salvar
Diferente dos anteriores, este erro não fala da aplicação. O kubelet monitora pressão de memória, disco e PID no nó. Ao cruzar o limiar de despejo, ele encerra pods para liberar recurso antes que o nó inteiro fique instável.
A ordem de escolha segue a classe de QoS. Primeiro os BestEffort. Depois os Burstable que passaram do próprio request. Por último os Guaranteed. Ou seja, um pod sem requests declarados vira candidato natural a despejo mesmo consumindo pouco.
Além disso, disco costuma ser a causa esquecida. Imagem antiga acumulada, log de container sem rotação, volume emptyDir crescendo sem teto enchem o nó em silêncio.
Quando o despejo vira rotina, o problema é do nó. A solução passa por acompanhar o consumo por container em vez de recriar o pod a cada ocorrência.
CreateContainerConfigError: falta um ConfigMap ou um Secret
Este estado tem a melhor relação entre clareza da mensagem e velocidade da correção. O manifesto referencia um ConfigMap, um Secret ou uma chave dentro deles. O objeto não existe no namespace do pod.
Três variações aparecem no dia a dia. A primeira é o objeto ausente, comum quando o deploy vai para um namespace novo sem os recursos de apoio. A segunda é a chave errada dentro de um objeto que existe, com um key que não bate no valueFrom. A terceira é volume que aponta para Secret inexistente.
O primo próximo é o CreateContainerError. Ele indica falha na criação por outro motivo: nome de container duplicado, ponto de montagem inválido ou caminho que colide com arquivo da imagem. Por fim, a mensagem do evento distingue os dois sem ambiguidade.
RunContainerError e os exit codes 126 e 127
Neste ponto o container foi criado, a imagem está no nó, a configuração resolveu. O processo é que não começa. A causa quase sempre mora no entrypoint. Os dois exit codes clássicos apontam direto para ela.
O 126 significa permissão negada: o arquivo existe, porém sem bit de execução. Acontece quando o binário entra na imagem por COPY sem chmod, ou quando o script vem de um checkout que perdeu as permissões.
Já o 127 significa comando não encontrado. É típico de troca de imagem base que removeu o shell ou um utilitário assumido como presente.
Inclusive, um terceiro caso engana pela mensagem: script com terminação de linha do Windows. O kernel lê o shebang com o retorno de carro ao final, não encontra o interpretador, devolve o mesmo 127.
Init:Error e Init:CrashLoopBackOff: a falha vem antes do container principal
Init containers rodam em sequência. Cada um precisa terminar com sucesso antes do próximo começar. Se um falha, o pod nem chega ao container principal. O status mostra o prefixo Init: seguido do erro.
O log exige a flag -c com o nome do init container. Essa é a razão de tanta gente concluir que o pod não gera log. O kubectl logs POD sozinho tenta ler o container principal, que ainda não existe.
Os candidatos habituais são três. Migração de banco que falha por schema ou credencial. Espera por dependência externa que estoura o tempo. Por último, ajuste de permissão em volume rodando com usuário sem privilégio.
ContainerCreating travado: volume, rede ou runtime segurando o pod
O ContainerCreating é estado normal por alguns segundos. Vira erro quando persiste por minutos. A partir daí, o describe mostra o que trava.
Volume lidera a lista. Eventos como FailedAttachVolume ou FailedMount indicam disco preso a outro nó, política de acesso incompatível com o número de réplicas ou provisionador fora do ar. Rede vem logo atrás: sem o plugin CNI pronto, o pod fica sem interface.
Por outro lado, imagem grande em pull também explica a demora, sem que exista falha real. Nesse caso o evento Pulling aparece sozinho, sem erro. Como o sintoma é o mesmo, aplique a disciplina de isolar camada por camada antes de recriar qualquer objeto.
NodeNotReady: o pod está certo, o nó é que não está
Quando vários pods de aplicações diferentes quebram ao mesmo tempo, pare de olhar o pod. O kubectl get nodes responde em um comando: um nó em NotReady parou de reportar ao control plane.
As causas moram fora do Kubernetes: kubelet parado, disco cheio, perda de rede, exaustão de recurso na máquina.
O control plane espera um tempo de tolerância antes de marcar os pods para remoção. Isso explica a janela de alguns minutos entre o nó cair e os pods começarem a migrar.
O describe node lista as condições: MemoryPressure, DiskPressure, PIDPressure. Cada uma, isolada, já basta para disparar despejo antes mesmo do nó ficar NotReady.
Como o erro chega ao time antes de chegar ao usuário
Todos os dez estados acima pressupõem alguém olhando para o cluster. Na prática, o erro mais caro não é o difícil de corrigir. É o que ninguém viu.
Um CrashLoopBackOff em três réplicas de cinco não derruba o serviço. Ele apenas reduz a capacidade, o que passa despercebido até o primeiro pico de tráfego.
Na prática, três sinais dão cobertura à maior parte do que este guia descreve. O primeiro é a contagem de reinícios por pod, que denuncia crash loop antes do impacto visível.
O segundo é o consumo de memória contra o limite declarado, que antecipa o OOMKilled pela tendência. O terceiro é a exportação dos eventos do cluster para o mesmo backend das métricas, já que eles expiram em cerca de uma hora.
Com esses três no lugar, o alerta chega pela causa, não pelo sintoma. Uma operação de observabilidade madura correlaciona o evento OOMKilled, o pico de memória na métrica e a exceção no log da mesma janela.
Visibilidade completa de pods, nodes e clusters Kubernetes em produção.
Monitoramos health checks, consumo de recursos e eventos de orquestração para equipes que rodam workloads críticos em containers.
O que levar para o próximo pod que não sobe
Em resumo, o status na tela é a pergunta, nunca a resposta. Os dez estados deste guia se resolvem com a mesma sequência de quatro comandos. A diferença entre um plantão de dez minutos e outro de três horas está em não pular etapa.
Leia o status. Abra o describe. Busque o Reason e o Exit Code. Leia o log da tentativa anterior. Ordene os eventos do namespace.
Guarde três atalhos que economizam tempo real. O exit code 137 com Reason: OOMKilled é memória; com Error, é encerramento mal tratado. Pod em Pending sem falta de CPU costuma ser PVC. Vários pods quebrando juntos raramente é aplicação.
Por fim, o passo seguinte é deixar de descobrir esses erros pelo chamado do usuário. Para avaliar como instrumentar reinícios, consumo contra limite e eventos do cluster no seu ambiente, fale com um especialista da OpServices.
Perguntas Frequentes
Qual é o erro de pod mais comum no Kubernetes?
CrashLoopBackOff é o mais frequente, porque funciona como estado guarda-chuva: qualquer container que encerre com código diferente de zero e seja reiniciado pelo kubelet acaba nele. Erro da aplicação, variável de ambiente ausente, sonda de liveness agressiva e estouro de memória produzem o mesmo status na tela. Por isso o nome não indica a correção. A causa só aparece no log da tentativa anterior e no campo Exit Code do describe.Qual a diferença entre CrashLoopBackOff e ImagePullBackOff?
CrashLoopBackOff o container chegou a rodar e morreu, então existe log para ler. No ImagePullBackOff o container nunca foi criado, porque o nó não conseguiu baixar a imagem do registry, o que não deixa log de aplicação nenhum. A investigação muda de lugar: o primeiro se resolve no log da tentativa anterior, o segundo se resolve nos eventos do describe, que dizem se o problema é tag inexistente, credencial ausente ou limite de requisições do registry.O CrashLoopBackOff é um erro do Kubernetes ou da minha aplicação?
1 aponta erro da aplicação, 137 com Reason OOMKilled aponta limite de memória, 126 e 127 apontam problema de entrypoint na imagem. Falha do próprio cluster costuma aparecer em outros estados, como ContainerCreating travado ou NodeNotReady.O que significa exit code 137 em um pod?
137 significa que o processo recebeu SIGKILL, ou seja, 128 mais o sinal 9. Duas situações produzem esse código. O campo Reason do describe separa as duas. Com Reason OOMKilled, o container passou do limite de memória e o kernel encerrou o processo. Com Reason Error, o container ignorou o SIGTERM inicial e levou SIGKILL quando o terminationGracePeriodSeconds venceu. A primeira pede revisão de limite, a segunda pede tratamento de sinal na aplicação.Como ver o log da tentativa anterior de um container que reiniciou?
--previous no kubectl logs, no formato kubectl logs POD -n NAMESPACE --previous. Sem ela o comando mostra o container atual, que muitas vezes acabou de subir sem ter falhado ainda, o que faz o log parecer vazio. Além disso, em pod com mais de um container, acrescente a flag -c com o nome do container desejado, incluindo init containers. Quando o pod foi recriado em vez de reiniciado, o log anterior deixa de existir e a informação passa a vir dos eventos do namespace.
