<h2>Introdução aos Probes no Kubernetes</h2>
<p>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.</p>
<p>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.</p>
<h2>Liveness Probe: A Aplicação Está Viva?</h2>
<h3>O Conceito</h3>
<p>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.</p>
<p>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.</p>
<h3>Exemplo Prático: HTTP Liveness Probe</h3>
<p>Criaremos uma aplicação Python simples que simula um problema depois de alguns segundos:</p>
<pre><code class="language-python">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)</code></pre>
<p>Agora, o manifesto Kubernetes configurando o Liveness Probe:</p>
<pre><code class="language-yaml">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</code></pre>
<p>O <code>initialDelaySeconds: 10</code> dá tempo para a aplicação iniciar. O <code>periodSeconds: 5</code> faz o Kubernetes verificar a cada 5 segundos. Após 3 falhas consecutivas (<code>failureThreshold: 3</code>), 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.</p>
<h3>TCP Liveness Probe</h3>
<p>Para aplicações que não expõem HTTP, você pode usar TCP:</p>
<pre><code class="language-yaml">livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3</code></pre>
<p>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.</p>
<h3>Exec Probe</h3>
<p>Você também pode executar comandos personalizados:</p>
<pre><code class="language-yaml">livenessProbe:
exec:
command:
- /bin/sh
- -c
- ps aux | grep -q "python" && exit 0 || exit 1
initialDelaySeconds: 10
periodSeconds: 10</code></pre>
<h2>Readiness Probe: A Aplicação Está Pronta?</h2>
<h3>O Conceito</h3>
<p>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.</p>
<p>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.</p>
<h3>Exemplo Prático: Readiness com Dependência de Banco de Dados</h3>
<p>Aqui temos uma aplicação Go que depende de uma conexão PostgreSQL:</p>
<pre><code class="language-go">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)
}</code></pre>
<p>O manifesto Kubernetes:</p>
<pre><code class="language-yaml">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</code></pre>
<p>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.</p>
<h3>Padrão: Readiness e Liveness Combinados</h3>
<p>Na prática, você quase sempre vai usar ambos juntos. O Readiness Probe é geralmente mais tolerante (usa <code>initialDelaySeconds</code> maior e <code>failureThreshold</code> maior), enquanto o Liveness é mais rigoroso. O padrão comum é:</p>
<ul>
<li><strong>Readiness</strong>: detecta se o serviço está pronto para servir</li>
<li><strong>Liveness</strong>: detecta se o serviço travou completamente</li>
</ul>
<p>Um pod pode estar "alive" mas "not ready", e isso é perfeitamente normal durante inicialização.</p>
<h2>Startup Probe: A Aplicação Finalmente Iniciou?</h2>
<h3>O Conceito</h3>
<p>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.</p>
<p>O problema é: se você configura um Liveness Probe com <code>initialDelaySeconds: 300</code>, 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 é <strong>ignorado</strong>. Uma vez que o Startup Probe passa, ele ativa o Liveness Probe.</p>
<h3>Exemplo Prático: Aplicação Java com Inicialização Lenta</h3>
<p>Uma aplicação Spring Boot clássica que demora para iniciar:</p>
<pre><code class="language-java">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\"}";
}
}</code></pre>
<p>O manifesto com os três probes:</p>
<pre><code class="language-yaml">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</code></pre>
<p>Com <code>failureThreshold: 30</code> e <code>periodSeconds: 5</code>, 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.</p>
<h3>Quando Usar Startup Probe</h3>
<p>Use Startup Probe quando:</p>
<ul>
<li>Sua aplicação leva mais de 1-2 minutos para iniciar</li>
<li>Você tem operações custosas no <code>__init__</code> ou no <code>main()</code></li>
<li>Você precisa distinguir entre "ainda inicializando" e "realmente travado"</li>
</ul>
<p>Se sua aplicação inicia em segundos, não precisa disso. Mantenha simples.</p>
<h2>Boas Práticas e Armadilhas Comuns</h2>
<h3>Timeouts Apropriados</h3>
<p>Um erro comum é configurar <code>timeoutSeconds</code> muito curto. Se seu serviço recebe requisições normalmente em 500ms mas está sob carga e demora 2 segundos, um <code>timeoutSeconds: 1</code> causará falsos positivos. Use <code>timeoutSeconds: 2</code> ou <code>3</code> como padrão e ajuste baseado em observações reais.</p>
<pre><code class="language-yaml">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</code></pre>
<h3>Endpoints de Health Devem Ser Rápidos</h3>
<p>Seu endpoint <code>/health/ready</code> 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:</p>
<pre><code class="language-python"></code></pre>
<h3>Diferenças Entre os Probes</h3>
<p>Muitos iniciantes confundem os probes. Aqui uma tabela clara:</p>
<div class="table-wrap"><table><thead><tr><th>Probe</th><th>Pergunta</th><th>Falha = ?</th><th>Caso de Uso</th></tr></thead><tbody><tr><td><strong>Liveness</strong></td><td>Ainda está vivo?</td><td>Reinicia</td><td>Detectar travamentos</td></tr><tr><td><strong>Readiness</strong></td><td>Pronto para tráfego?</td><td>Remove do serviço</td><td>Inicialização, manutenção</td></tr><tr><td><strong>Startup</strong></td><td>Já iniciou?</td><td>Ignora Liveness</td><td>Apps que demoram para iniciar</td></tr></tbody></table></div>
<h3>Monitoramento e Logs</h3>
<p>Sempre verifique os logs de restart e eventos do pod:</p>
<pre><code class="language-bash"># 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'</code></pre>
<p>Se você vê muitos restarts, seu Liveness Probe pode estar muito agressivo. Se o tráfego cai subitamente, seu Readiness Probe pode estar falhando.</p>
<h2>Conclusão</h2>
<p>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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" target="_blank" rel="noopener noreferrer">Kubernetes Documentation: Pod Lifecycle</a></li>
<li><a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/" target="_blank" rel="noopener noreferrer">Kubernetes Documentation: Configure Liveness, Readiness and Startup Probes</a></li>
<li><a href="https://12factor.net/processes" target="_blank" rel="noopener noreferrer">The Twelve-Factor App: Processes (relevante para health checks)</a></li>
<li><a href="https://sre.google/sre-book/monitoring-distributed-systems/" target="_blank" rel="noopener noreferrer">SRE Book: Monitoring Distributed Systems</a></li>
<li><a href="https://docs.docker.com/develop/health/" target="_blank" rel="noopener noreferrer">Container Best Practices: Health Checks</a></li>
</ul>
<p><!-- FIM --></p>