Olá! Vamos falar sobre RabbitMQ? O RabbitMQ é um servidor de mensagens feito especialmente para trabalhar com Advanced Message Queuing Protocol(AMQP), um protocolo de comunicação em rede que permite que aplicações se comuniquem.
Na Concrete Solutions já falamos um pouco sobre conceitos RabbitMQ, em um artigo que você lê aqui. Com a utilização de um middleware para controle de mensageria, a integração de sistemas fica geralmente mais barata e menos complexa. O AMQP torna possível a integração entre vários sistemas diferentes e também permite que diferentes produtos, que implementam este mesmo protocolo, possam trocar informações, o que torna o AMQP um dos pioneiros na evolução dos sistemas de troca de mensagem.
Aqui vamos utilizar Docker-Compose para “subir” quatro instâncias do RabbitMQ junto com o HAProxy, um Load Balancer para distribuir, eficientemente, o tráfego entre os nós do RabbitMQ.
A ideia é que o HAProxy receba uma publicação de alguma fila, de alguma exchange e redirecione para um dos nós do RabbitMQ. O cluster estaria, então, replicando os dados em todos os nós e, caso algum deles falhe, temos os dados nos demais e o próprio RabbitMQ escolhe outro nó para ser o novo “master”.
Mão na massa
Vamos utilizar Docker para subir nossas instâncias de RabbitMQ, lembra? Para que funcione é preciso ter o Docker já instalado. Aqui está o tutorial, verifique qual SO está usando e seja feliz.
É necessária a instalação do Docker-Compose também. Para instalar é um pouco mais simples que o Docker em si. Nesse caso, você pode seguir esse tutorial.
Com tudo instalado, vamos começar a destruir o teclado.
Vamos começar criando nosso script de subida dos nossos docker’s. Crie um arquivo chamado docker-compose.yml: vim docker-compose.yml Agora copie e cole o código abaixo no seu arquivo:
version: '2' services: rabbitmq1: image: rabbitmq:3-management container_name: rabbitmq1 hostname: rabbitmq1 ports: - "8080:15672" network_mode: "bridge" volumes: - $PWD/storage/rabbitmq1:/var/lib/rabbitmq environment: - RABBITMQ_ERLANG_COOKIE=This_is_my_secret_phrase - RABBITMQ_DEFAULT_USER=mqadmin - RABBITMQ_DEFAULT_PASS=Admin123XX_ - CLUSTERED=true rabbitmq2: image: rabbitmq:3-management container_name: rabbitmq2 hostname: rabbitmq2 ports: - "8081:15672" network_mode: "bridge" volumes: - $PWD/storage/rabbitmq2:/var/lib/rabbitmq environment: - RABBITMQ_ERLANG_COOKIE=This_is_my_secret_phrase - RABBITMQ_DEFAULT_USER=mqadmin - RABBITMQ_DEFAULT_PASS=Admin123XX_ - CLUSTERED=true links: - rabbitmq1:rabbitmq1 rabbitmq3: image: rabbitmq:3-management container_name: rabbitmq3 hostname: rabbitmq3 ports: - "8082:15672" network_mode: "bridge" volumes: - $PWD/storage/rabbitmq3:/var/lib/rabbitmq environment: - RABBITMQ_ERLANG_COOKIE=This_is_my_secret_phrase - RABBITMQ_DEFAULT_USER=mqadmin - RABBITMQ_DEFAULT_PASS=Admin123XX_ - CLUSTERED=true links: - rabbitmq1:rabbitmq1 - rabbitmq2:rabbitmq2 rabbitmq4: image: rabbitmq:3-management container_name: rabbitmq4 hostname: rabbitmq4 ports: - "8083:15672" network_mode: "bridge" volumes: - $PWD/storage/rabbitmq4:/var/lib/rabbitmq environment: - RABBITMQ_ERLANG_COOKIE=This_is_my_secret_phrase - RABBITMQ_DEFAULT_USER=mqadmin - RABBITMQ_DEFAULT_PASS=Admin123XX_ - CLUSTERED=true links: - rabbitmq1:rabbitmq1 - rabbitmq2:rabbitmq2 - rabbitmq3:rabbitmq3 haproxy: image: haproxy:1.6 container_name: haproxy hostname: haproxy ports: - 5672:5672 - 20000:20000 network_mode: "bridge" links: - rabbitmq1:rabbitmq1 - rabbitmq2:rabbitmq2 - rabbitmq3:rabbitmq3 - rabbitmq4:rabbitmq4
Parece complicado, mas eu juro, não é! Vamos dar uma olhada em cada um dos parâmetros.
- version: ‘2’: Com isso, já trabalharemos com as funcionalidades da versão mais recente do docker-compose;
- services: Aqui vamos listar todos os serviços que vamos utilizar, todos os docker que vão subir para que nossa aplicação funcione;
- rabbitmq1: Nome que daremos para os nossos serviços do RabbitMQ;
- image: rabbitmq:3-management: Nome da imagem que usaremos para nosso RabbitMQ;
- container_name: rabbitmq1: Vamos dar nomes para nossos containers para ficar mais fácil administrá-los;
- hostname: rabbitmq1: Vamos colocar o nome dos nossos hosts, isso facilitará e muito a nossa clusterização, pois sempre que utilizar um docker exec os nomes internos dos conteiners serão os mesmos;
- ports: – “8080:15672”: Com isto poderemos verificar, por meio do nosso browser, se nosso RabbitMQ está funcionando: localhost:8080;
- network_mode: “bridge”: Para que nosso cluster funcione, precisamos que todos os nós estejam na mesma “rede”, neste caso na bridge;
- volumes: – $PWD/storage/rabbitmq1:/var/lib/rabbitmq: Criação dos volumes que iremos utilizar;
- environment: Aqui vamos definir nossas variáveis de ambiente utilizadas por cada um dos serviços;
- RABBITMQ_ERLANG_COOKIE=This_is_my_secret_phrase: Para utilizar o RabbitMQ, precisamos definir um Erlang Cookie para cada nó se comunicar entre si;
- RABBITMQ_DEFAULT_USER=mqadmin: Definimos um usuário para nosso nó;
- RABBITMQ_DEFAULT_PASS=Admin123XX: Definimos uma senha para nosso usuário;
- CLUSTERED=true Isto é apenas para definir que rodaremos em modo cluster;
- links: Com este parâmetro definimos, todos os links dos nossos serviços com demais serviços da stack.
As configurações do HAProxy são as mesmas que o RabbitMQ no Docker-Compose.
Agora vamos preparar nossa imagem do HAProxy, poderíamos utilizar a default, mas precisamos alterar o arquivo de configuração para atender as nossas necessidades.
Primeiro, vamos criar um Dockerfile para fazer uma imagem do HAProxy que atenda os nossos requisitos: vim Dockerfile
Agora cole no Dockerfile os comandos abaixo:
FROM haproxy:1.6 ENV HAPROXY_USER haproxy RUN groupadd --system ${HAPROXY_USER} && \ useradd --system --gid ${HAPROXY_USER} ${HAPROXY_USER} && \ mkdir --parents /var/lib/${HAPROXY_USER} && \ chown -R ${HAPROXY_USER}:${HAPROXY_USER} /var/lib/${HAPROXY_USER} COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg CMD ["haproxy", "-db", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
Com esse Dockerfile será criada uma imagem com um usuário haproxy e o nosso arquivo de configuração já será copiado para dentro dela, deixando-a pronta para uso.
No mesmo diretório, vamos criar um arquivo chamado haproxy.cfg com o seguinte conteúdo:
global log 127.0.0.1 local0 info chroot /var/lib/haproxy user haproxy group haproxy listen stats bind *:20000 mode http stats enable stats refresh 3s stats uri / timeout client 10000 timeout connect 10000 timeout server 30000 stats auth guest:guest listen rabbitmq-tcp bind *:5672 mode tcp log global retries 4 option tcplog option persist balance roundrobin server rabbitmq1 rabbitmq1:5672 check inter 5s rise 2 fall 5 server rabbitmq2 rabbitmq2:5672 check inter 5s rise 2 fall 5 server rabbitmq3 rabbitmq3:5672 check inter 5s rise 2 fall 5 server rabbitmq4 rabbitmq4:5672 check inter 5s rise 2 fall 5
Essas configurações vão permitir o balanceamento dos nossos nós, checando, em um intervalo de 5 segundos, se estão online ou não. Roundrobin é um algoritmo simples de escalonamento que equilibra a carga local e distribui entre os nós do cluster. Nossos nós estarão sendo ouvidos e checados pela porta 5672, que é a porta padrão de comunicação TCP do RabbitMQ.
Pronto! Já criamos os arquivos de configuração. Agora vamos fazer o build da nossa nova imagem do HAProxy. No mesmo diretório que criamos o Dockerfile e o haproxy.cfg, digite:
docker build -t haproxy:1.6
O processo deverá ser algo parecido com isso:
Agora vamos juntar tudo isso. Calma que tudo vai dar certo, eu prometo.
Vamos criar um shell script bem básico para subir tudo de uma vez. Nesse mesmo diretório que criamos os outros arquivos, vamos criar um arquivo (prometo que é o último) com o nome de start_all.sh: vim start_all.sh e cole o código abaixo:
#!/bin/bash # "Bruno Luis Cardoso Novo <bruno.novo@concrete.com.br>" # Script to manage the containers execution ## Variables for images build. JOIN_RABBIT2_RABBIT1="rabbitmqctl stop_app; rabbitmqctl join_cluster rabbit@rabbitmq1; rabbitmqctl start_app" JOIN_RABBIT3_RABBIT1="rabbitmqctl stop_app; rabbitmqctl join_cluster rabbit@rabbitmq1; rabbitmqctl start_app" JOIN_RABBIT4_RABBIT1="rabbitmqctl stop_app; rabbitmqctl join_cluster rabbit@rabbitmq1; rabbitmqctl start_app" OPTIONAL_COMMAND="rabbitmqctl set_policy ha-all '' '{\"ha-mode\":\"all\", \"ha-sync-mode\":\"automatic\"}'" #Subindo os container's do rabbitmq echo -n "Starting container..." docker-compose down docker-compose up -d sleep 15 docker exec -ti rabbitmq2 bash -c "$JOIN_RABBIT2_RABBIT1" docker exec -ti rabbitmq3 bash -c "$JOIN_RABBIT3_RABBIT1" docker exec -ti rabbitmq4 bash -c "$JOIN_RABBIT4_RABBIT1" docker exec -ti rabbitmq1 bash -c "$OPTIONAL_COMMAND"
Como estamos utilizando Docker-Compose, fica muito mais fácil de “subir” um container. Ele verifica se essa stack já está em execução e termina a mesma caso seja necessário (docker-compose down). Depois, em modo background(-d), ele sobe todos os nossos serviços, um por um. Foi colocado um sleep de 15 segundos para dar tempo de todos eles subirem e, depois da ligação entre eles, executar, dentro de cada um dos nossos containers do RabbitMQ, os comandos para os nós se juntarem ao cluster. Logo depois disso, foi colocado um OPTIONAL_COMMAND para definirmos uma política para nosso cluster.
No caso da política, foi definido que o cluster funcionará em modo mirror, todos os nós terão as mesmas informações. Dependendo do tipo de cluster que sua aplicação necessite, o tipo de política pode ser alterada sem problemas.
Dê permissão à ele: chmod +x start_all.sh
E execute: ./start_all.sh
Com isso vamos poder ver algo parecido com isso:
Para verificarmos se tudo subiu como planejado, vamos digitar: docker ps:
Podemos abrir o browser e digitar: localhost:8080, por exemplo, e veremos a tela de login do RabbitMQ. Digite o usuário e a senha que configuramos e Voilá:
Aleluia, irmãos! Tudo funcionando a pleno vapor, mas e se tivermos algum problema em algum dos nós? E se algum deles cair? Vamos fazer um teste. Vamos parar um dos nós e verificar como o RabbitMQ vai se comportar. Volte para o terminal e digite: docker stop rabbitmq1.
Quando voltamos para o browser, não conseguimos mais ver a página do RabbitMQ porque o paramos. Ao invés de utilizar a porta 8080, vamos alterar para 8081 que é a porta do rabbitmq2 que configuramos no docker-compose.
Podemos verificar que o rabbitmq1 que paramos está off, mas o próprio RabbitMQ já verificou isso e já promoveu um novo nó para ser nosso “master” e continuarem seu funcionamento.
Para retornar o rabbitmq1 de volta das cinzas é só digitar: docker start rabbitmq1 e pronto. Tudo de volta à normalidade.
Uma outra maneira de ficarmos de olho nos Rabbit’s é utilizar o painel do HAProxy: localhost:20000
Link’s interessantes
Bom pessoal, é isso. Essa foi uma maneira mais simples de subirmos um RabbitMQ e começarmos a utilizá-lo nas nossas futuras aplicações. Se quiser se aprofundar ainda mais no assunto, existem vários links bem interessantes e que darão uma visão bem mais técnica de sua utilização.
- RabbitMQ – Site Oficial
- Introducao ao AMQP com Rabbitmq
- Por que utilizar filas? Implementação com RabbitMq
- O que é RabbitMQ?
Obrigado pelo tempo que disponibilizou para ler este artigo e qualquer dúvida é só chamar. Gostaria de agradecer ao Wesley Silva, Pedro Azevedo e o Kaio Kardamone que me ajudaram muito, esse artigo é de vocês também.
Ficou alguma dúvida ou tem algum comentário a fazer? Aproveite os campos abaixo.
***
Artigo publicado originalmente em: http://www.concretesolutions.com.br/2016/12/19/rabbitmq-docker-compose/