Introdução aos Probes no Kubernetes
Quando você deploya uma aplicação no Kubernetes, o cluster precisa saber se seu contêiner está saudável, pronto para receber tráfego e capaz de se recuperar de falhas. Os probes são mecanismos de health check nativos do Kubernetes que monitoram o estado de seus pods em tempo real. Sem eles, você pode ter contêineres falhando silenciosamente enquanto continuam recebendo requisições, degradando a qualidade do serviço.
Existem três tipos de probes: Liveness, Readiness e Startup. Cada um responde a uma pergunta diferente sobre o estado do seu pod. Entender quando usar cada um é fundamental para ter uma arquitetura confiável. Vou guiá-lo através dos conceitos, mostrando exemplos práticos que você pode usar imediatamente em seus projetos.
Liveness Probe: A Aplicação Está Viva?
O Conceito
O Liveness Probe responde à pergunta: "Minha aplicação ainda está rodando?". Se o probe falhar repetidamente, o Kubernetes assume que o contêiner entrou em estado irrecuperável e o reinicia automaticamente. Isso é útil para lidar com deadlocks, loops infinitos ou travamentos que não causam saída do processo.
Imagine um cenário onde sua aplicação consome memória indefinidamente, o processo continua rodando mas não responde mais. O Liveness Probe detectaria isso e o kubelet reiniciaria o contêiner. É importante notar que um restart do pod é uma ação drástica — você está dizendo que prefere começar do zero a continuar com aquele estado.
Exemplo Prático: HTTP Liveness Probe
Criaremos uma aplicação Python simples que simula um problema depois de alguns segundos:
from flask import Flask, jsonify
import time
import threading
app = Flask(__name__)
start_time = time.time()
is_healthy = True
def simulate_failure():
global is_healthy
time.sleep(30) # Falha após 30 segundos
is_healthy = False
@app.route('/health/live', methods=['GET'])
def liveness():
if is_healthy:
return jsonify({"status": "alive"}), 200
return jsonify({"status": "dead"}), 500
@app.route('/api/data', methods=['GET'])
def get_data():
return jsonify({"data": "important"}), 200
if __name__ == '__main__':
threading.Thread(target=simulate_failure, daemon=True).start()
app.run(host='0.0.0.0', port=5000)
Agora, o manifesto Kubernetes configurando o Liveness Probe:
apiVersion: v1
kind: Pod
metadata:
name: liveness-example
spec:
containers:
- name: python-app
image: python-liveness:1.0
ports:
- containerPort: 5000
livenessProbe:
httpGet:
path: /health/live
port: 5000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
O initialDelaySeconds: 10 dá tempo para a aplicação iniciar. O periodSeconds: 5 faz o Kubernetes verificar a cada 5 segundos. Após 3 falhas consecutivas (failureThreshold: 3), o contêiner é reiniciado. Este é um cenário realista: você quer detectar problemas rapidamente, mas sem ser tão agressivo que reinicie containers saudáveis por causa de picos temporários de latência.
TCP Liveness Probe
Para aplicações que não expõem HTTP, você pode usar TCP:
livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
Isso tenta estabelecer uma conexão TCP na porta 5432. Se conseguir, a aplicação está viva. Perfeito para bancos de dados ou serviços que não têm endpoints HTTP.
Exec Probe
Você também pode executar comandos personalizados:
livenessProbe:
exec:
command:
- /bin/sh
- -c
- ps aux | grep -q "python" && exit 0 || exit 1
initialDelaySeconds: 10
periodSeconds: 10
Readiness Probe: A Aplicação Está Pronta?
O Conceito
O Readiness Probe responde: "Minha aplicação está pronta para receber tráfego?". Diferente do Liveness, um Readiness Probe que falha não reinicia o pod — ele simplesmente remove o pod da lista de endpoints do serviço. Isso é crucial em cenários onde a aplicação está viva, mas ainda inicializando recursos, conectando ao banco de dados, ou aquecendo caches.
Um exemplo clássico é uma aplicação Java que precisa carregar configurações, conectar ao banco de dados e pré-compilar queries. Durante esse período, o processo está rodando, mas não pode servir requisições. O Readiness Probe evita que requisições sejam roteadas para ele nesse meio-tempo.
Exemplo Prático: Readiness com Dependência de Banco de Dados
Aqui temos uma aplicação Go que depende de uma conexão PostgreSQL:
package main
import (
"database/sql"
"fmt"
"net/http"
"time"
_ "github.com/lib/pq"
)
var db *sql.DB
var isReady = false
func init() {
go initializeDatabase()
}
func initializeDatabase() {
var err error
// Tenta conectar por até 60 segundos
for i := 0; i < 60; i++ {
db, err = sql.Open("postgres", "user=app password=pwd host=postgres port=5432 dbname=myapp sslmode=disable")
if err == nil && db.Ping() == nil {
isReady = true
fmt.Println("Database connection established")
return
}
time.Sleep(time.Second)
}
fmt.Println("Failed to connect to database")
}
func readinessHandler(w http.ResponseWriter, r *http.Request) {
if !isReady {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("Not ready"))
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Ready"))
}
func dataHandler(w http.ResponseWriter, r *http.Request) {
if !isReady {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"data":"value"}`))
}
func main() {
http.HandleFunc("/health/ready", readinessHandler)
http.HandleFunc("/api/data", dataHandler)
http.ListenAndServe(":8080", nil)
}
O manifesto Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-app
spec:
replicas: 3
selector:
matchLabels:
app: go-app
template:
metadata:
labels:
app: go-app
spec:
containers:
- name: go-app
image: go-app:1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
- name: postgres
image: postgres:14
env:
- name: POSTGRES_PASSWORD
value: pwd
Neste exemplo, o Readiness Probe espera 5 segundos antes de começar a verificar, dando tempo para conectar ao banco. Se falhar 3 vezes, o pod é removido do serviço, mas continua rodando. Quando o banco volta, ele fica pronto novamente e volta a receber tráfego. Isso é muito melhor que derrubar o pod.
Padrão: Readiness e Liveness Combinados
Na prática, você quase sempre vai usar ambos juntos. O Readiness Probe é geralmente mais tolerante (usa initialDelaySeconds maior e failureThreshold maior), enquanto o Liveness é mais rigoroso. O padrão comum é:
- Readiness: detecta se o serviço está pronto para servir
- Liveness: detecta se o serviço travou completamente
Um pod pode estar "alive" mas "not ready", e isso é perfeitamente normal durante inicialização.
Startup Probe: A Aplicação Finalmente Iniciou?
O Conceito
O Startup Probe é um tipo mais recente de probe (adicionado no Kubernetes 1.16) que resolve um problema específico: aplicações com tempo de inicialização muito longo. Pense em uma aplicação que leva 5 minutos para iniciar por precisar compilar código, migrar banco de dados, ou carregar terabytes em memória.
O problema é: se você configura um Liveness Probe com initialDelaySeconds: 300, você tem que esperar 5 minutos antes de receber alertas sobre um contêiner realmente travado. O Startup Probe resolve isso. Enquanto ele falha, o Liveness Probe é ignorado. Uma vez que o Startup Probe passa, ele ativa o Liveness Probe.
Exemplo Prático: Aplicação Java com Inicialização Lenta
Uma aplicação Spring Boot clássica que demora para iniciar:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
@SpringBootApplication
@RestController
public class SlowStartApp {
private static final long START_TIME = System.currentTimeMillis();
private static final long STARTUP_DURATION_MS = 120000; // 2 minutos
public static void main(String[] args) {
// Simula operações custosas de inicialização
try {
System.out.println("Starting database migrations...");
Thread.sleep(60000); // 60 segundos
System.out.println("Loading caches...");
Thread.sleep(60000); // 60 segundos
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
SpringApplication.run(SlowStartApp.class, args);
}
@GetMapping("/health/startup")
public String startup() {
long elapsed = System.currentTimeMillis() - START_TIME;
if (elapsed < STARTUP_DURATION_MS) {
// Retorna 503 enquanto ainda está inicializando
return "Starting up: " + elapsed + "ms";
}
return "Startup complete";
}
@GetMapping("/health/live")
public String liveness() {
return "Alive";
}
@GetMapping("/health/ready")
public String readiness() {
return "Ready";
}
@GetMapping("/api/data")
public String getData() {
return "{\"data\": \"value\"}";
}
}
O manifesto com os três probes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-slow-start
spec:
replicas: 1
selector:
matchLabels:
app: java-slow-start
template:
metadata:
labels:
app: java-slow-start
spec:
containers:
- name: java-app
image: java-slow-start:1.0
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30
periodSeconds: 5
# Pode levar até 150 segundos (30 * 5) para iniciar
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
Com failureThreshold: 30 e periodSeconds: 5, você tem até 150 segundos para o aplicativo iniciar. Uma vez que o Startup Probe passa, o Liveness e Readiness assumem o monitoramento. Se um deles falhar depois, o pod é reiniciado — mas você não perde tempo esperando startup novamente.
Quando Usar Startup Probe
Use Startup Probe quando:
- Sua aplicação leva mais de 1-2 minutos para iniciar
- Você tem operações custosas no __init__ ou no main()
- Você precisa distinguir entre "ainda inicializando" e "realmente travado"
Se sua aplicação inicia em segundos, não precisa disso. Mantenha simples.
Boas Práticas e Armadilhas Comuns
Timeouts Apropriados
Um erro comum é configurar timeoutSeconds muito curto. Se seu serviço recebe requisições normalmente em 500ms mas está sob carga e demora 2 segundos, um timeoutSeconds: 1 causará falsos positivos. Use timeoutSeconds: 2 ou 3 como padrão e ajuste baseado em observações reais.
readinessProbe:
httpGet:
path: /health/ready
port: 8080
timeoutSeconds: 3 # Espere até 3 segundos
periodSeconds: 10 # Verifique a cada 10 segundos
failureThreshold: 2 # 2 falhas = remove do serviço
Endpoints de Health Devem Ser Rápidos
Seu endpoint /health/ready não deve fazer operações custosas. Se ele consultar o banco de dados toda vez e o banco estiver lento, você terá problemas. Mantenha um estado local booleano que é atualizado em background:
# ❌ ERRADO - lento
@app.route('/health/ready')
def readiness():
result = db.execute("SELECT 1") # Bloqueia
return "ready", 200
# ✅ CORRETO - rápido
is_db_healthy = True
def check_db_health():
global is_db_healthy
while True:
try:
db.execute("SELECT 1")
is_db_healthy = True
except:
is_db_healthy = False
time.sleep(5)
@app.route('/health/ready')
def readiness():
if is_db_healthy:
return "ready", 200
return "not ready", 503
threading.Thread(target=check_db_health, daemon=True).start()
Diferenças Entre os Probes
Muitos iniciantes confundem os probes. Aqui uma tabela clara:
| Probe | Pergunta | Falha = ? | Caso de Uso |
|---|---|---|---|
| Liveness | Ainda está vivo? | Reinicia | Detectar travamentos |
| Readiness | Pronto para tráfego? | Remove do serviço | Inicialização, manutenção |
| Startup | Já iniciou? | Ignora Liveness | Apps que demoram para iniciar |
Monitoramento e Logs
Sempre verifique os logs de restart e eventos do pod:
# Ver eventos do pod
kubectl describe pod meu-pod
# Ver logs de restart
kubectl logs meu-pod --previous
# Ver todas as mudanças de estado
kubectl get events --sort-by='.lastTimestamp'
Se você vê muitos restarts, seu Liveness Probe pode estar muito agressivo. Se o tráfego cai subitamente, seu Readiness Probe pode estar falhando.
Conclusão
Os três tipos de probes — Liveness, Readiness e Startup — são ferramentas essenciais para ter um Kubernetes confiável em produção. Liveness Probe reinicia quando necessário, Readiness Probe gerencia o tráfego, e Startup Probe pacienta com inicializações lentas. Use-os juntos: configure Readiness e Liveness como padrão, adicione Startup apenas se sua app demora muito para iniciar. Ajuste os timeouts e thresholds baseado em comportamento real, não em suposições. Finalmente, mantenha seus endpoints de health simples e rápidos — eles são consultados frequentemente e não devem ser gargalos.