As férias acabaram e estamos de volta com nossa segunda publicação na série sobre Istio Service Mesh.

O tema de hoje é Disjuntor, que em russo significa "interruptor automático" ou, coloquialmente, "disjuntor de circuito". No Istio, porém, esse disjuntor desliga contêineres defeituosos, e não circuitos em curto-circuito ou sobrecarga.
Como deveria funcionar idealmente
Quando os microsserviços são gerenciados pelo Kubernetes, por exemplo, na plataforma OpenShift, eles escalam automaticamente para cima e para baixo, dependendo da carga. Como os microsserviços são executados em pods, várias instâncias de um microsserviço conteinerizado podem estar em execução em um único endpoint, e o Kubernetes roteará as solicitações e balanceará a carga entre elas. E, idealmente, tudo isso deveria funcionar perfeitamente.
Lembremos que os microsserviços são pequenos e efêmeros. A efemeridade, que aqui se refere à facilidade de surgimento e desaparecimento, é frequentemente subestimada. O nascimento e a morte de mais uma instância de microsserviço em um pod são totalmente esperados; o OpenShift e o Kubernetes lidam bem com isso, e tudo funciona maravilhosamente bem — mas, novamente, isso é apenas na teoria.
Como funciona na prática
Agora imagine que uma instância de microsserviço específica, ou um contêiner, se tornou inutilizável: ou não está respondendo (erro 503) ou, pior, está respondendo, mas muito lentamente. Em outras palavras, está apresentando falhas ou não responde, mas não é removido automaticamente do pool. O que você deve fazer nesse caso? Tentar novamente? Removê-lo do roteamento? E o que significa "muito lento"? O que isso representa e quem determina isso? Talvez você deva simplesmente aguardar um pouco e tentar novamente mais tarde? Se sim, quanto tempo depois?
O que é a ejeção de pool no Istio?
É aí que o Istio entra em ação com seus mecanismos de proteção Circuit Breaker, que removem temporariamente os contêineres com defeito do pool de recursos de roteamento e balanceamento de carga, implementando o procedimento de Ejeção do Pool.
Utilizando uma estratégia de detecção de outliers, o Istio detecta pods que estão dessincronizados e os remove do pool de recursos por um período de tempo especificado, chamado de janela de repouso.
Para mostrar como isso funciona no Kubernetes na plataforma OpenShift, vamos começar com uma captura de tela de microsserviços em execução normal do repositório de exemplo. Aqui temos dois pods, v1 e v2, cada um executando um contêiner. Quando as regras de roteamento do Istio não são usadas, o Kubernetes usa por padrão o roteamento round-robin balanceado:

Preparando-se para o fracasso
Antes de executar a ejeção do pool, precisamos criar uma regra de roteamento do Istio. Digamos que queremos distribuir as solicitações entre os pods em 50/50. Também aumentaremos o número de contêineres v2 de um para dois, assim:
oc scale deployment recommendation-v2 --replicas=2 -n tutorial
Agora, configuramos uma regra de roteamento para que o tráfego seja distribuído entre os pods em uma proporção de 50/50.

Eis o resultado dessa regra:

Pode-se criticar o fato de esta tela não ser 50/50, mas sim 14:9, porém a situação deverá melhorar com o tempo.
Estamos causando uma falha técnica.
Agora vamos desativar um dos dois contêineres v2 para que tenhamos um contêiner v1 íntegro, um contêiner v2 íntegro e um contêiner v2 com problemas:

Estamos resolvendo o problema.
Então, temos um contêiner com falha e é hora de ejetar o contêiner do pool. Usando uma configuração bem simples, vamos excluir esse contêiner com falha de todo o roteamento por 15 segundos, na esperança de que ele se recupere sozinho (seja reiniciando ou restaurando o desempenho). Veja como ficou essa configuração e os resultados:


Como você pode ver, o contêiner v2 com defeito não é mais usado para roteamento de requisições porque foi removido do pool. No entanto, após 15 segundos, ele retornará automaticamente ao pool. Na verdade, acabamos de demonstrar como funciona a ejeção de pool.
Vamos começar a construir a arquitetura.
O recurso Pool Ejection, combinado com as funcionalidades de monitoramento do Istio, permite que você comece a construir uma estrutura para substituir automaticamente contêineres com falha, reduzindo, ou até mesmo eliminando, o tempo de inatividade e as falhas.
A NASA tem um lema impactante: "Falhar não é uma opção", cujo autor é considerado o diretor de voo. A frase pode ser traduzida para o russo como "Falhar não é uma opção", e a ideia é que tudo pode funcionar com força de vontade suficiente. No entanto, na vida real, as falhas não acontecem por acaso; são inevitáveis, em todos os lugares e em tudo. Então, como lidar com elas no caso de microsserviços? Na nossa opinião, é melhor confiar não na força de vontade, mas nas capacidades dos contêineres. , E .
Como já mencionamos, o Istio implementa o conceito bem estabelecido de disjuntores no mundo físico. Assim como um disjuntor elétrico desconecta uma seção problemática de um circuito, o Circuit Breaker baseado em software do Istio desconecta a conexão entre o fluxo de requisições e o contêiner problemático quando algo dá errado com o endpoint, como uma falha no servidor ou lentidão.
Além disso, no segundo caso, os problemas só aumentam, uma vez que a lentidão de um contêiner não apenas causa uma cascata de atrasos nos serviços que o acessam e, consequentemente, reduz o desempenho do sistema como um todo, mas também gera solicitações repetidas a um serviço já lento, o que só piora a situação.
Disjuntor na teoria
O Circuit Breaker é um proxy que controla o fluxo de requisições para um endpoint. Quando esse endpoint para de funcionar ou, dependendo das configurações, começa a ficar lento, o proxy desconecta a conexão com o contêiner. O tráfego é então redirecionado para outros contêineres, simplesmente para fins de balanceamento de carga. A conexão permanece aberta por um período de espera especificado, digamos, dois minutos, e então é considerada semiaberta. Uma tentativa de enviar a próxima requisição determina o estado subsequente da conexão. Se o serviço estiver OK, a conexão retorna ao estado de funcionamento e é fechada novamente. Se o serviço ainda falhar, a conexão é desconectada e o período de espera é reativado. Aqui está um diagrama simplificado da transição de estado do Circuit Breaker:

É importante notar que tudo isso acontece no nível da arquitetura do sistema, por assim dizer. Portanto, em algum momento, você precisará ensinar seus aplicativos a trabalhar com o Circuit Breaker, por exemplo, fornecendo um valor padrão na resposta ou, se possível, ignorando a existência do serviço. Isso é feito usando o padrão Bulkhead, mas está além do escopo deste artigo.
Disjuntor na prática
Neste exemplo, executaremos duas versões do nosso microsserviço de recomendação no OpenShift. A versão 1 será executada normalmente, mas na versão 2 adicionaremos um atraso para simular a latência do servidor. Para visualizar os resultados, utilize a ferramenta. :
siege -r 2 -c 20 -v customer-tutorial.$(minishift ip).nip.io

Tudo parece estar funcionando, mas a que custo? À primeira vista, temos 100% de disponibilidade, mas, analisando mais de perto, vemos que a duração máxima da transação é de impressionantes 12 segundos. Isso é claramente um gargalo e precisa ser resolvido.
Para isso, usaremos o Istio para impedir chamadas a contêineres lentos. Veja como fica a configuração correspondente usando o Circuit Breaker:

A última linha com o parâmetro `httpMaxRequestsPerConnection` indica que a conexão deve ser fechada ao tentar criar uma segunda conexão além da existente. Como nosso contêiner está simulando um serviço lento, essas situações ocorrerão periodicamente e, então, o Istio retornará um erro 503. Veja o que o Siege exibirá:

Certo, temos o Circuit Breaker, qual é o próximo passo?
Implementamos, portanto, o desligamento automático sem alterar o código-fonte dos serviços em si. Usando o Circuit Breaker e o procedimento de ejeção de pool descrito acima, podemos remover contêineres lentos do pool de recursos até que voltem ao normal e verificar seu status em um intervalo especificado — em nosso exemplo, a cada dois minutos (o parâmetro sleepWindow).
Observe que a capacidade de um aplicativo responder a um erro 503 ainda é definida no nível do código-fonte. Existem diversas estratégias para lidar com o Circuit Breaker, dependendo da situação.
Na próxima publicação: Vamos abordar o rastreamento e o monitoramento, que já estão integrados ao Istio ou podem ser facilmente adicionados, bem como a forma de introduzir erros intencionalmente no sistema.
Fonte: habr.com
