Introdução
Neste laboratório prático, você aprenderá a identificar e resolver um problema comum do Docker: contêineres que saem imediatamente após a inicialização. Este problema frequentemente confunde iniciantes e pode ocorrer por várias razões, desde erros de configuração até problemas de aplicação.
Ao completar este laboratório, você entenderá o ciclo de vida de um contêiner Docker, aprenderá a diagnosticar saídas imediatas de contêineres, dominará técnicas de depuração e implementará as melhores práticas para garantir que seus contêineres sejam executados de forma confiável. Essas habilidades são essenciais para qualquer pessoa que trabalhe com Docker em ambientes de desenvolvimento ou produção.
Compreendendo os Fundamentos de Contêineres Docker
Vamos começar explorando os fundamentos dos contêineres Docker e nos familiarizar com os comandos básicos usados para gerenciar contêineres.
O que é um Contêiner Docker?
Um contêiner Docker é um pacote de software leve, autônomo e executável que inclui tudo o que é necessário para executar uma aplicação:
- Código
- Runtime (Tempo de Execução)
- Ferramentas do sistema
- Bibliotecas
- Configurações
Os contêineres são executados em isolamento do sistema host e de outros contêineres, proporcionando consistência em diferentes ambientes.
Explorando o Docker em Seu Sistema
Primeiro, vamos verificar se o Docker está corretamente instalado e em execução em seu sistema:
docker --version
Você deve ver uma saída semelhante a:
Docker version 20.10.21, build baeda1f
Em seguida, vamos verificar se algum contêiner está atualmente em execução:
docker ps
Este comando lista os contêineres em execução. Como ainda não iniciamos nenhum, você deve ver apenas os cabeçalhos das colunas:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Para visualizar todos os contêineres, incluindo os parados:
docker ps -a
Compreendendo o Ciclo de Vida do Contêiner
O ciclo de vida do contêiner Docker consiste em vários estados:
- Created (Criado): Um contêiner é criado, mas ainda não foi iniciado
- Running (Em Execução): O contêiner está executando seus processos definidos
- Paused (Pausado): Os processos do contêiner são temporariamente suspensos
- Stopped (Parado): O contêiner saiu ou foi parado
- Deleted (Excluído): O contêiner foi removido do sistema
Vamos executar um contêiner simples e observar seu ciclo de vida:
docker run hello-world
Você deve ver uma saída semelhante a:
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...
Observe que este contêiner foi executado e saiu imediatamente após exibir a mensagem. Este é, na verdade, um comportamento normal para o contêiner hello-world, pois ele foi projetado para simplesmente exibir uma mensagem e sair.
Verifique o contêiner na lista de todos os contêineres:
docker ps -a
Você deve ver seu contêiner hello-world na lista com o status "Exited (Saiu)":
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 hello-world "/hello" 30 seconds ago Exited (0) 30 seconds ago peaceful_hopper
O código de saída (0) indica que o contêiner saiu com sucesso, sem erros.
Comandos Docker Chave para Gerenciamento de Contêineres
Aqui estão alguns comandos Docker essenciais que você usará ao longo deste laboratório:
docker run [OPTIONS] IMAGE [COMMAND]: Cria e inicia um contêinerdocker ps: Lista os contêineres em execuçãodocker ps -a: Lista todos os contêineres (incluindo os parados)docker logs [CONTAINER_ID]: Visualiza os logs do contêinerdocker inspect [CONTAINER_ID]: Obtém informações detalhadas do contêinerdocker exec -it [CONTAINER_ID] [COMMAND]: Executa um comando em um contêiner em execuçãodocker stop [CONTAINER_ID]: Para um contêiner em execuçãodocker rm [CONTAINER_ID]: Remove um contêiner
Agora que você entende os fundamentos dos contêineres Docker e seu ciclo de vida, passaremos para a solução de problemas de contêineres que saem inesperadamente.
Identificando Problemas de Saída de Contêineres
Nesta etapa, criaremos um contêiner Docker que sai imediatamente e aprenderemos a diagnosticar o problema.
Executando um Contêiner que Sai Imediatamente
Primeiro, vamos tentar executar um contêiner Ubuntu:
docker run ubuntu
Você notará algo interessante - o comando é concluído instantaneamente e retorna ao seu prompt. Onde está nosso contêiner Ubuntu? Vamos verificar:
docker ps
Nenhum contêiner está em execução. Agora, verifique todos os contêineres, incluindo os parados:
docker ps -a
Você verá uma saída semelhante a:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7d9e7f6543d ubuntu "/bin/bash" 10 seconds ago Exited (0) 10 seconds ago focused_galileo
a1b2c3d4e5f6 hello-world "/hello" 10 minutes ago Exited (0) 10 minutes ago peaceful_hopper
O contêiner Ubuntu foi iniciado e, em seguida, saiu imediatamente com um código de status 0, indicando que saiu sem erros. Este é o comportamento esperado, mas pode ser confuso para iniciantes.
Entendendo Por Que os Contêineres Saem
Os contêineres são projetados para executar um comando ou processo específico. Quando esse processo é concluído ou sai, o contêiner para. Este é um princípio fundamental do design do Docker.
O contêiner Ubuntu saiu imediatamente porque:
- O comando padrão para a imagem Ubuntu é
/bin/bash - Quando executado sem as flags
-it(interativo, terminal), não há entrada para o shell bash - Sem entrada e sem um comando específico para executar, o bash sai imediatamente
- Quando o processo principal (bash) sai, o contêiner para
Visualizando Logs e Informações do Contêiner
Vamos examinar os logs do nosso contêiner Ubuntu que saiu para entender o que aconteceu. Primeiro, encontre o ID do contêiner na saída do seu docker ps -a, então:
docker logs CONTAINER_ID
Substitua CONTAINER_ID pelo ID real do seu contêiner. Você provavelmente não verá nenhuma saída porque o contêiner não produziu nenhum log antes de sair.
Para obter informações mais detalhadas sobre o contêiner:
docker inspect CONTAINER_ID
Isso exibirá um grande objeto JSON com a configuração do contêiner e informações de estado.
Vamos nos concentrar no código de saída:
docker inspect CONTAINER_ID --format='{{.State.ExitCode}}'
Você deve ver:
0
Isso confirma que o contêiner saiu normalmente, não devido a um erro.
Mantendo um Contêiner em Execução
Para manter o contêiner Ubuntu em execução, precisamos:
- Fornecer uma sessão interativa, ou
- Substituir o comando padrão por um processo de longa duração
Vamos tentar a abordagem interativa:
docker run -it ubuntu
Agora você está dentro do contêiner com um prompt bash:
root@3a4b5c6d7e8f:/#
O contêiner permanece em execução enquanto esta sessão bash estiver ativa. Digite exit ou pressione Ctrl+D para sair do contêiner.
exit
Alternativamente, podemos manter um contêiner em execução fornecendo um comando que não seja concluído imediatamente:
docker run -d ubuntu sleep 300
Isso executa o contêiner Ubuntu e executa o comando sleep 300, que manterá o contêiner em execução por 300 segundos (5 minutos).
Verifique se o contêiner está em execução:
docker ps
Você deve ver seu contêiner no estado em execução:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a8b7c6d5e4f ubuntu "sleep 300" 10 seconds ago Up 10 seconds hopeful_hopper
Ao diagnosticar contêineres que saem imediatamente, lembre-se destes pontos-chave:
- Os contêineres saem quando seu processo principal é concluído
- Verifique os logs e códigos de saída para entender por que eles pararam
- Se um contêiner deve continuar em execução, certifique-se de que seu processo principal não saia
Solucionando Problemas Comuns de Saída de Contêineres
Agora que entendemos por que os contêineres saem imediatamente, vamos explorar as causas comuns de saídas inesperadas de contêineres e como solucionar esses problemas.
Criando um Contêiner com um Problema
Vamos criar uma situação simples em que um contêiner sai inesperadamente. Primeiro, crie um diretório para nossos arquivos de teste:
mkdir -p ~/project/docker-exit-test
cd ~/project/docker-exit-test
Agora, crie um script Python simples com um erro:
nano app.py
Adicione o seguinte código:
import os
## Attempt to read a required environment variable
database_url = os.environ['DATABASE_URL']
print(f"Connecting to database: {database_url}")
print("Application running...")
## Rest of the application code would go here
Salve e saia do arquivo (pressione Ctrl+O, Enter, depois Ctrl+X).
Agora, crie um Dockerfile para construir uma imagem com esta aplicação:
nano Dockerfile
Adicione o seguinte conteúdo:
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
Salve e saia do arquivo.
Construa a imagem Docker:
docker build -t exit-test-app .
Você deve ver uma saída indicando que a imagem foi construída com sucesso:
Successfully built a1b2c3d4e5f6
Successfully tagged exit-test-app:latest
Agora, execute o contêiner:
docker run exit-test-app
Você deve ver o contêiner sair imediatamente com um erro:
Traceback (most recent call last):
File "/app/app.py", line 4, in <module>
database_url = os.environ['DATABASE_URL']
File "/usr/local/lib/python3.9/os.py", line 679, in __getitem__
raise KeyError(key) from None
KeyError: 'DATABASE_URL'
O contêiner saiu porque nosso script Python esperava uma variável de ambiente que não foi fornecida.
Diagnosticando o Problema
Quando um contêiner sai inesperadamente, siga estas etapas de solução de problemas:
- Verifique o código de saída para determinar se é um erro:
docker ps -a
Procure seu contêiner e observe o código de saída. Ele deve ser diferente de zero, indicando um erro:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1c2d3e4f5g6 exit-test-app "python app.py" 20 seconds ago Exited (1) 19 seconds ago vigilant_galileo
- Examine os logs do contêiner:
docker logs $(docker ps -a -q --filter ancestor=exit-test-app --latest)
Isso recupera os logs do contêiner mais recente criado a partir de nossa imagem.
A mensagem de erro mostra claramente o problema: o script Python está tentando acessar uma variável de ambiente chamada DATABASE_URL que não existe.
Corrigindo o Problema
Agora, vamos corrigir o problema fornecendo a variável de ambiente ausente:
docker run -e DATABASE_URL=postgresql://user:password@db:5432/mydatabase exit-test-app
Você deve ver o contêiner ser executado com sucesso:
Connecting to database: postgresql://user:password@db:5432/mydatabase
Application running...
O contêiner ainda sai, mas desta vez é porque nosso script chega ao fim e termina normalmente. Se quiséssemos que o contêiner continuasse em execução, precisaríamos modificar nosso script para incluir um loop infinito ou um processo de longa duração.
Causas Comuns de Saídas de Contêineres
Aqui estão várias razões comuns pelas quais os contêineres podem sair inesperadamente:
- Variáveis de ambiente ausentes: Como acabamos de demonstrar
- Dependências ausentes: Bibliotecas ou pacotes de sistema ausentes
- Falhas de conexão: Incapaz de alcançar um banco de dados ou outro serviço
- Problemas de permissão: Permissões insuficientes para acessar arquivos ou recursos
- Limitações de recursos: Contêiner fica sem memória ou CPU
- Falhas de aplicação: Bugs no código da aplicação
Técnicas de Solução de Problemas
Para cada um desses problemas, aqui estão abordagens eficazes de solução de problemas:
- Revisar logs: Sempre verifique os logs do contêiner primeiro com
docker logs - Substituir ponto de entrada (entrypoint): Use
docker run --entrypoint /bin/sh -it my-imagepara entrar no contêiner e investigar - Adicionar instruções de depuração: Modifique sua aplicação para adicionar mais logs
- Verificar o uso de recursos: Use
docker statspara monitorar o uso de recursos do contêiner - Inspecionar o ambiente: Execute
docker exec -it CONTAINER_ID envpara verificar as variáveis de ambiente
Vamos tentar a técnica de substituição do ponto de entrada com nossa imagem:
docker run --entrypoint /bin/sh -it exit-test-app
Agora você está dentro do contêiner com um shell. Você pode explorar o ambiente:
ls -la
cat app.py
echo $DATABASE_URL
Você verá que DATABASE_URL não está definido. Saia do contêiner quando terminar:
exit
Ao entender por que os contêineres saem e aplicar essas técnicas de solução de problemas, você pode diagnosticar e resolver rapidamente a maioria dos problemas de saída de contêineres.
Implementando as Melhores Práticas para a Confiabilidade de Contêineres
Agora que entendemos como solucionar problemas de saída de contêineres, vamos implementar as melhores práticas para tornar nossos contêineres mais confiáveis e robustos. Vamos aprimorar nosso aplicativo de exemplo com tratamento de erros adequado, verificações de integridade e registro (logging).
1. Melhorando o Tratamento de Erros
Vamos modificar nosso aplicativo Python para lidar com variáveis de ambiente ausentes de forma adequada:
cd ~/project/docker-exit-test
nano app.py
Atualize o conteúdo para:
import os
import time
import sys
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
print(f"Connecting to database: {database_url}")
## Simulate a long-running process
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Salve e saia do arquivo.
Esta versão aprimorada:
- Usa
os.environ.get()com um valor padrão em vez de gerar uma exceção - Implementa um loop de longa duração para manter o contêiner ativo
- Lida com o desligamento adequado quando é terminado
Vamos reconstruir a imagem:
docker build -t exit-test-app:v2 .
E execute o contêiner aprimorado:
docker run -d --name improved-app exit-test-app:v2
Verifique se o contêiner está em execução:
docker ps
Você deve ver seu contêiner em execução:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c1d2e3f4g5h6 exit-test-app:v2 "python app.py" 10 seconds ago Up 10 seconds improved-app
Visualize os logs para confirmar que está funcionando:
docker logs improved-app
Você deve ver uma saída como:
Connecting to database: sqlite:///default.db
Application running... Press Ctrl+C to exit
Application heartbeat: 1
Application heartbeat: 2
2. Implementando uma Verificação de Integridade no Dockerfile
As verificações de integridade permitem que o Docker monitore a integridade do seu contêiner. Vamos atualizar nosso Dockerfile para incluir uma verificação de integridade:
nano Dockerfile
Atualize-o para:
FROM python:3.9-slim
WORKDIR /app
## Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY app.py .
## Add a health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
## Expose port for the health check endpoint
EXPOSE 8080
CMD ["python", "app.py"]
Agora precisamos adicionar um endpoint de verificação de integridade ao nosso aplicativo:
nano app.py
Substitua o conteúdo por:
import os
import time
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
## Simple HTTP server for health checks
class HealthRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/health':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b'OK')
else:
self.send_response(404)
self.end_headers()
def run_health_server():
server = HTTPServer(('0.0.0.0', 8080), HealthRequestHandler)
print("Starting health check server on port 8080")
server.serve_forever()
## Start health check server in a separate thread
health_thread = threading.Thread(target=run_health_server, daemon=True)
health_thread.start()
print(f"Connecting to database: {database_url}")
## Main application loop
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Salve e saia do arquivo.
Reconstrua a imagem com nossa verificação de integridade:
docker build -t exit-test-app:v3 .
Execute o contêiner com a nova versão:
docker run -d --name healthcheck-app -p 8080:8080 exit-test-app:v3
Após cerca de 30 segundos, verifique o status da integridade:
docker inspect --format='{{.State.Health.Status}}' healthcheck-app
Você deve ver:
healthy
Você também pode testar o endpoint de integridade diretamente:
curl http://localhost:8080/health
Isso deve retornar OK.
3. Usando as Políticas de Reinicialização do Docker
O Docker fornece políticas de reinicialização para reiniciar automaticamente os contêineres quando eles saem ou encontram erros:
docker run -d --restart=on-failure:5 --name restart-app exit-test-app:v3
Esta política reiniciará o contêiner até 5 vezes se ele sair com um código diferente de zero.
Políticas de reinicialização disponíveis:
no: Nunca reiniciar (padrão)always: Sempre reiniciar, independentemente do status de saídaunless-stopped: Sempre reiniciar, a menos que seja parado manualmenteon-failure[:max-retries]: Reiniciar apenas em saída diferente de zero
4. Definindo Limites de Recursos
Para evitar falhas de contêineres devido ao esgotamento de recursos, defina limites de recursos apropriados:
docker run -d --name resource-limited-app \
--memory=256m \
--cpus=0.5 \
exit-test-app:v3
Isso limita o contêiner a 256MB de memória e metade de um núcleo de CPU.
5. Configuração de Registro (Logging) Adequada
Para uma melhor depuração, configure seu aplicativo para gerar logs estruturados:
docker run -d --name logging-app \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
exit-test-app:v3
Isso configura o contêiner para usar o driver de log JSON com rotação de log (máximo de 3 arquivos de 10MB cada).
Resumo das Melhores Práticas
Ao implementar essas melhores práticas, você aprimorou significativamente a confiabilidade de seus contêineres Docker:
- Tratamento de erros adequado com valores padrão
- Verificações de integridade do contêiner para monitoramento
- Políticas de reinicialização apropriadas para recuperação automática
- Limites de recursos para evitar o esgotamento de recursos
- Configuração de registro (logging) adequada para facilitar a solução de problemas
Essas técnicas o ajudarão a criar contêineres resilientes que podem se recuperar de falhas e fornecer melhor observabilidade quando ocorrem problemas.
Resumo
Neste laboratório, você aprendeu habilidades essenciais para solucionar problemas e resolver questões de saída de contêineres Docker:
- Compreensão do ciclo de vida do contêiner Docker e por que os contêineres saem quando seu processo principal é concluído
- Identificação das causas comuns de saídas imediatas de contêineres por meio de logs e códigos de saída
- Implementação de tratamento de erros robusto em aplicações em contêineres
- Adição de verificações de integridade do contêiner para monitorar o status da aplicação
- Configuração de políticas de reinicialização para recuperação automática de falhas
- Definição de limites de recursos apropriados para evitar falhas de contêineres
- Implementação de estratégias de registro (logging) adequadas para facilitar a depuração
Essas habilidades formam a base de implantações confiáveis de contêineres Docker. À medida que você continua trabalhando com o Docker em cenários do mundo real, você achará essas técnicas de solução de problemas inestimáveis para manter aplicações em contêineres estáveis e resilientes.
Lembre-se de que as causas mais comuns de saídas imediatas de contêineres são:
- O processo principal concluindo sua tarefa (por design)
- Variáveis de ambiente ou configuração ausentes
- Erros ou exceções de aplicação
- Restrições de recursos ou problemas de conectividade
Ao aplicar as técnicas de diagnóstico e as melhores práticas abordadas neste laboratório, você pode identificar e resolver rapidamente esses problemas, garantindo que seus contêineres Docker sejam executados de forma confiável em qualquer ambiente.



