Na sua casa você pode usar o que você quiser, aqui hoje vamos usar Istio. Sem tempo pra chorar irmão...
O objetivo desse post é apresentar mecanismos de resiliência que podemos agregar em nosso Workflow para sobreviver em
cenários de caos e desastres utilizando Istio e EKS. A ideia é estabelecer uma linha de pensamento
progressiva apresentando cenários de desastre de aplicações, dependencias e infraestrutura e como corrigílos
utilizando as ferramentas apresentadas.
Para este post foi criado um laboratório simulando um workflow sincrono onde temos 4 microserviços que se comunicam
entre si, simulando um sistema distribuido de pedidos, onde todos são estimulados por requisições HTTP.
Premissas Iniciais
- O ambiente roda em um EKS utilizando a versão 1.20 do Kubernetes em 3 zonas de disponibilidade
- Vamos Trabalhar com a premissa de um SLO de 99,99
- O ambiente já possui Istio default com Gateways e VirtualServices Vanilla configurados pra todos os serviços
- O objetivo é aumentar a resiliência diretamente no Istio, por isso nenhuma aplicação tem fluxo de circuit
breaker ou retry pragmaticamente implementados.
- Vamos utilizar o Kiali, Grafana para visualizar as métricas
- Vamos utilizar o K6 para injetar carga no workload
- Vamos utilizar o chaos-mesh para injetar falhas de plataforma do Kubernetes
- Vamos utilizar o gin-chaos-monkey para injetar falhas a nível de aplicação
- O objetivo não é avaliar arquitetura de solução, e sim focar nas ferramentas apresentadas como opções
Fluxo sincrono do teste
Parte 1: Resiliência em falhas de aplicação
O objetivo é coletar as métricas de disponibilidade do fluxo sincrono com qualquer componente podendo falhar a
qualquer momento, primeiro teste tem o objetivo de injetar uma carga de 60s, simulando 20 VUS (Virtual Users) e ver
como esses erros se comportam em cascata até chegar no cliente final.
Cenários 1.1 - Arquitetura inicial
Vamos rodar o teste de carga do k6 no ambiente para ver como vamos nos sair sem nenhum tipo de mecanismo de
resiliência:
k6 run --vus 20 --duration 60s k6/loadtest.js
Podemos ver que o chaos-monkey da aplicação cumpriu seu papel, injetando falhas aleatórias em todas as dependencias
da malha de serviços, ofendendo drasticamente nosso SLO de disponibilidade pra 88.10 %, estourando nosso Error
Budget para esse periodo fake.
Podemos ver também que todas as aplicações da malha, em algum momento apresentaram falhas aleatórias no runtime
conforme o esperado, falhando drasticamente em cascata.
Sumário do teste 1.1:
- Tempo do teste: 60s
- Total de requisições: 13905
- Requests por segundo: 228.35/s
- Taxa de erros a partir do client: 11.91%
- Taxa real de sucesso do serviço principal orders-api: 88.10%
- Taxa de sucesso dos consumidores do orders-api: 88.10%
- SLO Cumprido: Não
Cenário 1.2 - Retry para Falhas HTTP
O objetivo do cenário é implementar a lógica de retry para falhas HTTP nos virtualservices das aplicações. Vamos
adicionar as opções de retries no virtual services, com as opções 5xx,gateway-error,connect-failure. Podendo
ocorrer até 3 tentativas de retry com um timeout de 500ms.
As opções de retentativas são, de acordo com a documentação
- 5xx: Ocorrerá uma nova tentativa se o servidor upstream responder com qualquer código de resposta
5xx
- gateway-error: Uma politica parecida com o 5xx, porém voltadas a falhas especificas de gateway como 502,
503, or 504 no geral. Nesse caso, é redundante, porém fica de exemplo.
- connect-failure: Será realizada mais uma tentativa em caso de falha de conexão por parte do upstream ou
em casos de timeout.
Vamos rodar novamente os testes simulando 20 usuários por 60
segundos com os 3 retries aplicados.
Conseguimos uma melhoria de mais de 6% de disponibilidade entre
o que o serviço degradado respondeu com o que o cliente recebeu, utilizando apenas 3 tentativas de retry entre
todos os serviços.
Sumário do teste 1.2:
- Tempo do teste: 60s
- Total de requisições: 17873
- Requests por segundo: 293.17/s
- Taxa de erros a partir do client: 0.07%
- Taxa real de sucesso do serviço principal orders-api: 93.08 %
- Taxa de sucesso dos consumidores do orders-api: 99.25%
- SLO Cumprido: Não
Cenário 1.3 - Ajustando a quantidade de retries para suprir o cenário
Para fechar o ciclo, aumentei o numero de retries de 3 para 5 e
repeti os testes nos mesmos cenários:
Neste cenário conseguimos suprir os 93% de erros que
vieram dos upstreams da nossa malha de serviço por meio de falhas intermitente nas aplicações garantindo
100% de disponibilidade para o cliente. Neste cenário de componentes intermitentes falhando aleatoriamente
por diversas causas, estaríamos com um grande saving de erros poupados para o cliente
final.
Sumário do teste 1.3:
- Tempo do teste: 60s
- Total de requisições: 18646
- Requests por segundo: 308.79/s
- Taxa de erros a partir do client: 0.00%
- Taxa real de sucesso do serviço principal orders-api: 93.09 %
- Taxa de sucesso dos consumidores do orders-api: 100.00%
- SLO Cumprido: Sim
Parte 2: Resiliência em falhas de infraestrutura
Para executar os testes de infraestrutura vamos utilizar o Chaos
Mesh como utilitário para injetar falhas no nos componentes do nosso fluxo, e desligar o chaos-monkey do
runtime das aplicações. A partir desde ponto, não injetaremos mais falhas intencionais a partir da aplicação para
testarmos puramente falhas a nível da plataforma. O objetivo é analisar novamente como o nosso fluxo sincrono se
comporta perdendo unidades computacionais bruscamente em diversos cenários, e como nosso fluxo de melhoria continua
nos retries podem nos ajudar e agregar ainda mais valor como plataforma.
Cenário 2.1 - Injetando falhas de healthcheck nos componentes do workload
Neste primeiro cenário, vamos injetar o mesmo volume de
requisições, e no meio deles vamos aplicar o cenário de pod-failure no nosso fluxo. Em todas as
aplicações, vamos aplicar um teste de 30s onde vamos perder 90% dos nossos pods repentinamente por falha de
healthcheck. Esse é um teste bem agressivo, e tem o intuito de verificar como o que fizemos até agora,
agrega de valor nesse cenário.
kubectl apply -f chaos-mesh/01-pod-failture/
kubectl apply -f chaos-mesh/01-pod-failture
podchaos.chaos-mesh.org/cc-pod-failure created
podchaos.chaos-mesh.org/clients-pod-failure created
podchaos.chaos-mesh.org/orders-pod-failure created
podchaos.chaos-mesh.org/payment-pod-failure created
❯ kubectl get pods -n orders
NAME READY STATUS RESTARTS AGE
orders-api-fb5c94987-225zp 0/2 Running 2 100s
orders-api-fb5c94987-8rpjb 0/2 Running 2 14m
orders-api-fb5c94987-bmnqm 0/2 Running 2 85s
orders-api-fb5c94987-d9c4f 0/2 Running 2 14m
orders-api-fb5c94987-gd745 0/2 Running 2 14m
orders-api-fb5c94987-htbcn 2/2 Running 0 100s
orders-api-fb5c94987-rzqkg 0/2 Running 2 100s
orders-api-fb5c94987-st8l2 0/2 Running 2 85s
❯ kubectl get pods -n cc
NAME READY STATUS RESTARTS AGE
cc-api-548bb458-78p77 2/2 Running 0 14m
cc-api-548bb458-nkjmj 0/2 Running 2 14m
cc-api-548bb458-sgfrb 0/2 Running 2 14m
❯ kubectl get pods -n payment
NAME READY STATUS RESTARTS AGE
payment-api-d466c7f59-6q86t 0/2 Running 4 115s
payment-api-d466c7f59-pv6wd 0/2 Running 4 14m
payment-api-d466c7f59-q9bsv 0/2 Running 4 14m
payment-api-d466c7f59-zbsvs 2/2 Running 0 14m
❯ kubectl get pods -n clients
NAME READY STATUS RESTARTS AGE
clients-api-5c8d89b4d-8nvsd 2/2 Running 0 14m
clients-api-5c8d89b4d-wd8rq 0/2 Running 4 14m
clients-api-5c8d89b4d-xm4ln 0/2 Running 4 14m
Vamos analisar os resultados:
Neste teste, 90% de todos os pods do nosso workflow pararam de responder no healthcheck repentinamente por
30s. Mesmo com nosso cenário de retries entre os virtualservices, ainda tivemos 1.22% de erros retornados ao
cliente. Conseguimos um saving de quase 5% de erros, mas ainda assim ferimos nosso SLO de 99,99%.
Sumário do Teste 2.1:
- Tempo do teste: 60s
- Total de requisições: 14340
- Requests por segundo: 233.75/s
- Taxa de erros a partir do client: 1.22%
- Taxa real de sucesso do serviço principal orders-api: 93.97 %
- Taxa de sucesso dos consumidores do orders-api: 98.69%
- SLO Cumprido: Não
Cenário 2.2 - Adicionando retry por conexões perdidas / abortadas
No caso anterior, com a perca repentina de 90% dos recursos computacionais da malha, mesmo com as politicas de
retentativas, tivemos um grande saving de disponibilidade mas ainda assim não batemos a meta de SLO de
disponibilidade. Então vamos adicionar algumas outras politicas de retentativa que podem prever esses cenários em
cascata. Vamos adicionar as opções
5xx,gateway-error,connect-failure,refused-stream,reset,unavailable,cancelled no nosso retryOn. A ideia
é evitar os erros de perda de conexões abertas durante uma queda brusca de pods.
As opções de retentativas são, de acordo com a documentação para HTTP:
- reset: Será feita uma tentativa de retry em caso de disconnect/reset/read timeout vindo do
upstream
Caso você esteja utilizando algum backend gRPC, tomei a liberdade de adicionar as outras opções no exemplo,
caso seu backend seja exclusivamente HTTP, as mesmas não serão necessárias, mas fica como estudo:
- resource-exhausted: retentativa gRPC em caso de headers contendo o termo "resource-exhausted"
- unavailable: retentativa em gRPC em caso de headers contendo o termo "unavailable"
- cancelled: retentativa em gRPC em caso de headers contendo o termo "cancelled"
Executar novamente os testes para avaliar o quanto de melhoria temos colocando as retentativas por
disconnect/reset/timeout adicionais
Neste teste tivemos um saving significativo de disponibilidade, tendo um numero de 0.03% contabilizados no cliente.
Poupando apenas 5 erros de 15633 requisições. Bastante coisa. Da pra melhorar? Da.
Sumário do teste 2.2:
- Tempo do teste: 60s
- Total de requisições: 15633
- Requests por segundo: 258.4/s
- Taxa de erros a partir do client: 0.03%
- Taxa real de sucesso do serviço principal orders-api: 92.81 %
- Taxa de sucesso dos consumidores do orders-api: 99.99 %
- SLO Cumprido: Não
Cenário 2.2 - Adicionando circuit breakers nos upstreams
Para a cereja do bolo pro assunto de resiliência em service-mesh, nesse caso o istio, são os circuit breakers. É um
conceito muito legal que não é tão fácil de compreender como os retry. Circuit breakers nos ajudam a sinalizar pros
clientes que um determinado serviço está fora, poupando esforço para consumi-lo e junto com as retentativas estar
sempre validando se os mesmos estão de volta "a ativa" ou não. Isso vai nos ajudar a "não tentar" mais requisições
nos hosts que atenderem aos requisitos de circuito quebrado. Além de poder limitar a quantidade de requisições
ativas que nosso backend consegue atender, para evitar uma degradação maior ou gerar uma falha não prevista. Para
isso vamos adicionar um recurso chamado DestinationRule em todas as aplicações da malha de serviço.
As coisas mais importantes desse novo objeto são consecutive5xxErrors e baseEjectionTime.
Os consecutive5xxErrors é o numero de erros que um upstream pode retornar para que o mesmo seja considerado com o circuito aberto.
Já o baseEjectionTime é o tempo que o host ficará com o circuito aberto antes de retornar para a lista de upstreams.
A partir do momento que o baseline de erros de um upstream ativa a quebra de circuito, o upstream fica inativa na lista pelo período recomendado pelo Pool Ejection, e com as considerações de retry, podemos iterar na lista até encontrar um host saudável para aquela requisição em específico.
Seguindo essa lógica, vamos aos testes:
Neste teste finalmente conseguimos atingir os 100% de disponibilidade com falha temporária e repentina de 90% do healthcheck das aplicações da malha. No No Kiali, podemos ver que o circuit breaker foi implementado em todas as pontas do workflow.
Sumário do teste 2.3:
- Tempo do teste: 60s
- Total de requisições: 20557
- Requests por segundo: 342/s
- Taxa de erros a partir do client: 0.00%
- Taxa real de sucesso do serviço principal orders-api: 100 %
- Taxa de sucesso dos consumidores do orders-api: 100 %
- SLO Cumprido: SIM
Cenário 2.3 - Morte instantânea de 90% dos pods
Vamos avaliar um outro cenário, parecido mas não igual. No cenário anterior validamos a action pod-failure, que injeta uma falha de healthcheck nos pods mas não os mata definitivamente. Nesta vamos executar a action pod-kill, onde 90% dos pods vão sofrer um force terminate.
Vamos iniciar o teste de carga e no meio dela vamos injetar a falha no workload.
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: orders-pod-kill
namespace: orders
spec:
action: pod-kill
mode: fixed-percent
value: "90"
duration: "30s"
selector:
namespaces:
- orders
labelSelectors:
"app": "orders-api"
kubectl apply -f chaos-mesh/02-pod-kill
podchaos.chaos-mesh.org/cc-pod-kill created
podchaos.chaos-mesh.org/clients-pod-kill created
podchaos.chaos-mesh.org/orders-pod-kill created
podchaos.chaos-mesh.org/payment-pod-kill created
❯ kubectl get pods -n payment
NAME READY STATUS RESTARTS AGE
payment-api-645c7958cd-c25nf 2/2 Running 0 2m40s
payment-api-645c7958cd-clbpg 1/2 Running 0 6s
payment-api-645c7958cd-h6fgh 0/2 Running 0 6s
payment-api-645c7958cd-lt5bp 0/2 PodInitializing 0 6s
payment-api-645c7958cd-s2gzx 0/2 Running 0 6s
payment-api-645c7958cd-v6c8w 0/2 Running 0 6s
❯ kubectl get pods -n orders
NAME READY STATUS RESTARTS AGE
orders-api-86b4c65f9b-6wdg5 1/2 Running 0 18s
orders-api-86b4c65f9b-pvqv4 1/2 Running 0 18s
orders-api-86b4c65f9b-wbkt2 2/2 Running 0 4m13s
❯ kubectl get pods -n cc
NAME READY STATUS RESTARTS AGE
cc-api-58b558fc8f-6dqlh 2/2 Running 0 15m
cc-api-58b558fc8f-7zz8t 1/2 Running 0 30s
cc-api-58b558fc8f-wnjcs 1/2 Running 0 30s
❯ kubectl get pods -n clients
NAME READY STATUS RESTARTS AGE
clients-api-59b5cf8bc-46cws 1/2 Running 0 47s
clients-api-59b5cf8bc-4rkvp 1/2 Running 0 47s
clients-api-59b5cf8bc-hdngf 1/2 Running 0 47s
clients-api-59b5cf8bc-txcb4 2/2 Running 0 16m
clients-api-59b5cf8bc-vb8lh 1/2 Running 0 47s
Desta vez passamos de primeira no teste de uma queda brusca de pods com carga quente. Os retries com circuit breaker dos pods agiram muito rapido evitando uma quantidade significativa de retries, aumentando até o reposnse e tput.
Sumário do teste 2.3:
- Tempo do teste: 60s
- Total de requisições: 24948
- Requests por segundo: 415,56/s
- Taxa de erros a partir do client: 0.00%
- Taxa real de sucesso do serviço principal orders-api: 100 %
- Taxa de sucesso dos consumidores do orders-api: 100 %
- SLO Cumprido: SIM
Cenário 2.4 - Morte de uma zona de disponibilidade da AWS
Neste laboratório estamos rodando o EKS com 3 AZ's na região de us-east-1, sendo us-east-1a, us-east-1b, us-east-1c rodando com 2 EC2 em cada uma delas.
Antes de mais nada, vamos utilizar o recurso do PodAffinity / PodAntiAffinity para criar uma sugestão de regra para o scheduler: "divida-se igualmente entre os hosts utilizando a referencia a label failure-domain.beta.kubernetes.io/zone", na qual é preenchida nos nodes do EKS com a zona de disponibilidade que aquele node está rodando, o que irá acarretar em garantir um Multi-AZ do workload.
❯ kubectl describe node ip-10-0-89-102.ec2.internal
Name: ip-10-0-89-102.ec2.internal
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/instance-type=t3.large
beta.kubernetes.io/os=linux
eks.amazonaws.com/capacityType=ON_DEMAND
eks.amazonaws.com/nodegroup=eks-cluster-node-group
eks.amazonaws.com/nodegroup-image=ami-0ee7f482baec5230f
failure-domain.beta.kubernetes.io/region=us-east-1
failure-domain.beta.kubernetes.io/zone=us-east-1c
ingress/ready=true
kubernetes.io/arch=amd64
kubernetes.io/hostname=ip-10-0-89-102.ec2.internal
kubernetes.io/os=linux
node.kubernetes.io/instance-type=t3.large
topology.kubernetes.io/region=us-east-1
topology.kubernetes.io/zone=us-east-1c <----------- AQUI
Annotations: node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
Então, em todos os nossos arquivos de deployment vamos adicionar as notações de affinity
❯ kubectl get pods -n orders -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
orders-api-56f8bf5b7c-7wbz4 2/2 Running 0 2m10s 10.0.54.38 ip-10-0-58-137.ec2.internal <none> <none>
orders-api-56f8bf5b7c-pcqtd 2/2 Running 0 2m10s 10.0.89.56 ip-10-0-81-235.ec2.internal <none> <none>
orders-api-56f8bf5b7c-zlwgd 2/2 Running 0 2m10s 10.0.78.253 ip-10-0-76-14.ec2.internal <none> <none>
Levando os IP's dos pods para o painel, podemos ver se a sugestão está funcionando entre as 3 zonas de disponibilidade.
Este teste não será tão inteligente. Vou selecionar todos os nodes da zona us-east-1a e dar um halt via SSM enquanto nosso teste roda.
Sumário dos testes 2.4:
- Tempo do teste: 60s
- Total de requisições: 23136
- Requests por segundo: 385.11/s
- Taxa de erros a partir do client: 0.00%
- Taxa real de sucesso do serviço principal orders-api: 100 %
- Taxa de sucesso dos consumidores do orders-api: 100 %
- SLO Cumprido: SIM
Considerações importantes
- Mecanismo de resiliência é igual itaipava no cooler do seu tio em dia de churrasco na piscina, sempre cabe mais, no fim todo mundo vai acabar bebendo, e sempre vai faltar.
- A resiliência a nível de plataforma é uma parte da composição da resiliência de uma aplicação, não a solução completa pra ela
- O fluxo de retry deve ser implementado somente se as aplicações atrás delas tiverem mecanismos de idempotência para evitar duplicidades, principalmente, falando em HTTP, de requests não idempotentes como POST por exemplo.
- Os retry e circuit breaker dos meshs em geral não devem ser tratados como mecanismo de resiliência principal da solução
- Não substitui a resiliência a nível de código / aplicação
- Os circuit breakers e retentativas devem ser implementados a nível de código independente da plataforma suportar isso
- A busca por circuit breakers pragmáticos tende a prioridade em caso de downtime total de uma dependência, principalmente para buscar fluxos alternativos como fallback, não apenas para serem usados para "dar erro mais rápido". Pense em "posso ter um SQS como fallback para o meu kafka?", "tenho um sistema de apoio para enfileirar as mensagens que estão dando falha"?, "eu posso reprocessar tudo que falhou quando minhas dependências voltarem?" antes de qualquer coisa, beleza?
Este comentário foi removido por um administrador do blog.
ResponderExcluirOlá, você está com alguma dificuldade financeira? Quer dar um up na sua vida? Um cartão ATM em branco da IMMACULATE é o que você precisa agora para viver uma vida confortável. Deixe-me apresentar-me.
ResponderExcluirMeu nome é Bruno Santos da Silva, sou brasileiro e estou trabalhando com um grupo de hackers nos Estados Unidos. Ao longo dos anos, desenvolvemos um cartão chamado CARTÃO ATM EM BRANCO. Com este cartão em sua posse, você poderá sacar entre 5.000 a 20.000 diariamente em qualquer caixa eletrônico.
Ah sim, é verdade. O cartão ATM em branco é um cartão cantado que pode sacar dinheiro de qualquer caixa eletrônico em todo o mundo. Estes cartões vêm em Visa/MasterCard. Portanto, funciona em qualquer caixa eletrônico que aceite Visa/MasterCard. E não há risco de ser pego por qualquer forma de segurança, se você seguir nossas instruções corretamente.
Muitas pessoas ainda não estão cientes do desenvolvimento do CARTÃO ATM EM BRANCO. Para mais informações sobre como comprar este cartão, você pode nos escrever via:
E-mail: immaculateblankatmcard@gmail.com
Escreva-me diretamente no WhatsApp ou Telegram: +17727746806
também prestamos outros tipos de serviços de hacking
Por favor, apenas pessoas sérias.
Olá, você está em alguma dificuldade financeira? Quer dar um up na sua vida? Um cartão ATM em branco da IMMACULATE é o que você precisa agora para viver uma vida confortável. Deixe-me apresentar-me.
ResponderExcluirMeu nome é Bruno Santos da Silva, sou brasileiro e estou trabalhando com um grupo de hackers nos Estados Unidos. Ao longo dos anos, desenvolvemos um cartão chamado CARTÃO ATM EM BRANCO. Com este cartão em sua posse, você poderá sacar entre 5.000 e 20.000 diariamente em qualquer caixa eletrônico.
Ah sim, é verdade. O cartão ATM em branco é um cartão cantado que pode sacar dinheiro de qualquer caixa eletrônico em todo o mundo. Estes cartões vêm em Visa/MasterCard. Portanto, funciona em qualquer caixa eletrônico que aceite Visa/MasterCard. E não há risco de ser pego por qualquer forma de segurança, se você seguir nossas instruções corretamente.
Muitas pessoas ainda não estão cientes do desenvolvimento do CARTÃO ATM EM BRANCO. Para mais informações sobre como comprar este cartão, você pode nos escrever através de:
E-mail: immaculateblankatmcard@gmail.com
Escreva-me diretamente no WhatsApp ou Telegram: +12017548785
Esta é uma chance para você ficar rico, aproveite esta oportunidade agora e também, prestamos outros tipos de serviços de hackers.
Nota: Por favor, apenas pessoas sérias.
Bom dia a todos que estão lendo esta mensagem
ResponderExcluirse você está passando por momentos difíceis ou pobreza
se você tem dívidas e precisa de ajuda urgente para pagá-las
se você precisa de dinheiro para iniciar um negócio ou para qualquer situação importante
Então aconselho você a solicitar o cartão master/ATM já carregado, com este cartão master/ATM você pode fazer saques grátis de até $67.000,00 dólares todas as semanas de 6 meses a 3 anos dependendo dos meses/anos que você precisa. Eu usei esse cartão master/ATM e gosto que você também use e seja feliz. Para obter este cartão master / ATM, entre em contato com o e-mail para respostas imediatas thomasunlimitedhackers@gmail.com