terça-feira, 23 de junho de 2015

Curadoria de micro serviços


Para nos beneficiarmos de uma arquitetura de micro serviços, várias práticas são importantes e necessárias, como: Entrega contínua, Gestão de processamento paralelo e distribuído, Infrastructure As Code etc. Além disso, devemos ser capazes de subir várias instâncias de cada um dos nossos micro serviços, cuidando para mantermos um bom fluxo de processamento, e baixo consumo de recursos. Isto é possível com a Escalabilidade Elástica, algo que parece surgido da ficção científica, que o Bom Programador mostra agora para você.

Todos esses conceitos formam a atividade de: Curadoria de micro serviços.





Quer continuar brigando contra os problemas?

O que você quer fazer? Quer continuar brigando e apanhando dos problemas da TI moderna, como: Ubiquidade e Universalização do acesso à informação? Ou quer se adequar aos tempos modernos, com uma arquitetura elástica e distribuída?


Arquitetura de Micro Serviços não funciona sozinha

Para obter os benefícios que eu venho relatando, na nossa série sobre Micro Serviços, outras práticas e técnicas são necessárias.

Tem gente que pensa que arquitetura de micro serviços é apenas separar os componentes em JARs, dentro de um WAR... Isso é fruto da preguiça e ignorância, já que as informações estão amplamente disponíveis. Se você já leu os meus artigos, e ainda pensa dessa forma, eu recomendo ler mais sobre o assunto.

Para que uma arquitetura de micro serviços funcione e renda os benefícios esperados, é necessário adotar algumas práticas e técnicas paralelas. Se não fizer isso, é melhor não enveredar por essa seara, afinal, você está introduzido complexidade e lentidão, já que está criando barreiras inter-processos, logo, precisa adotar outras práticas complementares, caso contrário os benefícios jamais serão alcançados.

Entre as práticas necessárias para agregar valor ao uso de arquitetura de micro serviços, estão:

  • Isolamento em Hosts: Cada micro serviço deve ser executado em seu próprio "host", aumentando a sua autonomia e diminuindo o acoplamento com outros componentes. Isto permite o deploy independente de cada micro serviço, conforme bem diz Martin Fowler;
  • Phoenix servers: Cada micro serviço deve ser distribuído na forma de um Container imutável, de modo a evitar a "derrapada de configuração". Estes Containers devem poder ser destruidos e criados sob demanda (phoenix servers);
  • Infrastructure As Code: Infraestrutura de TI é "commodity". Você pode criar código-fonte que configura todos os elementos da sua infraestrutura, podendo criar e modificar os elementos, como parte do seu ciclo de desenvolvimento, mantendo o código de infraestrutura junto com o código funcional, em seu ambiente de versionamento de artefatos;
  • Entrega Contínua: Você deve entregar seu software ("botar em produção") como parte do seu ciclo de desenvolvimento de aplicações. O fruto final da sua Integração Contínua deve ser a entrega do seu software, de preferência, já rodando em um "Phoenix Server"; 
  • Escalabilidade Elástica: É a capacidade de um sistema de se adaptar à carga de transações, em um determinado momento. Uma arquitetura elástica é capaz de aumentar a quantidade de instâncias, se o fluxo aumentar, ou diminui-la, caso o fluxo também diminua, proporcionando, assim, a adequação ao acordo de nível de serviço, racionalizando o consumo de recursos;
Então, você deve pensar que, para usar micro serviços, terá que adotar um ambiente de cloud-computing. E não está errado! Porém, com as ferramentas mais modernas, pode criar um ambiente favorável a uma arquitetura de micro serviços, mesmo que não disponha de infraestrutura de cloud-computing. 

Algumas ferramentas de software são importantes para isto: 

Dropwizard


É um framework para criação de micro serviços, baseados em REST, em linguagem Java. Como usa a biblioteca Jetty, os serviços dispensam o uso de Servidores de Aplicação, como o JBoss, por exemplo. Assim, você reduz o consumo de recursos e criar serviços "enxutos", que rodam com menor consumo de recursos computacionais.

Docker


É um software que usa o conceito de Linux Containers (LXC), para gerenciar "hosts" mais leves, como se fosse um sistema de virtualização. Com o Docker, você obtém o isolamento necessário para os seus "Phoenix Servers", com baixo consumo de recursos. É a "pós-virtualização".

Zookeeper


O Zookeeper é um sistema de informações distribuídas, que serve para controlar e coordenar aplicações distribuídas. Ele cria um "File System" virtual, composto por nós (nodes), onde podemos armazenar informações e "pendurar" endereços de hosts.

Apache Curator


O Curator é oriundo da Netflix, e é um framework para automatizar certas "receitas" que usam o Zookeper, como: Service Discovery e Shared Counter. Com ele, você simplifica o uso do Zookeeper.

Jenkins


O Jenkins é a plataforma de Entrega Contínua / Integração Contínua, mais utilizada atualmente. Sua flexibilidade e versatilidade realmente o tornam a escolha principal para várias grandes empresas no mundo. 


Uma demonstração de tudo isso


Como você sabe, aqui, no Bom Programador, não tem caô!

Então, estou publicando um exemplo prático de arquitetura de micro serviços, escalável, distribuída e paralela, baseada em Entrega Contínua, para que você possa estudar melhor isso tudo.

Para começar, veja nosso Projeto "ServKeeper", no GitHub (https://github.com/cleuton/servkeeper). Ele é gratuito sob licença Apache.

O ServKeeper é um conjunto de três projetos: 

  • servkeeper : O servidor REST curador de instâncias de micro serviços;
  • ServiceClient : A API para os micro serviços usarem;
  • signature : Um exemplo de micro serviço.

O que ele faz? 

Bem, uma imagem vale mais do que mil palavras: 


A escalabilidade elástica funciona com 3 participantes:
  • Jenkins: Roda um Job que faz um request para supervisionar o serviço;
  • Control Server: Ao receber o request para supervisionar, obtém a contagem via Apache Curator, e verifica se tem que subir ou destruir instâncias;
  • Serviço: O Serviço alvo, ao ser acessado, incrementa a contagem de acessos global;

O Job do Jenkins:

Inicia uma classe Java, que efetua um request de supervisão para o ServKeeper.

O ServKeeper (Control Server):

Ao ser inicializado, carrega as configurações do serviço, que incluem, entre outras coisas:
  • Quantidade máxima de instâncias que podem ser lançadas;
  • Quantidade mínima de instâncias que devem existir;
  • Limite mínimo de requests para evitar deletar instâncias;
  • Limite máximo de requests, antes de subir novas instâncias;

Também zera a contagem global (Um shared counter do Apache Curator).

Ao receber um request de supervisão:
  1. Obtém a contagem atual;
  2. Se a lista de eliminação futura contiver alguma coisa: {
    • Elimina os containers da lista no Docker;
  1. }
  2. Zera a lista de eliminação futura;
  3. Se contagem > limite-superior, então {
    • Se Quantidade de instâncias < Quantidade máxima de instâncias:
      • Lança nova instância (docker, zookeeper)
      • Incrementa quantidade de instâncias
  1. }
  2. Se contagem < limite-inferior, então {
    • Se quantidade de instâncias > Quantidade mínima de instâncias:
      • Deleta instância, no zookeeper;
      • Coloca na lista de eliminação futura;
      • Decrementa quantidade de instâncias;
  1. }
  2. Zera a contagem atual.

Cada Serviço-alvo:

Os serviços-alvo, instanciados no Docker e registrados no Zookeeper, têm a responsabilidade de incrementar a Contagem Global, a cada novo request que recebem. Para isto, usam a classe criada pelo projeto ServiceClient.

O Usuário:

O usuário jamais acessa diretamente um Serviço-alvo. Ele pode consultar o Apache Curator, obtendo um Service Provider, que fornecerá uma instância do Serviço. Então, através do IP e Porta, ele pode consumir o serviço. 

Por conveniência, eu inclui uma rota "getinstance", no ServKeeper, para isolar você de criar um Cliente Apache Curator.

Os Jobs no Jenkins


O Jenkins é o controlador geral de tudo. Através dele, fazemos a entrega contínua do ServKeeper e do Micro serviço "alvo", e também disparamos requests para o ServKeeper fazer a curadoria do micro serviço. As rodas REST do ServKeeper são: 

  • ../servkeeper/requests : Mostra a quantidade de requests recebida por todas as instâncias do micro serviço;
  • ../servkeper/stopall : Para e destrói todas as instâncias dos micro serviços;
  • ../servkeeper/stopserver : Para as instâncias e destrói o próprio processo do ServKeeper;
  • ../servkeeper/supervise : Executa uma supervisão sobre todas as instâncias de micro serviços. Deleta as instâncias descartadas, verifica o contador de requests, e faz a escalabilidade para cima ou para baixo, zerando o contador ao final. Esta rota deve ser invocada periodicamente;
  • ../servkeeper/getinstance : Método de conveniência para retornar o endereço de uma das instâncias. Você pode usar diretamente o Apache Curator e pedir isso ao Zookeeper;
  • ../servkeeper/setcounter?value= : Muda o valor do contador global de requests (shared counter);
  • ../servkeeper/instancescount : Retorna a quantidade de instâncias registradas no Zookeeper e ativas no Docker. Geralmente, são iguais, porém, se o servidor tiver feito "scale down", poderá haver alguma instância ainda rodando no Docker, que será deletada na próxima supervisão;
  • ../servkeeper/start : Inicia o ServKeeper, zerando o contador de requests e subindo a quantidade mínima de instâncias requerida pela configuração;

Cada projeto do ServKeeper tem seus jobs jenkins. Você pode usar o Jenkins CLI para importar os arquivos XML.

Cada Micro serviço gerenciado pelo ServKeeper, é baseado em um "app folder", onde estarão: 
  • O Jar do ServKeeper (é um shaded jar);
  • O Jar (shaded jar) ou os arquivos do micro serviço alvo;
  • O arquivo de configuração "servkeeper.yml";
  • O arquivo de configuração do micro serviço;
  • O Dockerfile para criar uma imagem Docker do micro serviço;

O Jenkins faz entrega contínua diretamente para essa pasta "app folder", e roda os jobs que iniciam o ServKeeper e as instâncias do micro serviço alvo. Eis os jobs que podem ser importados para o Jenkins: 
  • ServiceClient/serviceclient_build_install.xml : Constrói o projeto ServiceClient e instala na pasta ".m2" do Jenkins;
  • servkeeper/servkeeper_build.xml : Constrói o ServKeeper e copia o Jar e o arquivo de configuração para o "app folder";
  • servkeeper/servkeeper_run_and_start.xml : Executa o ServKeeper (a partir do "app folder") e, depois de um tempo, envia um request "start";
  • servkeeper/servkeeper_stop_and_destroy.xml : Para todas as instâncias de micro serviços no Docker e para o processo do ServKeeper;
  • servkeeper/servkeeper_supervise.xml : Envia um request "supervise" para o ServKeeper. Este job deve ser agendado;
  • signature/signaturejob.xml : Constrói e entrega o micro serviço de exemplo para a pasta "app folder";


A configuração do ServKeeper

O ServKeeper é configurado por um arquivo "YML" chamado "servkeeper.yml". Para invocar o ServKeeper, você deve informar seu "path" na linha de comando: 

java -jar <shaded jar do servkeeper> server <path para o servkeeper.yml>

O arquivo "servkeeper.yml" tem o layout: 

# Docker host. Se usar boot2docker, é o endereço da VM:
dockerHost: "https://192.168.59.103:2376"
# Docker certificate path, para logar no Docker:
dockerCertPath: "/Users/cleutonsampaio/.boot2docker/certs/boot2docker-vm"
# Zookeper endereço e porta:
zkHost: "localhost:2181"
# Path do app folder:
path:  "/Users/cleutonsampaio/Documents/projetos/dockertest"
# Docker image name (será gerada uma imagem): 
imageName: "signatureimage"
# nome do micro serviço - será usado para localizar suas instâncias:
containerBaseName: "signatureserver"
# Porta original do micro serviço, que será mapeada para uma porta do host:
sourcePortNumber: 3000
# Quantidade mínima de instâncias que serão iniciadas com o ServKeeper:
startServerInstances: 2
# Quantidade mínima de instâncias do micro serviço que devem existir:
minServerInstances: 1
# Quantidade máxima de instâncias do micro serviço que podem existir:
maxServerInstances: 5
# Quantidade máxima de requests antes de fazer um scale up:
maxRequestLimit: 10
# Quantidade mínima de requests antes de fazer um scale down:
minRequestLimit: 5
# A roda REST de teste do micro serviço:
serviceTestPah: "/signature/checkstatus"
# Vetor de endereços e portas disponíveis para as instâncias:
serverAddresses: 
  - host: "192.168.59.103"
    port: 3000
  - host: "192.168.59.103"
    port: 3001
  - host: "192.168.59.103"
    port: 3002
  - host: "192.168.59.103"
    port: 3003
  - host: "192.168.59.103"
    port: 3004
  - host: "192.168.59.103"
    port: 3005
# Configuração do servidor ServKeeper:   
server:
  applicationConnectors:
  - type: http
    port: 3000
  adminConnectors:
  - type: http
    port: 3300

Um exemplo de uso


Configurei o ServKeeper da maneira que mostrei, e, no Jenkins, rodei os jobs "ServiceClient/serviceclient_build_install.xml", "servkeeper/servkeeper_build.xml" e "signature/signaturejob.xml". 

Agora, é só subir o ServKeeper, com o Job: "servkeeper/servkeeper_run_and_start.xml": 


Você pode verificar quantas instâncias existem, rodando o request: "http://localhost:3000/servkeeper/instancescount". Eis o que deve aparecer: 

{ "status": "ok","data": {"mensagem": "Zookeeper servers: 2, Docker containers: 2"}}

Dentro do projeto "signature", na pasta "src/main/resources", tem um Cliente Node.js, que pode ser usado para fazer requests "POST" ao micro serviço alvo. Copie todos os arquivos que estão na pasta "src/main/resources" para o "app folder" (a pasta onde o Jenkins fez deploy do micro serviço). Rode o cliente com: node request. Eis a resposta típica: 

{ "status": "success","data": {"mensagem": "assinatura ok!"}}

O micro serviço "signature" confere a assinatura digital de um arquivo, que o cliente Node.js lê e assina.

Escalabilidade elástica

Faça pelo menos 11 requests usando o cliente node.js. Você pode obter o valor do contador de requests com: "http://localhost:3000/servkeeper/requests". A resposta seria:

{ "status": "ok","data": {"mensagem": "requests 11"}}

Agora, se rodarmos pelo Jenkins o job "servkeeper/servkeeper_supervise.xml", ele vai fazer um "scale up: 

Se você exibir a console no Jenkins, verá a mensagem: 

{ "status": "","data": {"mensagem": "scaledup"}}

Agora, se repetir o "instancescount", verá que tem 3 instâncias: 

{ "status": "ok","data": {"mensagem": "Zookeeper servers: 3, Docker containers: 3"}}

Agora, sem fazer nenhum request, mande rodar a supervisão novamente. A resposta será "scale down 1". Se rodar "instancescount" novamente, verá o seguinte: 

{ "status": "ok","data": {"mensagem": "Zookeeper servers: 2, Docker containers: 3"}}

Ele tirou uma instância do Zookeeper, portanto, os usuários não mais a receberão. Porém, ainda não a deletou no Docker, dando chance para que termine qualquer trabalho que esteja processando.

Ao rodar a supervisão novamente, ele deletará a instância. E por aí vai, até que o número mínimo de instâncias, previsto na configuração (1) seja alcançado.

Conclusão

O objetivo do ServKeeper é demonstrar como implementar curadoria de micro serviços, com escalabiliade elástica, de forma bem simples. Ele não é uma ferramenta pronta, embora possa ser utilizada. Existem alternativas como o Eureka, da Netflix.

Espero ter demonstrado as principais práticas e técnicas que uma arquitetura de micro serviços necessita: 
  • Isolamento em Hosts: Cada micro serviço é executado em seu próprio "host", em um container Docker;
  • Phoenix servers: Os containers Docker são gerados a partir de imagens, criadas por um Dockerfile. Ele são imutáveis e são destruídos e reconstruídos a todo momento;
  • Infrastructure As Code: O Dockerfile é o código de infraestrutura utilizado. Com ele, é possível construir as instâncias dos micro serviços;
  • Entrega Contínua: Tanto o ServKeeper, como o micro serviço de exemplo "signature", são entregues a partir do código-fonte armazenado no Git, automaticamente e continuamente;
  • Escalabilidade Elástica: O ServKeeper cuida de manter a quantidade de instâncias do micro serviço, adequadas ao fluxo de requests recebidos.