Implementando Health Checks para dormir tranquilo

Boas práticas na implementação dos endpoints de Liveness e Readiness Probes

Rafa Kempfer
6 min readSep 2, 2020

Quando você começa a trabalhar com sistemas distribuídos, uma das primeiras coisas que você percebe quando seu sistema chega em produção é:

Sistemas distribuídos, problemas distribuídos!

Qual dos sistemas abaixo é mais fácil de monitorar? Qual dos sistemas apresenta mais pontos possíveis de falha?

Em sistemas distribuídos, como uma arquitetura de micro-serviços, temos maior ocorrência de pontos de falha e, muitas vezes, de maneira imprevisível.

Por esse e outros motivos que observabilidade é essencial e um dos maiores desafios em uma arquitetura de micro-serviços! Em compensação é um dos mais gratificantes.

Observabilidade é crucial para qualquer sistema

Quando estamos construindo aplicações deste tipo e que exigem alta-disponibilidade, a primeira preocupação é com a resiliência dos nossos serviços. De forma simplificada, uma aplicação é resiliente quando ela pode se recuperar rapidamente de falhas.

Os micro-serviços geralmente são desenhados para rodar em contêineres, onde os gerenciadores implementam padrões para garantir a alta-disponibilidade das aplicações. Um exemplo é a validação de saúde do serviço, ou Health Check Probe.

Health checks são requisitos essenciais para qualquer sistema distribuído.

The High Observability Principle (HOP)

A arquitetura de micro-serviços prevê que cada serviço não deva se importar em como sua solicitação é processada e respondida por outro serviço. Ou seja, cada serviço trata os outros como uma caixa preta, sem levar em consideração como as requisições são tratadas, qual linguagem está utilizando, etc. Desde que os contratos sejam respeitados.

Um contêiner deve possuir múltiplas APIs para permitir a Observabilidade

Porém, o princípio HOP determina que cada serviço deve expor endpoints para fornecer informações sobre o serviço, seu estado, integridade e disponibilidade. Além disso, um serviço cloud-native bem projetado, irá registrar eventos de execução em saídas padrões STDERR e STDOUT.

https://www.redhat.com/cms/managed-files/cl-cloud-native-container-design-whitepaper-f8808kc-201710-v3-en.pdf

Health Check no Kubernetes

Os endpoints de Health Check são formas de consultar o estado de uma aplicação ou serviço e, algumas vezes, o estado de suas dependências, como conexão com banco de dados.

O Kubernetes trata o contexto de Health Check através de duas probes: Readiness e Liveness Probes. Essas sondas são pequenos processos que rodam periodicamente e o resultado dessa execução irá determinar a perspectiva do Kubernetes em relação ao estado do container. Baseado nisso, o Kubernetes decide como vai proceder para manter a resiliência e a alta-disponibilidade.

Existem diferentes formas de validar o estado de um serviço, sendo o mais comum através de requisições HTTP que o Kubernetes faz, em intervalos de tempo definidos, em endpoints específicos que são fornecidos pelo serviço. E o Status Code da resposta é utilizado para decidir o que deve ser feito com o pod.

Liveness Probe

Imagine a situação em que um serviço não está respondendo, talvez por algum problema de deadlock ou algo assim. Uma forma de tentar resolver esse problema, é reiniciando o serviço.

Não seria uma boa alternativa gerar um alerta para que alguém, manualmente, fizesse essa operação (ainda mais se for as 3h da madrugada). Isso é feito pelo próprio Kubernetes, que irá “matar” o pod e subirá um novo após ser alterado do problema pelo Liveness Probe, mantendo a resiliência e alta-disponibilidade.

Readiness Probe

O objetivo dessa probe é garantir que o pod não irá receber trafego até que todos os contêineres estejam prontos para isso.

Isso é usado em situações em que o contêiner precise resolver algumas dependências para que possa “trabalhar”, como, por exemplo, carregar uma certa quantidade de informações em cache antes de começar a atender requisições.

O readiness probe será responsável em validar a disponibilidade do serviço e, em caso de falha, ele irá remover o pod do balanceador de carga até as dependências sejam resolvidas e este contêiner esteja pronto para receber requisições.

Mais detalhes sobre o funcionamento e como configurar as probes no Kubernetes, podem ser encontrados na documentação oficial da plataforma:

Health Check na Aplicação

Tudo lindo e maravilhoso, o Kubernetes sozinho irá garantir que nossa aplicação ficará “no ar” e disponível, certo? Errado.

Meio óbvio, mas o serviço precisa implementar os endpoints que o Kubernetes irá utilizar para realizar as validações. Mas também é importante que essa implementação siga algumas boas práticas para garantir a efetividade das probes.

Funções e endpoints separados

O Kubernetes trata cada probe separadamente, com objetivos diferentes. É recomendável que seja construído um endpoint para o liveness e outro para o readiness. Exemplo:

/healthcheck/live — Para as chamadas HTTP do Liveness probe.

/healthcheck/ready — Para as chamadas HTTP do Readiness probe.

Além disso, a implementação de cada endpoint deverá ter sua própria função que irá tratar as requisições da sua maneira.

O uso da mesma função poderá gerar situações onde uma das probes não seja efetiva em seu propósito.

Função do “live” deve evitar verificações

A probe de liveness quer saber se a aplicação está viva ou morta. A função responsável em tratar a requisição deverá retornar o status code 200 se a thread principal da aplicação está funcionando, caso contrário, deverá retornar um erro 5xx o mais rápido possível. Simples assim!

Nada de implementar verificações de outras condições como conexão com banco de dados, apontamento para outros serviços ou lógicas complexas.

Uma situação interessante que já vivenciei foi a implementação de “live” onde o serviço A validava a conexão requisitando o endpoint de “live” do serviço B e assim sucessivamente. Criando um grande ciclo e um encadeamento das validações.

Dois problemas que aconteciam nesse cenário:

  1. Uma grande quantidade de requisições entre os serviços para validação do estado, muito maior do que o necessário.
  2. Quando um pod ficava indisponível, criava um efeito em cascata fazendo com que o Kubernetes começasse a reiniciar todos os serviços.

Nesse caso, além de um problema na implementação do “live”, também temos um problema na arquitetura da aplicação e o alto acoplamento entre os serviços, mas isso é discussão para outro momento.

O “Ready” deve ter lógica

A regra para função que irá tratar as requisições do “Ready” é garantir uma resposta efetiva sobre a total disponibilidade do serviço. Ou seja, esta função deverá rodar todos os passos que são cruciais para garantir que o serviço está apto a receber e processar requisições.

Por exemplo, se o serviço precisa se conectar com uma base de dados para processar as requisições HTTP, é importante que a função que trata as requisições do “ready” valide que a conexão do serviço com a base de dados está estabelecida e disponível para o uso.

Um equívoco comum nesse ponto é desacoplar a implementação do readiness em um outro processo, causando problemas difíceis de detectar. No exemplo acima, podemos ter a situação onde o processo principal do serviço perde a conexão com o banco de dados e ainda não conseguiu reestabelecer. Se a função do “ready” estiver utilizando outra conexão ou outro pool de conexões, ela poderá não identificar o problema e não irá avisar o Kubernetes para que tire o pod do balanceador de cargas.

Não tente recuperar o estado

O processo do “ready” é responsável por retornar o estado da disponibilidade do serviço, apenas isso. Não é responsabilidade desta função tentar re-estabelecer o estado do serviço. Implementar lógica de recuperação de algum estado, pode levar a problemas mais difíceis de serem detectados.

Conclusão

As probes do Kubernetes são ferramentas de extrema importância para garantir a robustez, a resiliência e a alta-disponibilidade das aplicações. Entretanto, se não for considerado com cuidado como as probes funcionam e como elas devem ser implementadas, o efeito poderá ser o contrário, deixando o ambiente instável e apresentando erros difíceis de detectar.

--

--