Solução de Problemas: Diagnosticando Erros Comuns em Contêineres Docker Rapidamente

Domine a arte da solução rápida de problemas em contêineres Docker com este guia essencial. Aprenda o processo estruturado para diagnosticar falhas de inicialização usando comandos principais do Docker. Detalhamos como usar `docker ps -a` para identificar falhas, extrair informações críticas com `docker logs` e realizar análises avançadas de configuração com `docker inspect`. Este artigo fornece exemplos práticos e soluções direcionadas para problemas frequentes, incluindo erros de código de saída 127, conflitos de porta e eventos OOMKilled, garantindo que você possa identificar rapidamente a causa raiz e restaurar o serviço.

Solução de Problemas: Diagnosticando Erros Comuns em Contêineres Docker Rapidamente

Quando um contêiner Docker sai imediatamente, não comece reconstruindo a imagem ou alterando flags aleatórias. Comece descobrindo o que o Docker sabe: o estado do contêiner, o código de saída, os logs e o comando exato que o Docker tentou executar. Essas quatro informações geralmente reduzem o problema rapidamente.

Um contêiner é apenas um processo com isolamento ao redor dele. Se o processo principal sair, o contêiner sai. Isso pode ser uma falha, um executável ausente, um trabalho em lote concluído, uma dependência de saúde com falha ou o kernel matando o processo porque usou muita memória. Os comandos abaixo ajudam a distinguir esses casos.

Encontre o contêiner parado primeiro

docker ps mostra apenas contêineres em execução. Contêineres com falha na inicialização geralmente ficam ocultos, a menos que você peça todos os contêineres:

docker ps -a

Observe STATUS, COMMAND e NAMES:

CONTAINER ID   IMAGE          COMMAND              STATUS                      NAMES
2d3f4b5c6e7a   my-app:latest  "/usr/bin/start"     Exited (127) 2 minutes ago  web-service
91aa34c0db22   worker:latest  "python worker.py"   Exited (0) 10 minutes ago   nightly-worker

Exited (0) geralmente significa que o processo foi concluído com sucesso. Isso é normal para trabalhos únicos. Para um serviço web, pode significar que o comando foi executado e terminou em vez de permanecer em primeiro plano.

Códigos de saída diferentes de zero apontam para falha, mas trate-os como pistas em vez de respostas finais. O código de saída 127 geralmente significa comando não encontrado. 126 geralmente significa encontrado, mas não executável. 137 geralmente significa que o processo recebeu SIGKILL; em contêineres, isso é frequentemente, mas nem sempre, relacionado à pressão de memória. Sempre confirme com logs e saída de inspeção.

Leia os logs antes de alterar qualquer coisa

O Docker captura stdout e stderr do processo principal do contêiner para o driver de log padrão. Use:

docker logs web-service

Opções úteis:

docker logs --tail 100 web-service
docker logs --since 15m web-service
docker logs -t web-service
docker logs -f web-service

Se os logs disserem config file not found, verifique montagens e ambiente. Se mostrarem um stack trace do aplicativo, depure o aplicativo. Se estiverem vazios, o processo pode ter falhado antes de produzir saída, ou o entrypoint da imagem pode estar errado.

Para um loop de falha, evite docker logs -f como sua única ferramenta. Pode fazer a falha parecer ativa sem fornecer o estado. Combine logs com docker inspect.

Inspecione o estado do contêiner

docker inspect retorna um grande documento JSON. Raramente você precisa de tudo. Comece com campos formatados:

docker inspect -f 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}' web-service

Em seguida, inspecione o comando e a configuração da imagem:

docker inspect -f 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{.Config.User}}' web-service

Verifique montagens quando o erro envolver arquivos:

docker inspect -f '{{json .Mounts}}' web-service

Se o contêiner foi morto por memória, .State.OOMKilled é o campo importante. Se for true, aumentar a memória pode ajudar, mas a melhor próxima pergunta é por que a memória cresceu. Um limite maior pode esconder um vazamento por tempo suficiente para falhar mais tarde.

Reproduza com um shell interativo quando possível

Se a imagem contiver um shell, substitua o entrypoint e inspecione o sistema de arquivos:

docker run --rm -it --entrypoint /bin/sh my-app:latest

Algumas imagens têm Bash:

docker run --rm -it --entrypoint /bin/bash my-app:latest

Dentro, verifique os arquivos e caminhos de comando:

ls -l /usr/bin/start
id
env

Imagens mínimas podem não incluir um shell. Nesse caso, use as ferramentas disponíveis da imagem, reconstrua uma variante de depuração temporária ou inspecione o Dockerfile e a saída da construção. Não adicione permanentemente pacotes de depuração a uma imagem de produção apenas porque a solução de problemas foi inconveniente uma vez.

Comando não encontrado: saída 127

A saída 127 geralmente significa que o Docker não conseguiu encontrar o executável nomeado por ENTRYPOINT ou CMD, ou um script de inicialização tentou executar um comando ausente.

Causas comuns:

  • O executável nunca foi copiado para a imagem.
  • O caminho está correto no host, mas não dentro da imagem.
  • O script usa /bin/bash, mas a imagem só tem /bin/sh.
  • O comando depende de PATH, e PATH difere do que você espera.

Verifique o comando da imagem:

docker inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' web-service

Se o entrypoint for um script, verifique seu shebang e terminações de linha. Um script com terminações de linha CRLF do Windows pode falhar com mensagens confusas de "não encontrado" porque o caminho do interpretador efetivamente contém um retorno de carro.

Permissão negada: saída 126 ou erros de arquivo

A saída 126 geralmente significa que o Docker encontrou o comando, mas não conseguiu executá-lo. Para scripts, o arquivo pode não ter o bit executável:

COPY start.sh /usr/local/bin/start.sh
RUN chmod 0755 /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]

Para arquivos montados por volume, lembre-se de que as permissões do host se aplicam. Se um contêiner for executado como UID 1000 e o diretório do host for propriedade de root sem permissão de escrita, o contêiner não pode escrever lá apenas porque está "dentro do Docker".

Verifique o usuário em tempo de execução:

docker inspect -f 'user={{.Config.User}}' web-service

Se estiver em branco, muitas imagens são executadas como root por padrão, mas nem todas. Imagens oficiais e com segurança reforçada geralmente usam um usuário não root.

Porta já alocada

Um erro de bind geralmente aparece quando você publica uma porta do host que já está em uso:

docker run -p 8080:80 nginx

O Docker pode relatar algo como bind: address already in use. Encontre o conflito:

docker ps --format 'table {{.Names}}\t{{.Ports}}'
lsof -iTCP:8080 -sTCP:LISTEN

Em seguida, pare o processo conflitante ou escolha outra porta do host:

docker run -p 8081:80 nginx

A porta do contêiner pode permanecer a mesma. A porta do host é a parte antes dos dois pontos.

Arquivos ausentes e montagens ruins

Se os logs disserem que um arquivo de configuração está ausente, compare o que o aplicativo espera com o que o Docker montou:

docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service

Um erro comum é montar um diretório do host sobre um caminho que já tinha arquivos na imagem. A montagem oculta o conteúdo da imagem nesse destino. Se a imagem contiver /app/config/default.yml e você montar um diretório vazio do host sobre /app/config, o arquivo padrão desaparece da visão do contêiner.

Verifique também caminhos relativos. -v ./config:/app/config depende do diretório onde você executou docker run, não do diretório onde o Dockerfile reside.

Falhas na verificação de saúde nem sempre são falhas de contêiner

Um contêiner pode estar em execução, mas não saudável:

docker ps

Você pode ver Up 2 minutes (unhealthy). Inspecione a saída de saúde:

docker inspect -f '{{json .State.Health}}' web-service

As verificações de saúde geralmente falham porque o aplicativo está ouvindo em uma porta diferente, vincula apenas a 127.0.0.1, leva mais tempo para iniciar do que a verificação de saúde permite ou precisa de um banco de dados que ainda não está pronto. Não confunda um contêiner não saudável com um que saiu; o caminho de diagnóstico é diferente.

Uma sequência rápida de solução de problemas

Use esta ordem quando precisar de uma resposta rapidamente:

  1. docker ps -a para encontrar o contêiner e o código de saída.
  2. docker logs --tail 100 <nome> para ler o erro do aplicativo.
  3. docker inspect -f ... para verificar estado, comando, usuário e montagens.
  4. Execute um shell temporário na imagem se o comando ou sistema de arquivos for suspeito.
  5. Verifique conflitos de host para portas e permissões de diretório montado.
  6. Reconstrua somente depois de saber se o problema é conteúdo da imagem, flags de tempo de execução ou configuração do aplicativo.

Essa sequência mantém a investigação fundamentada. O Docker geralmente tem evidências suficientes; o truque é lê-las antes de alterar a cena.

Verifique a política de reinicialização antes de confiar no que você vê

Uma política de reinicialização pode fazer um contêiner parecer que está falhando constantemente ou se recuperando constantemente. Verifique-a:

docker inspect -f 'restart={{json .HostConfig.RestartPolicy}}' web-service

Se a política for always ou unless-stopped, o Docker pode reiniciar o contêiner após cada falha. docker ps pode mostrá-lo como em execução por alguns segundos e depois reiniciando novamente. Nesse caso, use logs com carimbos de data/hora e inspecione a contagem de reinicializações:

docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' web-service

Uma alta contagem de reinicializações geralmente significa que o processo principal sai rapidamente. A correção raramente é "alterar a política de reinicialização". A política está apenas revelando a falha subjacente.

Distinga problemas de tempo de construção de problemas de tempo de execução

Se um arquivo estiver ausente dentro do contêiner, pergunte quando ele deveria ter aparecido. Arquivos copiados no Dockerfile são preocupações de tempo de construção. Arquivos montados com -v ou volumes do Compose são preocupações de tempo de execução.

Verificações de tempo de construção:

docker image inspect my-app:latest
docker run --rm --entrypoint /bin/sh my-app:latest -c 'ls -la /app'

Verificações de tempo de execução:

docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service

Essa divisão economiza tempo. Reconstruir a imagem não corrigirá uma montagem de host ruim. Alterar uma flag de volume não corrigirá um Dockerfile que nunca copiou o binário.

Variáveis de ambiente e segredos podem falhar silenciosamente

Muitos aplicativos saem porque uma variável de ambiente necessária está ausente, mas o erro do Docker apenas diz que o processo saiu com o código 1. Inspecione o ambiente configurado cuidadosamente:

docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service

Tenha cuidado onde você executa esse comando; ele pode imprimir segredos. Em logs compartilhados, imprima apenas os nomes das variáveis:

docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service | sed 's/=.*//'

Se você usar --env-file, verifique terminações de linha CRLF, espaços não citados e arquivos ausentes. Arquivos de ambiente do Docker não são scripts de shell completos. Mantenha-os simples: linhas KEY=value, comentários onde suportados pela sua versão do Docker e sem suposições de que a expansão do shell ocorrerá dentro do arquivo.

Uma revisão do mundo real antes de enviar

Antes de considerar um script ou configuração de contêiner concluída, leia-o uma vez como se você fosse a próxima pessoa que terá que depurá-lo às 2 da manhã. Isso muda o que você percebe. Um prompt que fazia sentido enquanto escrevia o script pode ser ambíguo quando aparece em um log de CI. Um nome de serviço Docker que parecia óbvio pode não corresponder ao nome da variável no aplicativo. Um padrão Bash pode ser seguro para desenvolvimento e perigoso para produção.

Gosto de fazer uma breve simulação com valores deliberadamente estranhos. Use um caminho com espaços. Use um valor opcional vazio. Tente um nome de arquivo que comece com um traço. Execute o script de um diretório de trabalho diferente. Inicie o contêiner sem uma variável de ambiente esperada. Esses testes não são sofisticados, mas capturam as suposições que geralmente quebram primeiro.

Verifique também a mensagem de falha. Se a única saída for failed, o conselho do artigo não chegou à implementação. Uma falha útil diz qual valor foi usado, qual verificação falhou e o que o operador pode alterar. Isso não significa despejar todas as variáveis de ambiente ou imprimir segredos. Significa ser específico onde a especificidade ajuda: o caminho de configuração, o nome do comando ausente, o nome da rede, o nome do host do serviço ou a porta que o processo tentou vincular.

O hábito final é manter exemplos próximos da maneira como o sistema é realmente executado. Se a produção usa Compose, teste com Compose. Se um script é iniciado por systemd, teste-o com systemd ou com um ambiente igualmente mínimo. Se um comando deve ser seguro para copiar e colar, inclua as aspas, separadores -- e validação no próprio exemplo. Os leitores copiam padrões de trabalho com mais frequência do que copiam avisos.

Essa revisão não é burocracia. É como a automação pequena permanece chata. Chato é o que você quer de prompts de shell, carregadores de configuração, expansão de variáveis, diagnósticos de contêiner e rede Docker. Quanto menos surpreendente for o comportamento, mais fácil será para o próximo operador confiar nele.

Para falhas do Docker, salve o comando exato que criou o contêiner quando puder. docker inspect pode mostrar a configuração atual, mas uma nota de incidente que inclui o comando docker run original, serviço Compose, tag de imagem e nome do arquivo de ambiente é muito mais fácil de reproduzir mais tarde. Evite usar latest durante a depuração séria. Uma tag móvel pode transformar uma falha em duas investigações diferentes porque a imagem muda enquanto você ainda está lendo logs.

Se você estiver diagnosticando um problema semelhante ao de produção localmente, puxe o mesmo digest ou tag de imagem, use o mesmo layout de volume e passe a mesma forma de ambiente não secreta. Reprodução supera especulação.