Graceful Shutdown
TL;DR:
Este artigo aborda a importância do Graceful Shutdown nos microsserviços,
os sinais de desligamento (SIGTERM
, SIGINT
e SIGKILL
)
e três visões sobre esse desafio: Go, Kubernetes e Istio.
Introdução
A escalabilidade horizontal é uma das principais vantagens das arquiteturas de softwares modernas para que os sistemas continuem responsivos sob variações de carga.
Ou seja, réplicas dos microsserviços são escaladas quando a carga aumenta, e desligadas quando a carga diminui.
Para que essa elasticidade (aumento e diminuição de réplicas) seja natural e transparente, é importante que o tempo de inicialização dos microsserviços seja baixo. Quanto mais rápido um serviço inicializar, mais rápido estará disponível para atender requisições e dividir a carga com as outras réplicas.
E sobre o desligamento dos microsserviços?
O que acontece se uma réplica recebe um sinal para ser desligada no momento em que está processando requisições?
Algumas ocasiões em que uma réplica pode receber um sinal de desligamento, são:
- diminuição de carga;
- rolling update;
- rolling restart.
Normalmente, ao receber um sinal, o processamento dessas requisições seriam interrompidos e os clientes receberiam erros.
A não ser que esse serviço tenha um processo de desligamento mais inteligente: graceful shutdown.
Entendendo os sinais
Os sinais são usados principalmente em sistemas do tipo Unix, e são enviados pelo Kernel ou de algum outro programa.
Os principais sinais de desligamento/encerramento de um programa, são SIGTERM
, SIGINT
e SIGKILL
.
SIGTERM
é um sinal genérico usado para causar o encerramento do programa. É o sinal gerado pelo comando kill
.
SIGTERM
para matar um Pod.O sinal SIGINT
é enviado quando o usuário digita CTRL-c
.
SIGKILL
é usado para causar o encerramento imediato do programa.
Não pode ser interceptado ou ignorado e, portanto, é sempre fatal.
Graceful shutdown
Graceful Shutdown significa um desligamento inteligente, agradável, sem danos ao sistema.
Para que microsserviços tenham esse desligamento inteligente, eles precisam lidar com os sinais SIGTERM
e SIGINT
listados acima.
O comportamento padrão da maioria das tecnologias é interromper o processamento do programa, de forma que, muitas vezes, é prejudicial para o funcionamento.
Em Go, por exemplo, um sinal síncrono é convertido em panic
em tempo de execução.
Uma forma simples de lidar com esses sinais, é esperar alguns segundos para que o processamento seja finalizado. Mas pode ser necessário fechar conexões com banco de dados, redis ou um message broker, por exemplo.
O Graceful Shutdown pode ser implementado diretamente no código do serviço. Porém, o Kubernetes e Istio possuem configurações que podem ajudar nessa tarefa.
Go
A implementação mais comum de graceful shutdown em Go, é usando Goroutines e Channels, como o exemplo abaixo.
Dessa forma, o servidor HTTP é inicializado numa nova goroutine, enquanto a principal espera por um sinal no channel quit
.
Assim que um sinal é recebido, o servidor é desligado com um timeout de 5 segundos.
Ou seja, se em 5 segundos ainda existir alguma conexão ativa, a função Shutdown()
retorna um erro.
Shutdown()
foi introduzida no Go1.8Os principais frameworks web de Go sugerem implementações nesse padrão, com Goroutines e Channels:
Pra quem prefere usar bibliotecas de terceiros criadas exclusivamente para isso, eu recomendaria a ory/graceful. Foi a que mais me chamou atenção pela simplicidade e possibilidade de customizar a função de desligamento.
Kubernetes
Nos Pods do Kubernetes,
que representam réplicas de um Deployment,
é possível configurar um hook chamado preStop
, que é invocado antes do sinal SIGTERM
ser enviado.
Configurando um sleep
neste hook, podemos ter um graceful shutdown, como no exemplo abaixo.
|
|
O intervalo do sleep
deve ser o suficiente para que a alteração de endpoint do Kubernetes seja propagada ao
kube-proxy, Ingress Controller, CoreDNS, etc.
Para mais detalhes, veja esse artigo.
Por padrão, o Kubernetes espera até 30 segundos no processo de desligamento de um Pod
antes de forçar o encerramento do processo (SIGKILL
, que não pode ser interceptado).
sleep
,
dificultando o uso de imagens Distroless.Istio
O Istio possui uma configuração chamada TerminationDrainDuration
em que é possível definir uma pausa antes do desligamento do sidecar.
Sidecar é um conceito comum nas implementações de Service Mesh:
é um container que acompanha a aplicação (que também e um container) dentro do Pod do Kubernetes.
Assim, temos 2 containers dentro do Pod: app + sidecar
.
O sidecar é um proxy (no caso do Istio, é o Envoy) que faz a intermediação de todo tráfego do Pod para que tenhamos todas as vantagens do Service Mesh.
Quando o proxy recebe SIGTERM
ou SIGINT
, ele começa a drenar as conexões,
impedindo novas conexões e permitindo que as conexões existentes sejam concluídas.
SIGTERM
é enviado após a execução do hook preStop
A duração desse processo de drenagem é configurável tanto globalmente:
|
|
quanto por workload (por Pod):
|
|
A duração padrão é 5 segundos.
Conclusão
A melhor abordagem para habilitar Graceful Shutdown depende do cenário de cada projeto/sistema.
Existem projetos em que:
- alterar o código dos microsserviços é muito trabalhoso;
- não usa Service Mesh (Istio);
- o uso de imagens Distroless é prioridade;
- não estão no Kubernetes;
- que estão no Kubernetes, usando Istio, e tem agilidade para alterar o código dos microsserviços. Nesse caso é possível conciliar mais de uma estratégia para garantir zero downtime.
Compartilhe nos comentários os desafios e aprendizados do seu projeto! 😉
- Qual é o cenário do seu projeto?
- Qual abordagem é utilizada para graceful shutdown?
- Como é a implementação na sua linguagem de programação e framework preferido?