Pular para o conteúdo
9 min de leitura

Health Checks no Kubernetes: Liveness, Readiness e Startup Sem Confusão

Por Equipe Nebular ·

As três probes do Kubernetes fazem coisas diferentes, e confundi-las derruba produção. Entenda o que cada uma decide, como configurá-las e os erros que causam crash loops e cascatas.

Neste artigo

Poucas configurações do Kubernetes causam tanto estrago silencioso quanto probes de saúde mal ajustadas. Uma liveness agressiva demais reinicia pods saudáveis em loop. Uma readiness ausente manda tráfego para uma réplica que ainda está subindo, e o usuário recebe erro. Uma startup mal configurada mata a aplicação antes dela terminar de inicializar. Os três problemas têm a mesma raiz: tratar as probes como se fossem a mesma coisa, quando cada uma responde a uma pergunta diferente.

Health checks são o mecanismo pelo qual o Kubernetes descobre, sozinho, se um container está vivo, se está pronto para receber tráfego e se já terminou de inicializar. É a base da autocura da plataforma — mas só funciona se você disser a verdade a ela. Vamos separar as três probes, entender o que cada uma decide, e os padrões que evitam os desastres mais comuns.

As três perguntas, as três probes#

O Kubernetes oferece três tipos de probe, e a chave para nunca mais confundi-las é associar cada uma a uma pergunta:

  • Liveness — "O processo está travado e precisa ser reiniciado?" Se falha, o

Kubernetes mata e recria o container.

  • Readiness — "O container está pronto para receber tráfego agora?" Se falha,

o Kubernetes tira o pod dos endpoints do Service (para de mandar tráfego), mas não reinicia.

  • Startup — "A aplicação já terminou de inicializar?" Enquanto não passa, o

Kubernetes segura as outras duas probes, dando tempo para apps lentas subirem.

A confusão mais cara é usar liveness para responder à pergunta da readiness. Se sua liveness falha porque uma dependência externa está fora, o Kubernetes vai reiniciar seu pod — que não vai resolver nada, porque o problema é externo, e ainda cria um crash loop. Guarde a regra: liveness reinicia, readiness desconecta. Use cada uma para o que ela faz.

Liveness: só para deadlock, com parcimônia#

A liveness probe existe para um cenário específico: o processo está rodando, mas travado — um deadlock, um loop infinito, uma thread que não responde mais. Nesse caso, a única saída é reiniciar. A liveness é o mecanismo que detecta isso e aciona o restart.

``yaml livenessProbe: httpGet: path: /healthz # endpoint que checa só o processo, não dependências port: 8080 initialDelaySeconds: 10 periodSeconds: 15 timeoutSeconds: 2 failureThreshold: 3 # 3 falhas seguidas antes de reiniciar ``

O erro clássico é fazer o /healthz da liveness checar o banco, o cache, uma API externa. Parece cuidadoso, mas é uma armadilha: se o banco fica lento por trinta segundos, todas as réplicas falham a liveness ao mesmo tempo e o Kubernetes reinicia todas juntas — transformando uma degradação temporária em uma queda total, com um crash loop por cima. O endpoint da liveness deve responder "o processo está vivo e respondendo?" e nada mais. Se ele consegue devolver um 200, o processo não está travado. Ponto.

Readiness: o guardião do tráfego#

A readiness probe decide se o pod entra ou sai da rotação de tráfego do Service. É ela que deve considerar dependências — porque a pergunta aqui é "consigo atender uma requisição agora?", e se o banco está fora, a resposta é não.

``yaml readinessProbe: httpGet: path: /readyz # verifica dependências essenciais port: 8080 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 successThreshold: 1 ``

O ponto sutil: quando a readiness falha, o pod continua vivo, apenas para de receber tráfego. Assim que a dependência volta, a probe passa de novo e o pod retorna à rotação automaticamente, sem reinício, sem perda de estado. É exatamente o comportamento que você quer numa degradação: parar de mandar requisições para réplicas que não podem atendê-las, sem entrar em pânico e reiniciar tudo.

A readiness também é essencial durante o deploy. Quando um pod novo sobe, ele só começa a receber tráfego depois que a readiness passa. Isso, combinado com o rolling update, é o que garante que o Service nunca aponta para um pod que ainda não está pronto — a base do deploy sem downtime.

Uma nota de cautela sobre dependências na readiness: cheque apenas o que é essencial para atender. Se seu serviço precisa do banco para funcionar, cheque o banco. Mas se ele só usa um serviço de recomendação opcional, não coloque esse serviço na readiness — senão a queda de um componente não-crítico tira seu serviço inteiro do ar por engano.

Startup: a probe que dá paciência#

Aplicações que demoram para inicializar — uma JVM aquecendo, um cache que carrega na subida, uma migração que roda no boot — criam um dilema. Se você configura a liveness com um initialDelaySeconds grande o bastante para a app lenta subir, você perde a capacidade de detectar rápido um travamento em runtime. Se configura curto, a liveness mata a app antes dela terminar de inicializar, criando um crash loop eterno.

A startup probe resolve isso. Enquanto ela não passa, liveness e readiness ficam suspensas. Só depois que a startup confirma que a inicialização terminou é que as outras duas entram em ação com seus tempos normais e apertados.

``yaml startupProbe: httpGet: path: /healthz port: 8080 periodSeconds: 10 failureThreshold: 30 # 30 x 10s = até 5 minutos para subir livenessProbe: httpGet: path: /healthz port: 8080 periodSeconds: 15 failureThreshold: 3 # depois de pronto, detecta travamento em ~45s ``

Nesse exemplo, a aplicação tem até cinco minutos para inicializar (a startup tolera trinta falhas de dez segundos), mas, uma vez pronta, a liveness detecta um travamento em menos de um minuto. Você ganha os dois: paciência na subida, agilidade em runtime.

Os tipos de sonda#

Além do httpGet, o Kubernetes suporta outras formas de checar saúde, úteis para cargas que não são HTTP:

```yaml # TCP: só verifica se a porta aceita conexão. Bom para bancos, filas. readinessProbe: tcpSocket: port: 5432

# Exec: roda um comando dentro do container; saída 0 = sucesso. livenessProbe: exec: command: ["cat", "/tmp/healthy"]

# gRPC: para serviços que expõem o protocolo de health checking do gRPC. readinessProbe: grpc: port: 9000 ```

Escolha o tipo que reflete de verdade a saúde do serviço. Uma probe tcpSocket confirma que a porta abriu, mas não que a aplicação processa requisições — para um servidor web, o httpGet a um endpoint real é mais honesto.

Os parâmetros que decidem tudo#

Os campos numéricos das probes parecem detalhe, mas são onde a maioria dos problemas mora. Entender cada um evita ajustes no chute:

  • initialDelaySeconds: quanto esperar após o container subir antes da

primeira checagem. Com startup probe bem configurada, a liveness quase não precisa desse delay.

  • periodSeconds: intervalo entre checagens. Curto detecta problemas mais

rápido, mas gera mais carga.

  • timeoutSeconds: quanto esperar pela resposta antes de considerar a

checagem falha. Curto demais gera falsos negativos sob carga.

  • failureThreshold: quantas falhas seguidas antes de agir. Este é o que

evita reação a soluços momentâneos.

  • successThreshold: quantos sucessos seguidos para considerar recuperado

(relevante na readiness).

A regra de bolso: liveness tolerante, readiness responsiva. A liveness deve ter failureThreshold folgado para não reiniciar por um soluço passageiro (reiniciar é caro e disruptivo). A readiness pode ser mais sensível, porque tirar e recolocar um pod da rotação é barato e reversível.

Dois endpoints, dois propósitos#

Uma prática que resolve boa parte da confusão na origem é expor endpoints distintos para liveness e readiness, cada um respondendo à sua pergunta. O /healthz (liveness) verifica apenas se o processo está vivo e responsivo — é um handler trivial que devolve 200 sem tocar em nada externo. O /readyz (readiness) verifica as dependências essenciais para atender.

```go // Liveness: o processo consegue responder? Nada mais é checado. mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })

// Readiness: dá para atender uma requisição real agora? mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { if err := db.PingContext(r.Context()); err != nil { w.WriteHeader(http.StatusServiceUnavailable) // sai da rotação return } w.WriteHeader(http.StatusOK) }) ```

Separar os endpoints deixa a intenção explícita no código e evita o deslize de "melhorar" a liveness adicionando uma checagem de banco que, sem querer, transforma degradação em crash loop. Cada endpoint tem um propósito, e a diferença fica documentada na própria estrutura da aplicação.

Um detalhe importante do /readyz: durante o desligamento gracioso, ele deve passar a falhar de propósito assim que o processo recebe o SIGTERM. Isso sinaliza ao Kubernetes para tirar o pod da rotação antes de a aplicação começar a recusar conexões, evitando a janela em que o Service ainda manda tráfego para um pod que já começou a desligar.

Os antipadrões que derrubam produção#

Alguns erros aparecem com uma frequência que vale nomeá-los:

Liveness que checa dependências. Já dito, mas é o campeão. Transforma queda de dependência em crash loop de toda a frota. Liveness checa o processo, só.

Endpoint de health caro. Se o /healthz faz uma consulta pesada, roda lógica de negócio ou chama serviços externos, a própria checagem vira fonte de carga e de falha. O endpoint deve ser barato e rápido.

Probes iguais para as três. Apontar liveness, readiness e startup para o mesmo endpoint com a mesma lógica desperdiça a distinção que torna cada uma útil.

Ignorar o graceful shutdown. As probes trabalham junto com o desligamento gracioso. Quando um pod é removido, o Kubernetes primeiro o tira dos endpoints (via readiness/terminação) e só então envia SIGTERM. Se a aplicação não trata o SIGTERM drenando conexões, você perde requisições no meio, mesmo com probes perfeitas.

Fechando o ciclo da autocura#

Health checks bem configurados são o que transforma o Kubernetes de um agendador de containers em uma plataforma que se cura sozinha. A liveness recupera processos travados. A readiness protege o usuário de réplicas que não podem atender. A startup dá espaço para aplicações lentas sem sacrificar a detecção rápida depois.

O segredo não é decorar YAML, e sim manter na cabeça a pergunta que cada probe responde e agir de acordo: reiniciar só quando reiniciar resolve, desconectar quando o problema é temporário e externo, e ter paciência na subida sem perder a agilidade em runtime. Configure as três com intenção, teste o comportamento sob falha real — derrube uma dependência de propósito em homologação e veja se a frota degrada com elegância em vez de entrar em crash loop — e a plataforma vai cuidar do resto às três da manhã, enquanto você dorme.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly