Docker & Kubernetes

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

15 min de leitura

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

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

<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: &quot;Minha aplicação ainda está rodando?&quot;. 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(&#039;/health/live&#039;, methods=[&#039;GET&#039;])

def liveness():

if is_healthy:

return jsonify({&quot;status&quot;: &quot;alive&quot;}), 200

return jsonify({&quot;status&quot;: &quot;dead&quot;}), 500

@app.route(&#039;/api/data&#039;, methods=[&#039;GET&#039;])

def get_data():

return jsonify({&quot;data&quot;: &quot;important&quot;}), 200

if __name__ == &#039;__main__&#039;:

threading.Thread(target=simulate_failure, daemon=True).start()

app.run(host=&#039;0.0.0.0&#039;, 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 &quot;python&quot; &amp;&amp; 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: &quot;Minha aplicação está pronta para receber tráfego?&quot;. 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 (

&quot;database/sql&quot;

&quot;fmt&quot;

&quot;net/http&quot;

&quot;time&quot;

_ &quot;github.com/lib/pq&quot;

)

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 &lt; 60; i++ {

db, err = sql.Open(&quot;postgres&quot;, &quot;user=app password=pwd host=postgres port=5432 dbname=myapp sslmode=disable&quot;)

if err == nil &amp;&amp; db.Ping() == nil {

isReady = true

fmt.Println(&quot;Database connection established&quot;)

return

}

time.Sleep(time.Second)

}

fmt.Println(&quot;Failed to connect to database&quot;)

}

func readinessHandler(w http.ResponseWriter, r *http.Request) {

if !isReady {

w.WriteHeader(http.StatusServiceUnavailable)

w.Write([]byte(&quot;Not ready&quot;))

return

}

w.WriteHeader(http.StatusOK)

w.Write([]byte(&quot;Ready&quot;))

}

func dataHandler(w http.ResponseWriter, r *http.Request) {

if !isReady {

w.WriteHeader(http.StatusServiceUnavailable)

return

}

w.WriteHeader(http.StatusOK)

w.Write([]byte({&quot;data&quot;:&quot;value&quot;}))

}

func main() {

http.HandleFunc(&quot;/health/ready&quot;, readinessHandler)

http.HandleFunc(&quot;/api/data&quot;, dataHandler)

http.ListenAndServe(&quot;:8080&quot;, 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 &quot;alive&quot; mas &quot;not ready&quot;, 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(&quot;Starting database migrations...&quot;);

Thread.sleep(60000); // 60 segundos

System.out.println(&quot;Loading caches...&quot;);

Thread.sleep(60000); // 60 segundos

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

SpringApplication.run(SlowStartApp.class, args);

}

@GetMapping(&quot;/health/startup&quot;)

public String startup() {

long elapsed = System.currentTimeMillis() - START_TIME;

if (elapsed &lt; STARTUP_DURATION_MS) {

// Retorna 503 enquanto ainda está inicializando

return &quot;Starting up: &quot; + elapsed + &quot;ms&quot;;

}

return &quot;Startup complete&quot;;

}

@GetMapping(&quot;/health/live&quot;)

public String liveness() {

return &quot;Alive&quot;;

}

@GetMapping(&quot;/health/ready&quot;)

public String readiness() {

return &quot;Ready&quot;;

}

@GetMapping(&quot;/api/data&quot;)

public String getData() {

return &quot;{\&quot;data\&quot;: \&quot;value\&quot;}&quot;;

}

}</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 &quot;ainda inicializando&quot; e &quot;realmente travado&quot;</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=&#039;.lastTimestamp&#039;</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

GKE no GCP: Autopilot, Workload Identity e Cloud SQL Proxy: Do Básico ao Avançado
GKE no GCP: Autopilot, Workload Identity e Cloud SQL Proxy: Do Básico ao Avançado

GKE Autopilot: Gerenciamento Automático de Clusters Kubernetes O Google Kuber...

Boas Práticas de ArgoCD: GitOps Contínuo, App of Apps e Sync Policies para Times Ágeis
Boas Práticas de ArgoCD: GitOps Contínuo, App of Apps e Sync Policies para Times Ágeis

GitOps e ArgoCD: Fundamentos GitOps é um paradigma operacional onde o Git se...

O que Todo Dev Deve Saber sobre Dockerfile em Profundidade: Cada Instrução e seu Impacto no Build
O que Todo Dev Deve Saber sobre Dockerfile em Profundidade: Cada Instrução e seu Impacto no Build

Introdução: Por que entender Dockerfile é fundamental Um Dockerfile é um scri...