O blog da AWS
Otimize a escalabilidade de aplicações WebSocket com o API Gateway no Amazon EKS
Introdução
O WebSocket é um protocolo de comunicação comum usado em aplicações da web para facilitar a troca de dados bidirecional em tempo real entre cliente e servidor. No entanto, quando o servidor precisa manter uma conexão direta com o cliente, pode-se limitar a capacidade do servidor em reduzir a escala quando há clientes de longa duração. Essa redução pode ocorrer quando os nós são subutilizados durante períodos de baixo uso.
Neste post, demonstramos como redesenhar uma aplicação web para alcançar o escalonamento automático mesmo para clientes de longa duração, com alterações mínimas na aplicação original.
Contexto
Em muitos casos, as aplicações locais existentes são conteinerizadas e implantadas no Amazon Elastic Kubernetes Service (Amazon EKS) ou no Kubernetes, ou novas aplicações são escritas nativamente para lidar com conexões WebSocket. Essas aplicações podem ser escritas em várias linguagens, com bibliotecas e estruturas de suporte (por exemplo, Springboot para Java, SignalR ou API Websocket para .NET, websockets em python disponíveis via pip, etc.).
Quando uma aplicação com um servidor WebSocket é implantada em um servidor, ela aceita conexões de clientes. Estes, podem permanecer abertos por um longo período de tempo, às vezes por horas, dependendo do caso de uso. Durante períodos de tráfego intenso, o escalonamento automático horizontal de pods do Kubernetes aumenta a escala dos pods para atender à demanda. No entanto, reduzir os pods pode ser difícil quando restam conexões persistentes de clientes de longa duração que ainda não foram encerrados.
Além disso, cada comando recebido de um cliente pela conexão WebSocket pode exigir chamadas adicionais para vários microsserviços antes de enviar uma resposta. Isso limita o uso de um modelo de chamada assíncrona ou arquitetura orientada a eventos, pois o mesmo pod que tem o WebSocket aberto deve responder ao cliente, o que também limita a escalabilidade e o design de outros componentes da aplicação.
A arquitetura da aplicação pode ser simplificada transferindo a manutenção da conexão do WebSocket do pod Amazon EKS para outro serviço, como o Amazon API Gateway. Essa abordagem permite aumentar e diminuir a escala dos pods sem se preocupar com conexões persistentes e permite o uso de modelos de chamada assíncrona e arquitetura orientada a eventos para outros componentes.
Além disso, o uso de um Amazon API Gateway pode oferecer outros benefícios, como a capacidade de realizar gerenciamento avançado de tráfego (ou seja, roteamento, limitação e armazenamento em cache). Isso pode ajudar a otimizar ainda mais o desempenho da aplicação, além de fornecer maior flexibilidade e controle sobre o fluxo de tráfego.
Escalando o Amazon EKS
Usando o Kubernetes Event Driven Autoscaling (KEDA), podemos utilizar métricas mais granulares para ajudar a escalar nosso back-end do Amazon EKS para lidar com o aumento do tráfego para a API WebSocket e reduzir a escala quando necessário. Para aqueles que não estão familiarizados com o KEDA, é um projeto de código aberto que permite o escalonamento automático das cargas de trabalho do Kubernetes com base em várias fontes de eventos, em vez de apenas métricas de CPU ou memória. Com o KEDA, você pode impulsionar a escalabilidade de qualquer contêiner no Amazon EKS com base no número de eventos que precisam ser processados. O escalonamento baseado em CPU e memória usado pelo Horizontal Pod Autoscaler (HPA) pode não ser a melhor métrica para escalar, pois poderá não refletir a carga real da sua aplicação. O KEDA permite que você escale sua aplicação de maneira mais precisa e eficiente, permitindo que você responda rapidamente às mudanças nos padrões de tráfego.
O número de web sockets abertos no API Gateway a qualquer momento pode ser usado para ampliar os pods para lidar com mais solicitações de clientes e diminuir a escala quando este número diminur. Além disso, podemos adicionar uma segunda métrica da taxa de mensagens do WebSocket. Essa métrica mede a taxa na qual as mensagens do WebSocket são enviadas. Se a taxa de mensagens do WebSocket exceder um determinado limite, poderemos escalar pods adicionais para lidar com o aumento do tráfego.
Há mais um aspecto do escalonamento que precisamos abordar. O KEDA escala os pods para lidar com cargas adicionais à medida que mais solicitações são enviadas para o back-end do Amazon EKS a partir do Amazon API Gateway. À medida que mais pods são adicionados, eles consomem mais recursos nos nós até não poderem mais adicionar mais pods. Novos nós precisam ser adicionados ao cluster para oferecer suporte a mais pods. O Kubernetes Cluster Autoscaler é comumente utilizado como mecanismo de escalonamento para a maioria das implantações do Kubernetes; no entanto, o Cluster Autoscaler não é tão flexível quanto o Karpenter devido à exigência de que cada nó em um grupo de escalonamento automático tenha o mesmo perfil de recursos (ou seja, vCPU e memória) e manchas. O Karpenter é um projeto de código aberto que reduz os custos de computação e minimiza a sobrecarga operacional, ao dimensionar corretamente sua computação para sua carga de trabalho. O Karpenter tem a capacidade de abordar toda a gama de tipos de instância do Amazon Elastic Compute Cloud (Amazon EC2) disponíveis na AWS. Com o Karpenter, podemos aumentar e reduzir os nós de forma eficiente, conforme necessário. Você pode aprender mais sobre o Karpenter nesta postagem.
Visão geral da solução
Para superar as limitações de escalabilidade resultantes das conexões abertas do WebSocket, uma solução potencial é utilizar o Amazon API Gateway para estabelecer conexões do WebSocket com clientes e se comunicar com os serviços de back-end do Amazon EKS por meio de uma API REST, em vez do WebSocket. Além disso, para armazenar IDs de sessão para nossa aplicação, use o Amazon DynamoDB.
Para implementar essa solução, criamos uma API WebSocket do Amazon API Gateway, que gerencia as conexões de entrada dos clientes. Os clientes podem se conectar à API WebSocket por meio de um nome de domínio personalizado ou do endpoint padrão do API Gateway. O API Gateway é responsável por gerenciar o ciclo de vida das conexões do WebSocket e manipula as mensagens recebidas dos clientes.
Quando o cliente envia uma mensagem para o API Gateway, ele a encaminha para a aplicação executando no Amazon EKS usando uma chamada REST. Os serviços do Amazon EKS podem então processar a mensagem e enviar uma resposta de volta ao API Gateway, que pode então encaminhar a resposta de volta ao cliente.
Ao adotar essa abordagem, o API Gateway gerencia as conexões do WebSocket, permitindo que os serviços de back-end do Amazon EKS sejam ampliados ou reduzidos conforme necessário, sem afetar os clientes. Como o API Gateway se comunica com os serviços apoiados pelo Amazon EKS usando chamadas de API REST em vez de WebSockets, não há conexões abertas para impedir o encerramento dos pods. É importante notar que o Karpenter não está implementado atualmente no passo a passo a seguir, mas pode ser facilmente instalado consultando a documentação oficial.
Devido ao salto adicional via Amazon API Gateway e ao acesso ao Amazon DynamoDB para obter respostas, ele adiciona uma latência adicional que deve ser mínima na maioria dos casos. Se sua aplicação for sensível à latência, um teste de desempenho deve ser feito para garantir que você seja capaz de fornecer as respostas dentro do Acordo de Nível de Serviço (SLA) exigido.
Pré-requisitos
Você precisaria ter uma conta da AWS com os seguintes recursos pré-criados antes de executar as etapas de implementação.
- Um cluster Amazon EKS
- Um registro do Amazon Elastic Container Registry (Amazon ECR) ou um registro docker equivalente para hospedar a imagem do docker. Neste post, usaremos o Amazon ECR para hospedar a imagem da aplicação
- O Amazon EKS exige que o controlador do AWS Load Balancer seja instalado
- Uma máquina de teste com o cliente wscat instalado para teste.
- As roles do AWS Identify and Access Management (AWS IAM) para contas de serviço precisam ser configuradas para a implantação do Kubernetes com a política apropriada que permitirá que os pods invoquem uma chamada de API no Amazon API Gateway. Você pode seguir este guia do usuário para configurar roles do IAM para a conta de serviço (IRSA) e esta política para permitir que seus pods invoquem a chamada de API.
Passo a passo
- O cliente inicia uma conexão WebSocket com o Amazon API Gateway.
- O Amazon API Gateway recebe a solicitação de conexão do WebSocket e cria uma sessão do WebSocket para o cliente. Em seguida, o Amazon API Gateway recebe a mensagem e a encaminha para a API REST de back-end em execução no Amazon EKS.
- A mensagem é enviada para um pod no cluster por meio do Ingress Controller. O controlador do AWS Load Balancer pode ser usado como controlador de entrada. A aplicação em execução no pod determina se o ID da sessão existe na tabela do Amazon DynamoDB. Se não existir, ele cria um novo item na tabela.
- A aplicação em execução no pod processa a solicitação e gera uma resposta para o Amazon API Gateway.
- O Amazon API Gateway recebe a resposta e a encaminha para o cliente por meio da conexão WebSocket.
- O cliente recebe a resposta e pode continuar enviando e recebendo mensagens por meio da conexão WebSocket.
- Quando há um grande número de solicitações do WebSocket ou taxas de mensagens aumentadas, a KEDA escala o número de réplicas do Kubernetes para lidar com a carga adicional com base nos eventos. Consulte a seção Escalabilidade do Amazon EKS para obter mais detalhes sobre a escalabilidade.
- Por outro lado, quando há uma queda na solicitação do WebSocket ou uma diminuição nas taxas de mensagens, o KEDA reduz o número de réplicas do Kubernetes e reduz o número de pods sem problemas, pois não há conexões abertas do WebSocket. Quando um pod Kubernetes precisa ser reduzido, o sinal SIGTERM garante que o pod seja encerrado normalmente.
- Durante a redução, se mensagens adicionais forem enviadas por meio de conexões abertas do WebSocket, o back-end do Amazon EKS recuperará o ID da sessão da tabela do Amazon DynamoDB. Definir um atributo Time to Live (TTL) na tabela do Amazon DynamoDB faz com que ele automaticamente realize o garbage collection das suas sessões e evita a necessidade de você mesmo faze-las.
Etapas de implementação
Consulte os Pré-requisitos antes de executar as etapas abaixo.
1. Faça o download dos arquivos do projeto:
git clone https://github.com/aws-samples/websocket-eks
2. Crie a imagem do contêiner para uma aplicação de amostra. Acesse o registro do Amazon ECR e veja os comandos view push. Ele tem instruções para criar e enviar a imagem do docker para o Amazon ECR.
3. Crie e aplique o arquivo de implantação para o serviço:
Substitua <ecr image> pela localização da sua imagem em Deployment-Service.yml kubectl apply -f Deployment-Service.yml Execute o comando a seguir para confirmar que a implantação foi criada com sucesso kubectl get deployment websocket-microservice
kubectl apply -f NodePort-Service.yml Execute o comando a seguir para confirmar que o serviço foi criado com sucesso kubectl get service websocket-restapp-nodeport-service
5. Crie um Ingress Application Load Balancer (ALB):
kubectl apply -f ALBingress.ymlExecute o comando a seguir para obter o endereço do ALB kubectl get ingress ingress-websocket-restapp-service
6. Crie o API Gateway Websocket executando o comando a seguir. Substitua o endereço do ALB pelo seu próprio do comando anterior na etapa 5:
aws cloudformation create-stack --stack-name websocket-api --template-body file://. /websocket-api-gateway-cfn.yml --parameters parameterKey=integrationURI, parameterValue=http://<ALB Address>
7. Edite o arquivo Update-Deployment-Service.yml e substitua-o pelo <IRSA ServiceAccount> nome da Conta de Serviço que você criou como parte do pré-requisito, além de atualizá-lo com a imagem correta do contêiner.
Execute o comando a seguir para aplicar a atualização à implantação:
kubectl apply -f update-deployment-service.yml
Atualize sua política do AWS IAM para conceder a role somente acesso ao seu API Gateway e nenhum recurso adicional como parte das melhores práticas de segurança.
8. Teste com o cliente. Recupere a URL do API Gateway da saída do Amazon CloudFormation e execute o seguinte comando:
wscat --connect wss://<Your API gateway url>
9. No prompt do wscat, digite qualquer mensagem (por exemplo, olá) e ela deverá repetir a mensagem indicando que a solução de ponta a ponta está funcionando.
Limpando
Limpe os recursos posteriormente para não incorrer em cobranças futuras. Exclua a stack do Amazon CloudFormation usando o comando abaixo:
aws cloudformation delete-stack --stack-name websocket-api
Exclua os recursos do kubernetes:
kubectl delete -f albIngress.yml
kubectl delete -f nodePort-Service.yml
kubectl delete -f deployment-Service.yml
Exclua todos os outros recursos criados como parte das etapas de pré-requisito.
Custo
A introdução de componentes adicionais, como API Gateway e Amazon DynamoDB, na arquitetura fornece capacidade de escalabilidade e introduz alguns custos adicionais. Consulte os preços do API Gateway e os preços do DynamoDB para obter uma estimativa baseada na sua utilização.
Arquitetura alternativa
A arquitetura proposta também se aplica a aplicações que estão sendo executadas no Amazon ECS com pequenas diferenças. O controlador de entrada é substituído pelo ALB e não há necessidade de usar o KEDA e o Karpenter para escalonamento automático. Em vez disso, o escalonamento automático de aplicações da AWS pode ser usado para escalar automaticamente com métricas personalizadas.
Outra abordagem seria usar o Amazon API Gateway com o AWS Lambda. A função AWS Lambda manipula a lógica de processamento semelhante ao pod Amazon EKS. Essa abordagem requer mudanças significativas no código, mas se beneficiaria de ser totalmente serverless.
Conclusão
Neste post, mostramos como mover a manipulação do Websocket do pod Kubernetes para um Amazon API Gateway. Ao transferir a manipulação do Websocket para o Amazon API Gateway, o pod não é mais responsável pelo gerenciamento das conexões, o que facilita o escalonamento automático do pod. No geral, essa abordagem simplifica o gerenciamento das conexões do Websocket e permite um uso mais eficiente dos recursos, o que pode levar a um melhor desempenho da aplicação.
Abaixo estão algumas referências que você pode ler para obter informações adicionais:
Escalonamento automático do Karpenter
Websocket com API Gateway e back-end Lambda
Este artigo foi traduzido do Blog da AWS em Inglês.