Guia Completo de Probes em Kubernetes: Liveness, Readiness e Startup na Prática Já leu

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

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.

Referências


Artigos relacionados