No último post sobre Operators, eu falei sobre o RabbitMQ Cluster Operator for Kubernetes, o papel dele é construir um cluster. Uma vez que o cluster está ativo e em operação, é hora de começar a usar esse cluster. Virtual Host, Exchanges, Filas e Binds, afinal quem e quando eu deveria criar esses recursos?
Eles devem ser criados previamente durante a construção do cluster?
Ou durante o deployment da aplicação?
Ou especificamente pela aplicação?
Essas dúvidas são super comuns com quem está começando a usar RabbitMQ. E só existem 2 respostas para essa pergunta:
“Depende!” é a primeira resposta. E “De todas as formas” é outra resposta equivalentemente correta e adequada.
Que objetos são esses?
No post RabbitMQ & AMQP – #3 – Conceitos abordei cada um desses objetos.
São poucos:
- Virtual Host
- Exchanges
- Filas
- Binds
01/08/2022 | Aliás, acabei de revisar esse post!
Virtual Host
Normalmente virtual hosts são criados previamente por algum administrador, no ato da criação do seu servidor ou cluster RabbitMQ.
Esse tipo de objeto é muito especial e criado com uma freqüência muito baixa. Dinamismo aqui não é muito relevante.
- Principalmente por as conexões serem criadas apontando para virtual host previamente criado.
- Pelo fato de uma conexão não conseguir acessar objetos de outro virtual host.
- Pelo fato de só ser possível manipular e criar virtual hosts, via outra API administrativa do RabbitMQ, acessível via HTTP dependendo do seu setup, ou via linha de comando.
Dessa forma, a criação de virtual host acontece com uma freqüência muito baixa.
Exchanges
A criação de exchanges é um pouco mais comum. Não tanto quanto a criação de filas, mas a criação de exchanges pode acontecer toda vez que você cria um serviço novo dentro de uma aplicação, por exemplo. É possível, e não há nada de errado.
Note que não estou recomendando que seja assim, estou dizendo o que é possível, cuidado para não tomar essa informação como recomendação.
Filas e Binds
Ao mesmo tempo a criação de filas e binds pode tanto ser muito dinâmico, quanto ser tão estático quanto os itens anteriores.
Há uma forma de trabalhar com RabbitMQ onde filas e binds são mais estáticos. Ao mesmo tempo, há cenários de uso onde a criação de filas e binds acontece com uma freqüência impensável antes de conhecer essas táticas de utilização de mensageria.
1 | Filas estáticas
Aqui estamos diante de um cenário comum, algo super básico e mais próximo de quem está começando. Esse é o primeiro cenário que você consegue pensar nos seus primeiros contatos com mensageria. Uma vez que criou o servidor, criou virtual host, talvez tenha criado exchange, criou uma fila, e pronto. Ninguém precisa mexer nisso mais.
Esse é um modelo comum, e é o mais simples de entendermos como funciona. Esse é o típico cenário em que trazer essa criação para o setup do server talvez faça bastante sentido.
2 | Filas de resposta RPC e Callback
Pega um copo d’água, agora começa a ficar complexo. Talvez esteja claro para você que esperar uma resposta seja um problema, seja um motivo para você falhar e tornar um fluxo síncrono. Não é a espera de uma resposta, é a espera síncrona, naquela thread, que é o problema. Esperar a resposta na mesma thread que criou a pergunta.
Se você pode receber uma resposta, sem outra thread, tempos depois, minutos, horas, dias, até meses. Então não há um fluxo síncrono, nesse caso.
Muitos dos nossos projetos precisam enviar comandos e receber respostas. E há 2 formas com RabbitMQ de você realizar esse tipo de operação.
Seja com RPC ou Callback.
Quando você envia um comando para um terceiro, usando RabbitMQ, você pode usar uma fila estática para receber a resposta, esse é um desenho 100% assíncrono. Você não tem controle da thread que receberá a mensagem, só sabe que ela será processada quando for recebida. Não importa quando, pode ser hoje, amanhã, ou ano que vem.
Esse é o modelo de Callback. Ele continua sendo 100% assíncrono, portanto, extremamente resiliente.
Mas há casos em que você precisa da resposta na mesma thread que enviou a demanda, como no caso de uma interação do usuário, por exemplo, como uma consulta ou uma demanda de cálculo, ou algum outro tipo de processamento.
Nesse caso você precisa de um fluxo síncrono.
Se essa for sua realidade, 3 coisas são importantes:
- Você jogou fora a resiliência “infinita”, esse fluxo é muito pouco resiliente em relação ao que poderia ser se usasse callback.
- Esse é um fluxo muito parecido com o fluxo de uma requisição HTTP, por isso encanta e corrompe os novatos.
- Esse desenho exige que seja criada UMA FILA DE RESPOSTA [ A CADA | A TODA | PARA TODA ] [ REQUISIÇÃO | MENSAGEM ].
Então no cenário de RPC você cria filas dinâmicas de resposta, uma para cada mensagem enviada. Quem consome a mensagem que você enviou, espera receber no cabeçalho AMQP a propriedade ReplyTo preenchida com um endereço AMQP de resposta.
3 | Filas Eventuais
Imagine que você tem uma exchange que lida com todo o fluxo de pagamentos de um e-commerce. Essa exchange é do tipo TOPIC, que usa o match entre a routing key da mensagem e a routing key do bind para determinar a(s) fila(s) adequadas para rotear sua mensagem.
Essa exchange recebe 2 tipos de mensagens, Pagamento Solicitado com uma routing key A, e Resposta de Pagamento com uma routing key B.
Para cada uma dessas 2 routing keys, temos uma fila de processamento.
Imagine que eu queira fazer algum tipo de troubleshooting em que seja necessário escutar ao vivo, quais mensagens estão passando?
Nesse caso, cria-se uma fila auto-delete, ou seja, quando o consumidor parar de consumir, a fila será excluída. Ao mesmo tempo, define-se o bind entre a fila e a exchange, usando uma routing key que produza o clone das mensagens, que vão agora também para nossa fila temporária.
Enquanto a aplicação estiver conectada, essa fila está sendo alimentada e recebe uma cópia da mensagem, já que esse é o comportamento padrão quando 2 routing keys fazem match para filas diferentes.
Você pode usar filas eventuais para diversas finalidades. Desde log, até filas de troubleshooting e métricas.
4 Filas estáticas criadas dinamicamente
O modelo mais comum de filas criadas programaticamente está relacionado aao uso de coreografias, que visam automatizar a criação de filas, exchanges e binds.
Dos cenários mais simples aos mais complexos, esse desenho visa no startup da aplicação criar todos os objetos necessários para o fluxo, ou pelo menos a parte mais burocrática e chata deles.
Por exemplo, supondo que você queira usar dead letter. Esse é um tipo de configuração que você não consegue alterar depois da criação da fila. Para alterar uma configuração de dead letter de uma fila será necessário recriar a fila.
Se esse desenho for baseado em uma política de reprocessamento, com delay, por exemplo, precisará de 3 filas e configurações específicas que fazem uma fila depender da outra em cascata.
Tudo isso é complexo de especificar e detalhar, portanto, criar essas coreografias dinamicamente, é uma estratégia comum.
O que é o RabbitMQ Messaging Topology Operator for Kubernetes e como ele pode ajudar?
O RabbitMQ Messaging Topology Operator for Kubernetes é um conjunto de CRD’s para o Kubernetes que possibilitam que você defina Filas, Binds, Virtual Hosts, Exchanges, Usuários e muito mais como configurações do Kubernetes.
Do lado de deployments, services e ingresses você cria exchanges, filas e quase todos os objetos do RabbitMQ.
kind: Vhost apiVersion: rabbitmq.com/v1beta1 metadata: name: rabbitmq-eshop-vhost namespace: eshop-resources spec: name: eshop rabbitmqClusterReference: name: rabbitmq
kind: User apiVersion: rabbitmq.com/v1beta1 metadata: name: rabbitmq-eshop-user namespace: eshop-resources spec: rabbitmqClusterReference: name: rabbitmq # tags: # - administrator importCredentialsSecret: name: rabbitmq-eshop-secret
kind: Permission apiVersion: rabbitmq.com/v1beta1 metadata: name: rabbitmq-eshop-permission namespace: eshop-resources spec: vhost: "eshop" user: "eshop_user" permissions: write: ".*" configure: ".*" read: ".*" rabbitmqClusterReference: name: rabbitmq
kind: Exchange apiVersion: rabbitmq.com/v1beta1 metadata: name: exchange1 namespace: eshop-resources spec: name: exchange1 type: fanout # 'direct', 'fanout', 'headers', and 'topic' autoDelete: false durable: true rabbitmqClusterReference: name: rabbitmq namespace: eshop-resources
kind: Queue apiVersion: rabbitmq.com/v1beta1 metadata: name: fila1 namespace: eshop-resources spec: name: fila1 rabbitmqClusterReference: name: rabbitmq namespace: eshop-resources
kind: Binding apiVersion: rabbitmq.com/v1beta1 metadata: name: exchange1-to-fila1 namespace: eshop-resources spec: source: exchange1 destination: fila1 destinationType: queue # 'queue' / 'exchange' rabbitmqClusterReference: name: rabbitmq namespace: eshop-resources
Acima temos alguns exemplos.
No contexto do eShop-CloudNative
Você pode ver alguns exemplos do que tenho usado no eshop-cloudnative em https://github.com/luizcarlosfaria/eshop-project-infra/tree/main/02-Resources/02.
Vale lembrar que no caso do eShop Cloud Native algumas coisas precisam estar claras. Primeiro é que o que temos nesse repositório (infra) nesse momento é resultado da prova de conceito com foco no setup do ambiente kubernetes, inclusive com a implentação de postgres e RabbitMQ, ambos com alta disponibilidade em cluster, portanto, até concluirmos nosso projeto teremos mudanças.
Sobre as definições no eShop Cloud Native é que a criação de usuários, vhosts e permissões é feita usando o operator. No entanto, a criação de filas e binds serão responsabilidade da aplicação, principalmente por causa da complexidade. Já a criação de exchanges, depende, há espaço para serem criadas manualmente, via configuração ou dinamicamente via aplicação. Cada caso será avaliado individualmente.
Como instalar o RabbitMQ Messaging Topology Operator for Kubernetes
Primeiro é necessário entender que o RabbitMQ Messaging Topology Operator for Kubernetes é um complemento para o RabbitMQ Cluster Operator for Kubernetes.
O Cluster Operator foi abordado no post anterior da série:
Assim o RabbitMQ Messaging Topology Operator for Kubernetes depende de um cluster criado com o RabbitMQ Cluster Operator for Kubernetes, por isso precisamos dos 2 operators nesse caso.
Ou seja, o Messaging Topology Operator não lida com qualquer instancia de RabbitMQ, ele precisa de uma referência para um cluster previamente criado dentro do Kubernetes com o Cluster Operator.
Assim a instalação do Messaging Topology Operator é extremamente simples, basta executar:
kubectl apply -f "https://github.com/rabbitmq/messaging-topology-operator/releases/latest/download/messaging-topology-operator-with-certmanager.yaml"
Uma vez que você realizou a instalação, você está apto a realizar as configurações como eu apresentei no decorrer desse texto.
A documentação oficial está aqui onde você encontre exemplos atualizados de todos os tipos de estruturas que você pode criar:
Conclusão
Entender as estratégias e táticas utilizadas possíveis e comuns na criação desses objetos ajuda no entendimento e análise de ambientes, na hora em que nos deparamos com ambientes existentes ou planejados por outros especialistas.
Umas das crenças comuns é a de que você deve criar todos os objetos previamente e que nunca criará esses objetos dinamicamente. Com se estivéssemos pensando na criação de tabelas em um banco de dados.
Nem preciso dizer que no ambiente corporativo isso gera algum desconforto com equipes de arquitetura e segurança. Principalmente quando estamos diante de equipes e até empresas sem nenhum especialista em RabbitMQ.
Conhecer essas táticas ajuda planejar, implementar e implantar estratégias de mensageria que sejam de fato eficientes.
Entender que boa parte da criação dos objetos é feito pela aplicação ajuda a entender como minimizar erros humanos, como reduzir significativamente o esforço braçal e a necessidade de produzir documentação. Na media que conseguimos antecipar esse momento para a criação do cluster, e principalmente quando conseguimos determinar tudo com base em declarações, como com o RabbitMQ Messaging Topology Operator for Kubernetes, reduzimos totalmente a necessidade de documentos de setup que expressem um passo-a-passo de configuração do RabbitMQ.
Isso nos dá a oportunidade de reduzir a interação humana entre equipes, reduzir a necessidade de intervenção humana para configuração de detalhes do cluster, e tudo isso se traduz em eficiência. Há uma redução de custos clara aqui.
Além de ganharmos transparência.
Ignorar esse tipo de conhecimento custa o esforço braçal, humano, repleto de steps, aumentando a necessidade de atenção e aumentando o risco de falhas humanas, dificultando o dia-a-dia e a utilização da solução e principalmente: limitando seu uso e impossibilitando a adoção de estratégias importantes.
Até o próximo post!
0 comentários