Uma possível receita de arquitetura para microservices: Docker, Consul, Registrator e RabbitMQ

Mônica Ribeiro
bawilabs
Published in
6 min readApr 19, 2016

Sabe aquela época da faculdade onde o cara que manjava muito de C achava que era o mais “fodão” de todos e de tanto se orgulhar disso ele apenas focava em estudar mais sobre C e esquecia do resto?

Então… Por favor, cuidado pra não ser esse cara.

Não estou dizendo para você ser um “profissional genérico”… De forma alguma! Mas, no meu ponto de vista, hoje em dia ser “o cara” em uma tecnologia específica e focar só nela não tem tanta vantagem. Por quê? Bem, se você sair da sua zona de conforto, verá que existem várias ferramentas que são constantemente melhoradas para transformar nossos serviços em algo de grande potencial.

Essa arquitetura que criaremos (calma aí que eu já vou parar de falação e vamos pra prática) é apenas uma possível arquitetura para microservices. Com certeza existem outras ferramentas que também podem ser úteis de acordo com sua necessidade.

Você pode acessar o código desse exemplo no Microservices-Recipe.

Enfim, vamos começar ;)

“Ingredientes”

Docker

Docker é uma plataforma que facilita muito a criação de ambientes isolados. O legal do Docker é que podemos criar uma aplicação ou até mesmo um ambiente inteiro dentro de um container. Isso ajuda muito caso necessitemos diminuir o tempo de deploy de uma aplicação ou uma infraestrutura. Além disso, também podemos usar o Docker para criar imagens de containers a partir de arquivos chamados Dockerfiles. Vale a pena muito estudar mais sobre essa ferramenta!

Todos nossos serviços nessa demo estarão dentro de containers.

Consul + Registrator

Consul é uma ferramenta que vem se destacando pelas seguintes features: Service Discovery, detecção de falha com health checking, escalabilidade em múltiplos datacenters e por ter um armazenamento key/value flexível e dinâmico para configuração.

Iremos utilizá-lo com o Registrator que é uma ferramenta que fica verificando a existência de novos containers Docker. Quando o Registrator encontra um novo container, ele o inspeciona para determinar quais serviços são fornecidos. No nosso caso, um serviço é tudo que está escutando uma porta. A partir dessa inspeção o serviço é cadastrado no Consul. :)

Utilizaremos nessa demo o service discovery via DNS do Consul.

RabbitMQ

RabbitMQ é uma ferramenta muito interessante e cheia de funcionalidades, tanto que nem sei por onde começar a explicar. O RabbitMQ é um AMQP messaging broker, baseado em publishers/subscribers e em filas. Usar messaging compensa muito em vários cenários. É uma boa forma de desacoplar publishers e consumers; é assíncrono; você pode enfileirar para entregar/consumir posteriormente, facilita muito o load balancing e a escalabilidade. Essa é outra ferramenta que vale muito a pena estudar.

O RabbitMQ será utilizado em nossa demo para criar um service que publica um cliente para ser cadastrado e outro que consome da fila os clientes a serem cadastrados.

Let’s Cook!

Pra começar, vamos criar nosso docker-compose.yml com com Consul e o Registrator.

version: "2"
services:
consul:
image: "progrium/consul:latest"
container_name: "consul"
hostname: "consul"
ports:
- "8400:8400"
- "8500:8500"
- "8600:53"
- "<<YOUR DOCKER0>>:53:53/udp"
command: “-server -bootstrap-expect 3 -ui-dir /ui”
consul2:
image: "progrium/consul:latest"
container_name: "consul2"
hostname: "consul2"
expose:
- "8400"
- "8500"
- "8600"
command: "-server -join consul"
depends_on:
- consul
consul3:
image: "progrium/consul:latest"
container_name: "consul3"
hostname: "consul3"
expose:
- "8400"
- "8500"
- "8600"
command: "-server -join consul"
depends_on:
— consul
registrator:
command: -ip=<<YOUR DOCKER0>> consul://consul:8500
image: gliderlabs/registrator:latest
volumes:
— "/var/run/docker.sock:/tmp/docker.sock"
links:
— consul

O Consul sugere que sejam criados no mínimo três servers. Caso aconteça algum problema com o server Master, eles irão eleger entre eles outro Master e nossas aplicações que dependem do Consul não serão afetadas. Portanto, usaremos -bootstrap-expect 3 para determinar que o Consul apenas iniciará quando existirem três servers.

Outro fator importante é o IP no <<YOUR DOCKER0>>. Esse IP é do docker0 que pode ser encontrado pelo comando: ifconfig docker0 em inet addr.

Depois dessas modificações, é só dar um docker-compose up e checar a interface do Consul através do localhost:8500 e ver ele já com os serviços encontrados.

Agora que já pegamos o jeito, podemos adicionar o RabbitMQ no nosso docker-compose.yml

rabbitmq:
image: rabbitmq:management
privileged: true
environment:
- ERL_EPMD_PORT=55950
ports:
- "55950:55950"
- "5672:5672"
- "8080:15672"

E, basta apenas executar docker-compose up e veremos os serviços do RabbitMQ cadastrados no Consul :)

Todas as nossas ferramentas estão prontas para serem utilizadas!

Nosso diretório está da seguinte forma:

= microservices-recipe/
.docker-compose.yml
= services/
. docker-compose.yml
= consumer/
= producer/

Então, agora criaremos o docker-compose.yml no folder services para iniciar nossos services.

version: "2"
services:
producer:
build: ./producer
dns:
- <<YOUR DOCKER0>>
dns_search: service.consul
ports:
- "3000"
environment:
SERVICE_NAME: producer-service
SERVICE_TAGS: production
consumer:
build: ./consumer
dns:
- <<YOUR DOCKER0>>
dns_search: service.consul
ports:
- "3001"
environment:
SERVICE_NAME: consumer-service
SERVICE_TAGS: production

Essa parte do DNS e DNS_SEARCH é um ponto chave. Através desses atributos que nosso container será capaz de usar a DNS search do Consul.

Nossos serviços com o RabbitMQ funcionarão da seguinte maneira:

http://tryrabbitmq.com/

O código do nosso producer ficará da seguinte forma:

#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='rabbitmq-5672'))
channel = connection.channel()
channel.queue_declare(queue=’cadastrar-clientes’)teste = 0
while (teste < 10):
channel.basic_publish(exchange=’’,
routing_key=’cadastrar-clientes’,
body=’{“name”:”monica”, “lastname”: “ribeiro”}’)
print(“ [x] Sent!”)
teste = teste + 1
connection.close()

Ó, ATENÇÃO! Criar uma conexão com o RabbitMQ através do host ‘rabbitmq-5672’ só foi possível graças ao Consul. Caso contrário, teríamos que colocar o ip do RabbitMQ. O que acontece é que esse nome é o nome do serviço exposto no Consul e ele irá “traduzir” esse nome para o IP e a porta daquele serviço.

No nosso producer nós declaramos a queue e criamos um channel para publicar nossas mensagens. Nesse caso, deixamos o exchage vazio e o direct é o default. Utilizamos também uma routing key, pois uma mensagem é enviada para as filas onde a binding key corresponde exatamente com a routing key da mensagem.

Já nosso consumer.py ficará assim:

#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(
host=’rabbitmq-5672'))
channel = connection.channel()
def callback(ch, method, properties, body):
print(“ [x] Received %r” % body)
channel.basic_consume(callback,
queue=’cadastrar-clientes’,
no_ack=True)
print(‘ [*] Waiting for messages. To exit press CTRL+C’)
channel.start_consuming()

Ele se inscreve na fila “cadastrar-clientes” e pega as mensagens que estão por lá aguardando para serem consumidas.

Tanto o Dockerfile do producer quanto o do consumer ficarão com o seguinte código do Dockerfile:

FROM python:3-onbuild
ENTRYPOINT [“python”, “./main.py”]

Esse ENTRYPOINT sempre executa nosso script ao iniciar o container.

Você pode escalar e criar vários outros consumers e produces.

Então, é basicamente isso.

É possível criar uma infinidade de projetos interessantes com essas ferramentas e até mesmo usar uma delas para alguma necessidade especial no seu job. O segredo é ter sempre a mente aberta ao pensar em possíveis soluções… Isso inclui também trocar uma ideia com alguém, pode ser que ele já tenha passado pelo mesmo problema ou até conheça algo que poderia te ajudar :)

Espero que tenha ajudado em algo.

Até! ;)

Mônica Ribeiro

Problem Solver at QuickMobile

--

--