Health Check a Falhar, mas a Aplicação Funciona
Quando um health check falha durante o deployment, embora a aplicação funcione localmente, a causa costuma estar num de cinco pontos. Verifique binding, path, port, timing e dependências pela ordem que permite encontrar o problema mais rapidamente.
Existe um tipo particularmente frustrante de bloqueio: um health check a falhar no deployment impede todos os releases, embora a aplicação esteja, por qualquer métrica que consiga verificar, completamente funcional. Funciona localmente. Funciona localmente em Docker. Os logs mostram que está à escuta. E a plataforma reporta falha após falha, por vezes vinte seguidas, sem que nenhum pedido apareça no access log.
Este último detalhe é importante e reduz imediatamente o número de hipóteses. Se a aplicação nunca registou o pedido, o check nunca chegou à aplicação — por isso, nada no código da aplicação vai explicar o problema.
Estas são as cinco causas, pela ordem que permite encontrar o problema mais rapidamente.
1. Está associado a localhost
Esta é a causa mais comum e explica exatamente o sintoma de que “nunca chega tráfego”.
Dentro de um container, 127.0.0.1 significa o loopback do próprio container. Um health check que chega de fora do container não consegue alcançá-lo. O processo está à escuta, os logs confirmam-no e o socket está inacessível a partir de qualquer lugar que realmente importe.
// Unreachable from outside the container
app.listen(3000, '127.0.0.1')
// Correct
app.listen(3000, '0.0.0.0')
Os frameworks têm comportamentos predefinidos diferentes e vários alteraram o valor predefinido entre versões principais. Confirme a que endereço o seu framework está realmente associado, em vez de confiar no que se lembra.
# Confirm from inside the running container
dockup exec "ss -ltn || netstat -ltn" my-project/my-api
Se o endereço à escuta for 127.0.0.1:3000 em vez de 0.0.0.0:3000, encontrou o problema e nada mais nesta lista importa.
2. A port que a plataforma verifica não é a port onde serve
Estão envolvidas duas ports e é fácil confundi-las: a port onde o processo está à escuta dentro do container e a port para a qual a plataforma encaminha o tráfego. Se a aplicação lê PORT a partir do ambiente e definiu 3000 diretamente num Dockerfile, as duas podem divergir silenciosamente.
O padrão fiável é deixar que a plataforma lhe indique o valor:
const port = process.env.PORT || 3000
app.listen(port, '0.0.0.0')
Depois, defina a port do serviço uma única vez, na plataforma, e deixe de manter o número em dois locais.
3. O path devolve algo que não é um sucesso
Um path de health check é correspondido exatamente e um número surpreendente de falhas deve-se a um redirect. Se a aplicação redirecionar /healthz para /healthz/ ou forçar HTTPS com um 301, um checker que considera apenas respostas 2xx como sucesso falhará sempre, enquanto um browser segue o redirect e mostra uma página funcional.
Três armadilhas específicas:
- Redirects causados pela barra final.
/healthz→/healthz/é um 301. - HTTPS forçado. O check interno chega normalmente através de HTTP simples no loopback. Um redirect incondicional para HTTPS faz com que falhe.
- Middleware de autenticação. Um guard de autenticação global executado antes do routing também devolverá 401 para o health path.
Exclua explicitamente o health path da autenticação e da imposição de HTTPS. É a única route que deve ser aborrecida.
4. O check é mais rápido do que o seu cold start
Se o check falhar algumas vezes e depois passar, ou falhar durante o deploy e passar quando tenta novamente, o problema é de timing, não de configuração.
O budget de que precisa não é uma única tentativa — é interval × retries. Uma aplicação que demora doze segundos a ligar-se à base de dados e a aquecer uma cache precisa de um budget total superior a doze segundos; caso contrário, todos os releases falharão e acabará por desativar o gate, removendo a única barreira entre um build com problemas e os seus utilizadores.
dockup info my-project/my-api --json | grep -A6 healthCheck
Defina o timeout acima da duração da sua tentativa legítima mais lenta e configure retries para que interval × retries exceda confortavelmente o arranque legítimo mais lento. Meça o arranque em vez de adivinhar — os logs têm timestamps.
5. A aplicação não está realmente pronta
Este é o caso para o qual o check existe: a aplicação arrancou, não conseguiu alcançar uma dependência e está a tentar novamente. Não sofreu crash, por isso nada a reinicia. Não consegue servir pedidos, por isso o check falha. O sistema está a funcionar exatamente como foi concebido e a indicar-lhe que este release não deve receber tráfego.
A forma de distinguir este caso dos outros quatro é que a aplicação registou o pedido e respondeu com um status não-2xx. Se o pedido aparece nos logs, as causas 1 a 3 estão eliminadas.
A ordem de diagnóstico que poupa tempo
# 1. Did the request reach the app at all?
dockup logs my-project/my-api --follow
# 2. What is the process actually bound to?
dockup exec "ss -ltn || netstat -ltn" my-project/my-api
# 3. Does the path answer from inside the container?
dockup exec "curl -si localhost:3000/healthz" my-project/my-api
# 4. What is the gate configured to expect?
dockup info my-project/my-api --json
O passo 3 é o que resolve a maioria destes casos. Um curl executado dentro do container elimina todas as variáveis de rede de uma só vez: se devolver 200 aí e a plataforma continuar a falhar, o problema está no endereço ou na port, não na aplicação. Se devolver um 301 ou um 401, encontrou a causa sem tocar sequer na plataforma.
Porque vale a pena manter o gate
Depois do quarto deploy falhado, é tentador desativar o health check e avançar com o release. Vale a pena lembrar o que está a desativar.
No Dockup, o health gate é o mecanismo que mantém um release com problemas longe dos seus utilizadores. A nova versão é construída e iniciada enquanto a atual continua a servir tráfego; o tráfego só muda quando a nova versão responde. Desative o gate e reativa o cenário de falha em que um container que arranca mas não consegue funcionar substitui outro que estava correto.
Um check que falha em quatro releases consecutivos é irritante. Um check que passa incondicionalmente é um check que não impedirá o deploy que realmente importa.
Perguntas frequentes
Porque é que o health check falha quando a aplicação funciona localmente?
Quase sempre porque o container está associado a 127.0.0.1 em vez de 0.0.0.0. Localmente, liga-se através do mesmo loopback; a partir de fora do container, esse endereço está inacessível.
O endpoint de health deve exigir autenticação? Não. Exclua-o do middleware de autenticação global; caso contrário, o checker recebe um 401 e o deploy falha, embora a aplicação esteja correta.
Que timeout devo utilizar? Um valor superior ao da sua tentativa legítima mais lenta, com retries suficientes para cobrir o seu cold start legítimo mais demorado. Consulte o tempo de arranque nos logs em vez de adivinhar.
É seguro desativar o health check para desbloquear um release? Isso desbloqueia o release, mas remove a proteção que impede uma versão com problemas de receber tráfego. Corrija o check — na maioria dos casos, a causa é o bind address ou um redirect e a resolução demora apenas alguns minutos.
