Healthchecks em containers

1. Introdução aos Healthchecks: Por que sua aplicação precisa deles?

Healthchecks são mecanismos que permitem verificar se um container está realmente funcionando como esperado, e não apenas se o processo principal está em execução. Em ambientes de produção, um processo pode estar rodando mas a aplicação pode estar travada, sem responder a requisições, ou em estado degradado.

A diferença fundamental é entre:
- Healthcheck de processo: verifica apenas se o PID do processo principal existe (o que Docker faz por padrão)
- Healthcheck de aplicação: verifica se a aplicação responde corretamente a requisições, se consegue conectar ao banco, se a fila está acessível, etc.

Cenários onde a ausência de healthchecks causa falhas:
- Aplicação web que trava mas o processo Node.js continua rodando
- Banco que aceita conexões mas não processa queries corretamente
- Worker que consumiu toda a memória mas o processo ainda está ativo

Sem healthchecks, orquestradores como Docker Swarm e Kubernetes não conseguem tomar decisões inteligentes sobre quando reiniciar ou remover tráfego de um container.

2. Healthchecks no Docker: Comandos e Configurações

O Docker oferece a diretiva HEALTHCHECK no Dockerfile para definir verificações periódicas. A sintaxe básica:

HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost/ || exit 1

Opções disponíveis:
- --interval: tempo entre verificações (padrão 30s)
- --timeout: tempo máximo para o comando responder (padrão 30s)
- --retries: tentativas consecutivas antes de marcar como unhealthy (padrão 3)
- --start-period: tempo de inicialização antes de começar os checks (Docker 17.05+)

Existem duas formas de declarar o comando:
- CMD-SHELL: executa via shell (interpreta variáveis de ambiente, pipes)
- CMD: execução direta sem shell

Exemplo prático em um Dockerfile de aplicação Node.js:

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

EXPOSE 3000

HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=2 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/healthz || exit 1

CMD ["node", "server.js"]

Para inspecionar o status:

docker inspect --format='{{.State.Health.Status}}' container_name
docker ps --filter "health=unhealthy"

3. Implementando Healthchecks com Docker Compose

No Docker Compose, os healthchecks são configurados por serviço. Além disso, podemos usar depends_on com condition: service_healthy para controlar a ordem de inicialização.

Exemplo completo com PostgreSQL e API Node.js:

version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/healthz"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 10s

Dessa forma, o serviço api só será iniciado após o PostgreSQL passar no healthcheck, evitando erros de conexão no startup.

4. Healthchecks no Kubernetes: Probes Essenciais

Kubernetes oferece três tipos de probes para healthchecks:

  • livenessProbe: indica se o container está vivo. Se falhar, o kubelet reinicia o container
  • readinessProbe: indica se o container está pronto para receber tráfego. Se falhar, o Service remove o pod dos endpoints
  • startupProbe: indica se a aplicação iniciou. Protege containers lentos de serem reiniciados por livenessProbe

Exemplo de configuração com httpGet:

apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
  - name: app
    image: myapp:1.0
    ports:
    - containerPort: 8080
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10

Para aplicações que não expõem HTTP, podemos usar tcpSocket ou exec:

tcpSocket:
  port: 3306
exec:
  command:
  - cat
  - /tmp/healthy

5. Estratégias Avançadas de Healthchecks em Kubernetes

Parâmetros importantes para ajuste fino:

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30   # tempo antes do primeiro check
  periodSeconds: 10         # intervalo entre checks
  timeoutSeconds: 5         # timeout para cada check
  failureThreshold: 3       # falhas consecutivas para considerar unhealthy
  successThreshold: 1       # sucessos consecutivos para considerar healthy

Endpoint HTTP customizado:

Crie endpoints específicos no seu servidor:

# Express.js
app.get('/healthz', (req, res) => {
  // Verifica conexão com banco, cache, etc.
  if (dbConnected && cacheConnected) {
    res.status(200).send('OK');
  } else {
    res.status(503).send('Service Unavailable');
  }
});

app.get('/ready', (req, res) => {
  // Verifica se está pronto para receber tráfego
  if (warmupComplete) {
    res.status(200).send('Ready');
  } else {
    res.status(503).send('Not Ready');
  }
});

Boas práticas para evitar cascatas:
- Use startupProbe para aplicações com inicialização lenta (> 30s)
- Evite healthchecks que dependem de serviços externos no livenessProbe (use apenas no readinessProbe)
- Ajuste failureThreshold para evitar reinicializações por picos momentâneos
- Implemente circuit breaker no código para evitar thundering herd

6. Monitorando e Depurando Healthchecks

Para depurar probes no Kubernetes:

kubectl describe pod pod-name
kubectl get events --field-selector involvedObject.name=pod-name
kubectl logs pod-name --previous

Eventos comuns:
- Liveness probe failed: container será reiniciado
- Readiness probe failed: tráfego será removido
- Back-off restarting failed container: muitas reinicializações

Para métricas com Prometheus, exporte o status dos healthchecks:

# Exemplo de métrica customizada
# HELP app_health_status Current health status of the application
# TYPE app_health_status gauge
app_health_status{service="api"} 1

Ferramentas de monitoramento:
- Prometheus + Grafana: dashboards com métricas de restart, probe failures
- Kube-state-metrics: expõe métricas de estado dos pods
- Custom metrics: exponha healthchecks como métricas Prometheus

7. Casos de Uso e Exemplos Práticos

Aplicação web Nginx:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-health
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    livenessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 15
    readinessProbe:
      httpGet:
        path: /index.html
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5

Worker assíncrono com Redis:

livenessProbe:
  exec:
    command:
    - redis-cli
    - ping
  initialDelaySeconds: 20
  periodSeconds: 10
readinessProbe:
  exec:
    command:
    - sh
    - -c
    - "redis-cli ping && [ $(redis-cli dbsize) -gt 0 ]"
  initialDelaySeconds: 10
  periodSeconds: 15

Stack completa com Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: full-stack
spec:
  replicas: 3
  selector:
    matchLabels:
      app: full-stack
  template:
    metadata:
      labels:
        app: full-stack
    spec:
      containers:
      - name: api
        image: api:1.0
        ports:
        - containerPort: 3000
        livenessProbe:
          httpGet:
            path: /healthz
            port: 3000
          initialDelaySeconds: 15
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        startupProbe:
          httpGet:
            path: /startup
            port: 3000
          initialDelaySeconds: 0
          periodSeconds: 5
          failureThreshold: 30

8. Conclusão e Próximos Passos

Healthchecks são fundamentais para construir sistemas resilientes em containers. Eles permitem que orquestradores tomem decisões automáticas sobre reinicialização, roteamento de tráfego e escalabilidade, garantindo alta disponibilidade mesmo em cenários de falha parcial.

Pontos principais a reter:
- Sempre implemente healthchecks granulares (liveness + readiness + startup)
- Use endpoints HTTP customizados para verificar dependências internas
- Ajuste thresholds e delays conforme as características da sua aplicação
- Monitore os resultados dos healthchecks para identificar padrões de falha

Próximos passos sugeridos:
- Implemente healthchecks em todos os seus deployments
- Adicione métricas de healthcheck ao Prometheus
- Automatize testes de resiliência com ferramentas como Chaos Monkey
- Estude padrões de circuit breaker e retry com backoff

Referências