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
- Kubernetes Official Documentation: Configure Liveness, Readiness and Startup Probes — Documentação oficial do Kubernetes sobre configuração de probes com exemplos práticos de YAML
- Google Cloud Blog: Health Checks in Microservices — Artigo técnico da Google Cloud sobre padrões de health checks em sistemas distribuídos
- Martin Fowler: Microservices Health Checks — Artigo clássico de Martin Fowler sobre design de endpoints de health check
- Red Hat Developer: Liveness and Readiness Probes Best Practices — Guia de melhores práticas para probes da Red Hat
- CNCF Blog: Observability for Kubernetes Health Checks — Artigo da CNCF sobre monitoramento e observabilidade de health checks
- Istio Documentation: Health Checking for Service Mesh — Documentação do Istio sobre health checks em malha de serviço
- Prometheus Blog: Monitoring Kubernetes Probes — Tutorial sobre métricas de probes no Prometheus