Health checks em microsserviços: liveness vs readiness

1. Fundamentos dos Health Checks em Microsserviços

1.1. O papel dos health checks na resiliência e auto-recuperação de sistemas distribuídos

Em arquiteturas de microsserviços, a resiliência não é opcional — é um requisito fundamental. Health checks são mecanismos que permitem que orquestradores como Kubernetes determinem se um contêiner está funcionando corretamente. Sem eles, um serviço que entrou em deadlock continuaria recebendo tráfego, causando degradação progressiva do sistema.

1.2. Diferença entre monitoramento tradicional e health checks orientados a orquestradores

O monitoramento tradicional (Prometheus, Datadog) coleta métricas para análise humana ou alertas. Já os health checks são sondas automatizadas que acionam ações corretivas imediatas: reinicialização de contêineres, remoção de balanceamento de carga ou bloqueio de tráfego. Enquanto o monitoramento responde "o que aconteceu?", os probes respondem "o que fazer agora?".

1.3. Ciclo de vida de um pod/container e a importância dos probes no Kubernetes

Um pod Kubernetes passa por estados: Pending, Running, Succeeded ou Failed. Os probes atuam durante o estado Running:
- Liveness probe: decide se o contêiner deve ser reiniciado
- Readiness probe: decide se o pod deve receber tráfego
- Startup probe (Kubernetes 1.18+): protege contêineres lentos na inicialização

2. Liveness Probe: Detectando Deadlocks e Estados Irrecuperáveis

2.1. Conceito de liveness: quando o processo está vivo mas não responde corretamente

Um processo pode estar em execução (PID ativo) mas incapaz de processar requisições — por exemplo, um deadlock em um pool de threads ou um vazamento de memória que congelou o garbage collector. A liveness probe detecta esses cenários.

2.2. Estratégias para implementar endpoints de liveness

Exemplo de endpoint /healthz em Go:

// healthz handler - verifica apenas se o servidor HTTP responde
func healthzHandler(w http.ResponseWriter, r *http.Request) {
    if !server.IsAlive() {
        w.WriteHeader(http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("ok"))
}

2.3. Riscos de falsos positivos e cascata de reinicializações desnecessárias

Uma liveness probe muito pesada (ex: verificar banco de dados a cada 5 segundos) pode causar:
- Reinicializações em cascata durante picos de latência
- Perda de estado em memória
- Amplificação de falhas (efeito dominó)

Regra prática: liveness deve verificar apenas a saúde do próprio processo, não de dependências externas.

3. Readiness Probe: Controle de Tráfego e Disponibilidade Funcional

3.1. Conceito de readiness: quando o serviço está pronto para receber requisições

Readiness indica se o serviço pode processar requisições no momento. Um serviço pode estar "vivo" mas "não pronto" — por exemplo, durante um cache warming ou após falha temporária de banco de dados.

3.2. Dependências externas (banco de dados, filas, APIs) como indicadores de readiness

Exemplo de endpoint /readyz em Python:

import redis, psycopg2

def readyz_handler():
    try:
        # Verifica conexão com Redis
        r = redis.Redis()
        r.ping()
        # Verifica conexão com PostgreSQL
        conn = psycopg2.connect("dbname=test")
        conn.close()
        return (200, "ready")
    except Exception as e:
        return (503, f"not ready: {str(e)}")

3.3. Como readiness afeta o balanceamento de carga e o roteamento de tráfego

Quando um pod falha na readiness probe:
- Kubernetes remove o pod do Endpoints do Service
- Service mesh (Istio, Linkerd) remove o pod do pool de balanceamento
- O tráfego é redirecionado automaticamente para pods saudáveis

4. Projetando Endpoints de Health Check Eficientes

4.1. Separação clara entre endpoints /healthz (liveness) e /readyz (readiness)

Nunca use o mesmo endpoint para ambos. A separação permite:
- Diferentes frequências de verificação
- Diferentes níveis de granularidade
- Isolamento de responsabilidades

4.2. Granularidade das verificações: evitar sobrecarga em cada probe

Estratégia recomendada:
- Liveness: verificação leve (status 200/503)
- Readiness: verificação moderada (dependências críticas)
- Health check completo: endpoint separado /status para debug humano

4.3. Cache de estado e polling inteligente para reduzir latência

Implemente cache com TTL para evitar verificações repetitivas:

// Cache de estado com expiração de 30 segundos
type HealthCache struct {
    state    bool
    lastCheck time.Time
}

func (c *HealthCache) IsReady() bool {
    if time.Since(c.lastCheck) < 30*time.Second {
        return c.state
    }
    c.state = c.checkDependencies()
    c.lastCheck = time.Now()
    return c.state
}

5. Configuração de Probes no Kubernetes: Parâmetros Essenciais

5.1. initialDelaySeconds, periodSeconds, timeoutSeconds e failureThreshold

  • initialDelaySeconds: tempo antes da primeira verificação (evita reinicialização prematura)
  • periodSeconds: intervalo entre verificações (default 10s)
  • timeoutSeconds: tempo máximo para resposta (default 1s)
  • failureThreshold: número de falhas consecutivas para considerar falha (default 3)

5.2. Diferenças entre HTTP, TCP e command probes

  • HTTP probe: mais comum, verifica código de status HTTP (200-399 = sucesso)
  • TCP probe: verifica se porta está ouvindo (útil para serviços que não expõem HTTP)
  • Command probe: executa comando dentro do contêiner (flexível, mas mais pesado)

5.3. Exemplo prático de manifesto YAML com liveness e readiness configurados

apiVersion: v1
kind: Pod
metadata:
  name: my-service
spec:
  containers:
  - name: app
    image: myapp:1.0
    ports:
    - containerPort: 8080
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 15
      timeoutSeconds: 2
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /readyz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
      timeoutSeconds: 3
      failureThreshold: 2

6. Estratégias Avançadas e Padrões de Implementação

6.1. Health checks em ambientes multi-tenancy com isolamento por schema

Em bancos multi-tenant, a readiness probe deve verificar a conectividade de todos os schemas ativos. Use verificações paralelas com timeout global:

func checkMultiTenantReadiness() bool {
    tenants := getActiveTenants()
    results := make(chan bool, len(tenants))
    for _, t := range tenants {
        go func(tenant string) {
            results <- checkTenantDB(tenant)
        }(t)
    }
    for i := 0; i < len(tenants); i++ {
        if !<-results {
            return false
        }
    }
    return true
}

6.2. Integração com máquinas de estado persistentes para workflows assíncronos

Workflows longos (ex: processamento de vídeo) podem exigir readiness baseada em estado da máquina de estados:

type WorkflowState string
const (
    StateIdle     WorkflowState = "idle"
    StateProcessing WorkflowState = "processing"
    StateDegraded WorkflowState = "degraded"
)

func readinessForWorkflow(state WorkflowState) bool {
    return state == StateIdle || state == StateProcessing
}

6.3. Policy as Code (OPA) para validar regras de health check em pipelines de CI/CD

Use Open Policy Agent para garantir que todo deployment tenha probes configuradas:

package kubernetes.admission

deny[msg] {
    input.kind == "Deployment"
    container := input.spec.template.spec.containers[_]
    not container.livenessProbe
    msg := sprintf("Container %v must have livenessProbe", [container.name])
}

7. Monitoramento, Alertas e Observabilidade dos Probes

7.1. Métricas chave: taxa de falhas de probes, tempo de recuperação e reinicializações

Métricas essenciais para Prometheus:
- kube_pod_status_ready: pods prontos vs não prontos
- kube_pod_container_status_restarts_total: contagem de reinicializações
- probe_duration_seconds: latência das verificações

7.2. Logs estruturados e rastreamento distribuído para diagnósticos

Estruture logs com campos padronizados:

{
  "level": "warn",
  "probe": "readiness",
  "service": "payment-service",
  "dependency": "postgresql",
  "error": "connection timeout",
  "duration_ms": 2500,
  "timestamp": "2024-01-15T10:30:00Z"
}

7.3. Criação de dashboards e alertas proativos (ex: flapping de readiness)

Alerta crítico: readiness flapping (alternância entre ready/not-ready mais de 5 vezes em 5 minutos)

# Alerta Prometheus para readiness flapping
- alert: ReadinessFlapping
  expr: changes(kube_pod_status_ready{condition="true"}[5m]) > 5
  for: 2m
  annotations:
    summary: "Pod {{ $labels.pod }} está alternando readiness rapidamente"

Conclusão

Health checks são a espinha dorsal da auto-recuperação em microsserviços. A distinção entre liveness (o processo está vivo?) e readiness (o serviço está funcional?) permite que orquestradores tomem decisões precisas: reiniciar contêineres travados ou apenas redirecionar tráfego durante degradações temporárias.

Implementar probes eficientes requer:
1. Endpoints separados com granularidade adequada
2. Cache de estado para evitar sobrecarga
3. Configuração cuidadosa dos parâmetros de timeout e thresholds
4. Monitoramento contínuo das métricas de probe

Ao dominar esses conceitos, você transforma seus microsserviços de frágeis para resilientes, capazes de se auto-curar e manter a disponibilidade mesmo sob condições adversas.

Referências