O que Todo Dev Deve Saber sobre Docker Engine: Arquitetura, Daemon, CLI e o Ciclo de Vida de Containers Já leu

Docker Engine: Arquitetura Fundamental O Docker Engine é o coração de toda a plataforma Docker. Trata-se de uma aplicação cliente-servidor que funciona como um gerenciador de containers, permitindo que você crie, execute, distribua e gerencie aplicações em ambientes isolados. Diferentemente de máquinas virtuais tradicionais, containers compartilham o kernel do sistema operacional host, tornando-os leves e eficientes. A arquitetura do Docker Engine é construída sobre três pilares fundamentais: o daemon Docker (dockerd), a CLI (Command Line Interface) e a API REST. O daemon é um processo de longa duração que executa no sistema operacional, gerenciando objetos Docker como images, containers, volumes e networks. A CLI é a interface pela qual você interage com o daemon, enquanto a API REST permite que programas terceirizados se comuniquem com o Docker programaticamente. Componentes Principais da Arquitetura O Docker Engine funciona através de uma arquitetura modular bem definida. O containerd é o runtime de container que atua como intermediário entre o daemon e o sistema

Docker Engine: Arquitetura Fundamental

O Docker Engine é o coração de toda a plataforma Docker. Trata-se de uma aplicação cliente-servidor que funciona como um gerenciador de containers, permitindo que você crie, execute, distribua e gerencie aplicações em ambientes isolados. Diferentemente de máquinas virtuais tradicionais, containers compartilham o kernel do sistema operacional host, tornando-os leves e eficientes.

A arquitetura do Docker Engine é construída sobre três pilares fundamentais: o daemon Docker (dockerd), a CLI (Command Line Interface) e a API REST. O daemon é um processo de longa duração que executa no sistema operacional, gerenciando objetos Docker como images, containers, volumes e networks. A CLI é a interface pela qual você interage com o daemon, enquanto a API REST permite que programas terceirizados se comuniquem com o Docker programaticamente.

Componentes Principais da Arquitetura

O Docker Engine funciona através de uma arquitetura modular bem definida. O containerd é o runtime de container que atua como intermediário entre o daemon e o sistema operacional, gerenciando o ciclo de vida dos containers de forma isolada. O runc é o runtime de container de baixo nível que efetivamente executa os containers usando features nativas do Linux como namespaces e cgroups.

┌─────────────────────────────────────────────────┐
│              Aplicação do Usuário               │
├─────────────────────────────────────────────────┤
│         Docker CLI (docker command)             │
├─────────────────────────────────────────────────┤
│    Docker Daemon (dockerd) - API Server        │
├─────────────────────────────────────────────────┤
│    containerd - Container Runtime Manager      │
├─────────────────────────────────────────────────┤
│    runc - Container Runtime (executa)          │
├─────────────────────────────────────────────────┤
│   Kernel Linux (namespaces, cgroups, etc)     │
└─────────────────────────────────────────────────┘

Quando você executa um comando Docker, a CLI se conecta ao daemon através de um socket Unix (/var/run/docker.sock por padrão). O daemon processa a solicitação, interage com o containerd, que por sua vez utiliza o runc para executar os containers. Esse design em camadas permite que o Docker seja modular e que cada componente possa ser atualizado independentemente.

Docker Daemon e Sua Operação

O Docker Daemon (dockerd) é um serviço que roda em background no seu sistema operacional. Ele é responsável por escutar requisições da CLI ou da API, gerenciar o ciclo de vida completo dos containers e manter o estado de todos os objetos Docker. Em sistemas Linux, o daemon geralmente roda como um serviço systemd, enquanto em Windows e macOS roda através do Docker Desktop.

O daemon armazena suas configurações em /etc/docker/daemon.json (no Linux), permitindo customizações como limites de recursos, drivers de storage, logging e outras opções avançadas. Ele mantém um estado persistente que sobrevive a reinicializações, garantindo que containers e volumes configurados anteriormente sejam recuperados quando o daemon reinicia.

Iniciando e Gerenciando o Daemon

No Linux, você pode controlar o daemon usando systemctl. Para verificar se o daemon está rodando:

# Verificar status do daemon
sudo systemctl status docker

# Iniciar o daemon
sudo systemctl start docker

# Parar o daemon
sudo systemctl stop docker

# Reiniciar o daemon
sudo systemctl restart docker

# Habilitar para iniciar automaticamente no boot
sudo systemctl enable docker

Para visualizar informações detalhadas sobre o daemon e seu ambiente:

docker info

Esse comando retorna informações como versão do Docker, número de containers em execução, drivers de storage, logging, e configurações de rede. É uma ferramenta valiosa para diagnosticar problemas e entender o estado atual do seu ambiente Docker.

Configuração do Daemon

Um arquivo daemon.json típico pode parecer assim:

{
  "debug": false,
  "log-level": "info",
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "insecure-registries": [
    "registry.interno.local:5000"
  ],
  "registry-mirrors": [
    "https://mirror.docker.io"
  ],
  "live-restore": true,
  "max-concurrent-downloads": 5
}

Cada propriedade controla um aspecto diferente da operação do daemon. A opção live-restore é particularmente importante em ambientes de produção, pois permite que containers continuem rodando mesmo se o daemon parar e reiniciar. A configuração storage-driver define qual mecanismo de armazenamento o Docker usará para as camadas das imagens.

Docker CLI: Interagindo com Containers

A Docker CLI é a ferramenta de linha de comando que você utiliza diariamente para interagir com o Docker. Ela funciona como um cliente que se conecta ao daemon através de uma chamada de API REST. Cada comando que você executa é traduzido em uma requisição HTTP que o daemon processa e responde.

A CLI segue um padrão de comando bem estruturado: docker [COMANDO] [OPÇÕES] [ARGUMENTOS]. Os comandos principais são divididos em categorias que trabalham com diferentes objetos Docker: imagens (docker image), containers (docker container), volumes (docker volume), networks (docker network), e muitos outros.

Comandos Essenciais para Containers

Os comandos mais frequentemente utilizados são aqueles que lidam diretamente com o ciclo de vida dos containers. Vamos explorar os principais:

# Criar e executar um container
docker run --name meu-app -d -p 8080:80 nginx:latest

# Listar containers em execução
docker ps

# Listar todos os containers (incluindo parados)
docker ps -a

# Executar um comando dentro de um container rodando
docker exec -it meu-app /bin/bash

# Visualizar logs de um container
docker logs meu-app

# Ver logs em tempo real
docker logs -f meu-app

# Parar um container
docker stop meu-app

# Iniciar um container parado
docker start meu-app

# Remover um container
docker rm meu-app

# Inspecionar detalhes de um container
docker inspect meu-app

A flag -d (detached) inicia o container em background, retornando o controle do terminal imediatamente. A flag -it (interactive + tty) permite interação direta com o container, abrindo um shell quando combinada com um comando como /bin/bash. A flag -p mapeia portas entre o host e o container, no formato porta_host:porta_container.

Buildando Imagens com a CLI

Criar imagens Docker também é feito através da CLI. Um Dockerfile é um arquivo de texto que contém instruções para construir uma imagem. Aqui está um exemplo prático:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV FLASK_APP=app.py
ENV FLASK_ENV=production

EXPOSE 5000

CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]

Para construir a imagem e executar o container:

# Construir a imagem
docker build -t minha-app-flask:1.0 .

# Executar um container baseado na imagem
docker run -d -p 5000:5000 --name app-flask minha-app-flask:1.0

# Verificar se está rodando
docker ps

A flag -t especifica uma tag para a imagem no formato nome:versao. O ponto final (.) indica que o Dockerfile está no diretório atual. O Docker irá executar cada instrução do Dockerfile sequencialmente, criando camadas (layers) que são cacheadas para builds futuros.

Ciclo de Vida de Containers

O ciclo de vida de um container é uma sequência bem definida de estados pelos quais passa desde sua criação até sua remoção. Compreender esse ciclo é essencial para gerenciar containers efetivamente em produção. Um container passa por vários estados: Created, Running, Paused, Stopped e Deleted.

Quando você executa docker run, ocorre uma sequência de operações: primeiro a imagem é baixada (se não existir localmente), depois um container é criado a partir dessa imagem, e finalmente o container é iniciado executando o comando especificado. Se nenhum comando for fornecido, o container usará o comando padrão definido na imagem (ENTRYPOINT ou CMD).

Estados e Transições

Um container começa no estado Created quando é criado mas não iniciado. Quando você executa docker start, ele transita para Running, onde o processo principal está executando. Do estado Running, você pode pausar o container com docker pause, movendo-o para Paused. Um container pausado pode ser retomado com docker unpause.

# Demonstração do ciclo de vida
docker create --name meu-container nginx:latest
# Container agora está em Created

docker start meu-container
# Container agora está em Running

docker pause meu-container
# Container agora está em Paused

docker unpause meu-container
# Container volta para Running

docker stop meu-container
# Container agora está em Stopped

docker rm meu-container
# Container é removido (estado Deleted)

Quando você executa docker stop, o Docker envia um sinal SIGTERM para o processo principal do container, dando-lhe tempo para encerrar gracefully. Se o container não parar dentro do timeout padrão (10 segundos), o Docker envia SIGKILL para forçar o término. Você pode customizar esse timeout com a flag --time.

Gerenciamento do Ciclo de Vida em Aplicações Reais

Em aplicações reais, você frequentemente precisa garantir que containers reiniciem automaticamente em caso de falha. O Docker oferece políticas de restart para isso:

# Restart sempre que o container parar
docker run -d --restart always --name api-service nginx

# Restart apenas se saiu com código de erro (não-zero)
docker run -d --restart on-failure --name worker nginx

# Restart com máximo de tentativas
docker run -d --restart on-failure:5 --name worker nginx

# Restart com delay entre tentativas
docker run -d --restart on-failure:5 --restart-delay 5s --name worker nginx

A política always é útil para serviços que devem estar sempre disponíveis, enquanto on-failure é melhor para jobs que podem completar com sucesso e não devem ser reiniciados continuamente. Você também pode combinar essas opções com --health-cmd para realizar health checks e permitir que o Docker tome decisões mais inteligentes sobre quando reiniciar.

Exemplo Completo: Ciclo de Vida com Health Checks

Um padrão robusto em produção é combinar restart policies com health checks:

docker run -d \
  --name banco-dados \
  --restart on-failure:3 \
  --health-cmd="curl -f http://localhost:8000/health || exit 1" \
  --health-interval=30s \
  --health-timeout=5s \
  --health-retries=3 \
  -p 8000:8000 \
  minha-api:1.0

O health check executa o comando especificado a cada 30 segundos. Se o comando retornar sucesso (código 0), o container é considerado saudável. Se falhar 3 vezes consecutivas, o container é marcado como unhealthy. Combinado com a política restart, isso garante que containers problemáticos sejam automaticamente reiniciados.

Para verificar o status de saúde:

# Ver status do health check
docker inspect --format='{{json .State.Health}}' banco-dados | jq .

# Exemplo de saída:
# {
#   "Status": "healthy",
#   "FailingStreak": 0,
#   "Log": [...]
# }

Conclusão

Dominar Docker Engine requer compreensão de três elementos interdependentes: a arquitetura em camadas que separa responsabilidades entre CLI, daemon, containerd e runc; o daemon como orquestrador central que gerencia estado e comunica com o sistema operacional; e finalmente o ciclo de vida de containers como mecanismo essencial para construir aplicações resilientes. Esses conceitos formam a base sólida necessária para avançar em tópicos como orquestração com Kubernetes, deploy em produção e otimização de performance. A prática constante com a CLI e a compreensão profunda de como o daemon funciona transformarão você de um usuário casual para um profissional capaz de diagnosticar problemas complexos e arquitetar soluções robustas.

Referências


Artigos relacionados