Na quinta-feira, dia 18/Março/2021 vamos falar do Hub de eventos que produzi para atender uma demanda particular. Eu não precisava de extrema performance e redundância, mas e se precisasse?
Um assunto que eu estava abordando em Janeiro era o Hub, que agora vira conteúdo no Docker Definitivo. Mas também vira conteúdo aqui fora, claro que é impossível entregar a mesma profundidade aqui fora, mesmo em uma live de 8 horas, imagina uma de 4 horas, mas é o que fizemos nesse dia.
Contexto
Hoje eu trabalho usando diversas aplicações especialistas para compor todo o stack que dá suporte ao Docker Definitivo, Masterclasses e várias outras iniciativas. As lives privadas do Zoom contam com alunos de todas as turmas. Claro que uns são mais presentes que outros.
Então são quase 160 alunos que navegam no site, assistem conteúdo, precisam de acesso ao github. Por outro lado, no momento da venda, eles podem vir da hotmart, da kiwify, do stripe. Lidar com tudo isso é um desafio. Fora as inscrições na lista de espera que acontecem entre uma turma e outra, e que usa outra plataforma. Ou ainda as conversas com o bot, que ainda usam uma outra plataforma.
Esse caos precisa de ordem.
O Hub é uma solução de integração, para receber os eventos e tratá-los, criando uma visão unificada de acordo com o meu processo. Ele precisa receber dados de diversos lugares e centralizar a visão ao redor de alunos e não alunos.
Ao mesmo tempo que existe uma demanda de negócio, existe também novas iniciativas que preciso lidar, como um bot dedicado ao passo-a-passo do Docker Definitivo, e uma visão unificada da jornada dos alunos.
A solução
A solução consiste em criar um hub de eventos, seguro, escalável, resiliente.
Entregar alta disponibilidade e resiliência nesse tipo de cenário é uma atividade que me força a quebrar o processamento em 2 partes. Ao invés de ter uma API que faça um insert em algum lugar, é mais inteligente para esse caso, enviar uma mensagem para uma fila. Uma vez em uma fila, garantimos que a mensagem “está dentro de casa”, em um lugar seguro.
Essa quebra assíncrona visa entregar a mensagem o mais rápido possível para o RabbitMQ. Uma vez nele, eu tenho algumas garantias:
- a segurança de que a mensagem será processada
- capacidade de reprocessamento (por falha ou qualquer outra necessidade)
- posso escalar consumidores indefinidamente
- tolerância a shutdowns e falhas que tombem todo o cluster (por conta garantia de persistência)
- Capacidade de distribuir 1 única mensagens em diversos interessados.
Essa são algumas das garantias que temos com esse desenho. E não é nada intuitivo.
À primeira impressão é de que seria mais fácil fazer algumas API’s que resolvessem tudo direto na controller do web api. Com toda certeza seria mais fácil.
Minha demanda atual por escalabilidade beira o nulo, com alguns eventos por minuto, no entanto há planos para o emprego dessa solução em algo muito maior, com uma demanda de milhares de mensagens por minuto em um cenário comum e milhões de mensagens em momentos de pico.
Esses motivadores me apontam para uma solução que seja agnóstica, e que atenda aos mais variados workloads. Esse é um conteúdo que vamos abordar no Canal .NET no dia 18 de Março, às 21h, comigo e com o Renato Groffe.
Docker
Docker dá suporte a diversas coisas nessa arquitetura. Desde a subida do RabbitMQ, que está fora do cluster kubernetes, o desenvolvimento com containers usando docker desktop. Só o Kubernetes mesmo que nesse setup está usando containerd, um projeto extraído do docker há alguns anos.
Kubernetes
Ao mesmo tempo, eu havia prometido para os alunos do Docker Definitivo um módulo dedicado à Kubernetes, e esse virou a bola da vez aqui do lado de dentro. Então resolvi empregá-lo nesse projeto com um cluster de 2 nós, um cluster modesto com 2 servidores, ambos AMD Ryzen 7 1700X Eight-Core @ 16x 3.4GHz, com 64GB RAM cada, hospedados na Alemanha. E eu tenho mais de um bom motivo para subir essa infra na mão. Isso que eu chamo de skin in the game.
NGINX
O NGINX é o Ingress Controller principal da nossa infra com Kubernetes. E como eu já havia falado no passado, eu queria a possibilidade de subir aplicações plugadas diretamente nesse ingress controller.
Kong (API Gateway)
Enquanto aplicações html simples poderiam tirar proveito do NGINX, API’s precisam lidar com gestão, segurança e por isso um API Gateway se faz necessário. Como esse cluster é um cluster misto de aplicações web e API’s, eu me aventurei a colocar um segundo Ingress Controller no Kubernetes, e vou dizer: Pensa em algo maluco de ver funcionando!
O kong poderia ser o único ingress controller do cluster, mas eu queria explorar as possibilidades e entender até que ponto daria para fazer isso. No kubernetes há uma questão fundamental que é o que o NGINX default do NGINX Ingress Controller oferece de recursos, e é substancialmente limitado em relação ao que podemos fazer buildando nossa própria versão do NGINX com plugins adicionais, como o Google PageSpeed Module.
Não tendo essa possibilidade, que entrega features que o kong mesmo tendo como ancestral comum o nginx, não possui, fica difícil argumentar sobre a diferença entre o Kong Ingress Controller e o NGINX Ingress Controller.
O NGINX standalone é amis rápido que o kong, primeiro por ser sempre uma versão superior, e por ser mais enxuto, sem a camada LUA do OpenResty e sem a implementação do Kong em si (em lua), no entanto mesmo performance sendo um drive universal, a diferença é tão insignificante que não justifica a complexidade.
Mas no caso dessa aplicação, eu escolhi usar o kong como API Gateway, pois preciso autenticar os serviços que estão fazendo as requisições para o hub. Então em vez de colocar essa lógica no serviço, eu deleguei essa responsabilidade para o Kong. Como resultado eu tenho um serviço mais simples.
ASP.NET Core 5
Curiosamente fui olhar o nome para ver se seria ASP.NET 5 ou ASP.NET Core 5, e lembrei da confusão que estão esses nomes aqui no mundo Microsoft. Bom estou torcendo aqui para que no .NET 6 tenhamos apenas ASP.NET 6, mas isso é irrelevante.
O importante aqui é que a aplicação que realiza a publicação da mensagem na fila é uma aplicação ASP.NET Core. Uma WEB API que recebe a requisição que passou pelo Kong e foi autenticada, e faz a publicação em uma exchange do RabbitMQ.
Algumas coisas interessantes estão sendo empregadas aqui, como o uso de healthchecks para a integração com o ciclo de vida do kubernetes. São alguns endpoints dedicados à liveness probe, readness probe, heathcheck e outros internos.
RabbitMQ
Aqui acaba a primeira parte da nossa solução.
Uma vez que a mensagem chega ao RabbitMQ, acaba a fase crítica do processo. Ou seja, eu escolhi o caminho mais curto, mais rápido e eficiente de ter a mensagem em um lugar seguro. Em cenários de pressão, o RabbitMQ se sai melhor que um banco de dados, levando em conta a resiliência (persistência em disco) como uma garantia. Só isso já faz o redis ser jogado de lado para essa operação.
Resiliência e escalabilidade são os pontos fortes dessa decisão. Outro ponto é que eu posso receber eventos de todas as naturezas, cada um no seu momento. Em algumas semanas um novo conjunto de aplicações fará o seu processamento. Mas daí pra frente, não importa o momento. Posso inclusive ter mensagens que só serão processadas no futuro, talvez daqui a 1 mês, ou talvez daqui a 1 ano. Pouco me importa. A relevância é garantir que a mensagem chegou à fila.
Tem alguns truques com as rotas HTTP que aplico aqui nesse desenho, mas em resumo elas chegam ao RabbitMQ de alguma forma. Isso torna a API mais dinâmica.
Jenkins (CI/CD)
O Jenkins, como sempre está presente ajudando no build e deploy da aplicação. Aqui temos algumas customizações interessantes no pipeline e o uso de um kubectl dockenizado.
Outros assuntos pertinentes
Ainda tem outros assuntos ligados a esse projeto que vou abordar aqui no gago.io, como a virtualização do Kubectl em uma imagem docker, isso é excelente para demonstrar a diferença do CMD para Entrypoint com docker, além de tornar mais explícito o pipeline de build.
Hub de Eventos: processando webhooks like a boss!
Não deixe de se inscrever lá no meetup para saber mais.
0 comentários