Ou Docker ou racha

Aplicação web em arquitetura de microserviço utilizando Docker 🐳



Tendo em vista a Metodologia “The Twelve-Factor App”, criada por Adam Wiggins, co-fundador da PaaS Heroku, que preconiza todo software cloud entregue como serviço deve seguir algumas premissas de desenvolvimento que impulsonam o uso da arquitetura de Microserviços, eu apresento uma solução web simples que demonstra na prática o emprego de todos os 12 fatores da Metodologia!

Por que Microserviço? 🤨

Microserviço é um conceito que garante que uma aplicação possa ser dividida em múltiplos serviços menores (microserviços) que se comunicam entre si, tradicionalmente, via APIs Rest (métodos GET/POST/PUT/DELETE do protocolo HTTP + JSON).

As principais vantagens são:

  • Cada microserviço é independente garantindo baixo acoplamento e possui responsabilidade única garantindo alta coesão melhorando a manutenção do código e reuso do software
  • Desse modo os pontos de falhas tendem a serem isolodados mais facilmente gatarantido a resilência do software. Por outro lado, aumentam-se os pontos de falhas que exigem maior esforço no monitoramento para a mitigação efeciente das falhas
  • Modelagem com base em conceitos de negócio (Domain-Driven Design) evitando uma modelagem falha e baseada puramente em aspectos técnicos
  • Autonomia e indepedência exigem automação no deploy que facilitam a implementação de técnicas de CI/CD vistas no DevOps
  • Cada microserviço pode ter sua própria linguaguem de desenvolvimento, databases e ferramentas
  • Tudo passa a ser orientado por eventos e mensagens entre as partes
  • Totalmente aplicável a escalonar horizontalmente seu software. Aqui encaixa-se o uso de orquestradores de containers, como Swarm e Kubernetes

Tudo isso confronta com as práticas tradicionais de desenvolvimento que estabelecem um único ponto (famoso monólito) de criação. Dificultando a manutenção, concentrando as falhas e diminuindo a resiliência já que a aplicação precisará parar ou reiniciar, por completo caso o módulo de upload precise de atualizaçaõ ou tenha dado problema, por exemplo.

Qual é dessa Metodologia aí? 🤔

Ela sintetiza, em 12 fatores, boas práticas de desenvolvimento de aplicação web. Ela é, altamente, recomendada para qualquer Desenvolvedor que esteja construindo aplicações que rodam como serviço e Engenheiros de Operações que implantam ou administram tais aplicações! Recomendações do próprio autor.

Estes fatores são realmente um ótimo ‘guideline’ para direcionar a melhor maneira para construir uma aplicação baseada em microserviço, pois oferecem maneiras para garantir os principais pilares dos microserviços, tais como: independência, resiliência, autonomia, descentralização e escalabilidade.

Sendo assim, os 12 fatores são:

  1. Base de Código “Codebase”

Uma base de código com rastreamento utilizando controle de revisão

  1. Dependências “Dependencies”

Declare e isole as dependências

  1. Configurações “Config”

Armazene as configurações no ambiente

  1. Serviços de Apoio “Backing Services”

Trate os serviços de apoio, como recursos ligados

  1. Construa, lance, execute “Build, Run, Release”

Separe estritamente os builds e execute em estágios

  1. Processos “Stateless Processes”

Execute a aplicação como um ou mais processos que não armazenam estado

  1. Vínculo de porta “Port Binding”:

Exporte serviços por ligação de porta

  1. Concorrência “Concurrency”

Dimensione por um modelo de processo

  1. Descartabilidade “Disposability”

Maximizar a robustez com inicialização e desligamento rápido

  1. Dev/prod semelhantes “Dev-Prod Parity”

Mantenha o desenvolvimento, teste, produção o mais semelhante possível

  1. Logs

Trate logs como fluxo de eventos

  1. Processos de Admin “Admin Processes”

Executar tarefas de administração/gerenciamento como processos pontuais

Veja na prática! 🤘

Visão geral do projeto:

Tecnicamente, é uma aplicação web PHP em arquitetura de microserviço com containers Docker que se conecta via API Restful Flask com mapeamento objeto-relacional SQLAlchemy, escrito em Python, com persistência em banco de dados PostgreSQL.

A fim de garantir que você tenha uma maior absorção do que está sendo apresentado aqui, acesse o código completo desse projeto no meu GitHub e acompanhe em detalhes como cada trecho e recurso foi adotado tendo como base a Metodologia discutida anteriormente.

Leia o Readme para proceder com a execução e veja na prática seu funcionamento!

Esse artigo foi criado por conta da aula “Microserviço na prática - aplicação web PHP simples conectada via API Restful” apresentado no meu curso de Introdução a Docker! Curtiu? Eu quero o curso

O escopo aqui não é provisionamento e orquestração da aplicação, apenas apresentar práticas de desenvolvimento em microserviços usando containers Docker. Por isso, não abordo as diferentes etapas de desenvolvimento!

1. Base de Código “Codebase”

Esteja sempre centrado no versionamento do código, em diferentes etapas do desenvolvimento integrado e continuado (development, staging, production, etc) e ciente de que existe apenas uma base de código por aplicação, mas existirão vários deploys da mesma.

Acesse toda a base de código desse projeto no meu GitHub aqui!

2. Dependências “Dependencies”

No projeto, eu uso o arquivo requirements.txt que usa o gerenciador de pacotes pip3 e o virtualenv do Python. Isso mantém declarada e isolada explicitamente, respectivamente, todas as dependências, e suas versões, do projeto.

Os services API mantêm essa característica.

1flask==1.0.2
2flask-sqlalchemy==2.3.0
3psycopg2==2.8.4

3. Configurações “Config”

Mantenha todas as configurações do ambiente declaradas “fora do código”. Isso auxilia a administração entre os diversos tipos de etapas do desenvolvimento. Evite armazenar as configurações no código como constantes, por exemplo: variáveis de conexão com banco de dados. Pois, esse 4º fator exige uma estrita separação entre configuração e código!

Tendo isso em vista, é possível observar que eu uso os arquivos de configuração de conexão com o banco PostgreSQL explícitos nos arquivos database.conf em todos os services que persistem dados. Inclusive, elas são passadas como variáveis de ambiente (ENVs) no docker-compose.yml:

POSTGRES_USER=test
POSTGRES_PASSWORD=password
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=books
1version: '3.5'
2services:
3  database-books:
4    image: postgres:13-alpine
5    env_file: db-books/database.conf
6    volumes:
7      - db_volume-books:/var/lib/postgresql/data
8#rest of code

4. Serviços de Apoio “Backing Services”

A fim de garantir boa portabilidade e fácil manutenção, trate serviços de apoio, como persistência de dados, mensageria e envio de e-mails, como um “recurso anexado” que facilmente pode ser alterado e não causar grandes impactos ou mudanças de código. Tudo isso garante baixo acoplamento ao deploy!

Sendo assim, no meu caso, atráves do docker-compose, eu consigo anexar e desanexar facilmente os recursos de containers de persistência de dados. No caso o service do PostgreSQL. Inclusive, posso alterar de versão ou indicar outro volume de dados que nenhuma mudança de código é exigida:

1version: '3.5'
2services:
3  database-books:
4    image: postgres:13-alpine
5    env_file: db-books/database.conf
6    volumes:
7      - db_volume-books:/var/lib/postgresql/data
8#rest of code

5. Construa, lance, execute “Build, Run, Release”

Esse fator reforça as etapas do desenvolvimento moderno conhecidos como Build, Release e Run stages. Inclusive, automação de deploy.

Mesmo não sendo escopo do meu projeto, tenho uma base de código (fator 1) que é pré-requisito para esse critério! Veja mais sobre aqui

6. Processos “Stateless Processes”

Esse fator está no “core” da arquitetura de microserviços. Ela preconiza que você não deve salvar estado em seus serviços, pois aplicações devem executar como um único processo sem armazenar estado. Isso garante a alta coesão e mantém a responsabilidade única!

“Sem armazenar estado” está relacionado com a persistência dos dados dos microserviços. No caso de containers Dockers isso é primordial, pois containers são voláteis e não armazenam estado (pegou a ideia?). Logo, precisa usar um serviço de apoio stateful (que armazena o seu estado), tipicamente uma base de dados.

No meu caso, simplesmente o fato de ter um container PostgreSQL não garante esse fator. Para tal, foi preciso criar um objeto Docker volume para persistir todos os dados de banco:

 9    volumes:
10       - db_volume-books:/var/lib/postgresql/data
11#rest of code

7. Vínculo de Portas “Port Binding”

De acordo com esse fator, a aplicação web deve exportar requisição HTTP como um serviço através da vinculação a uma porta e escutar as requisições que chegam na mesma. Dessa maneira, ao vincular portas, um serviço pode se tornar apoio para um outro serviços, provendo a URL do microserviço como um identificador de recurso na configuração do microserviço consumidor.

No meu caso, no microserviço web, no arquivo index.php, eu tive que informar a URL e porta do microserviço da API Restful Flask para ele “consumir” as requisições de saída exibindo os dados na web:

1<?php
2$_ENV['URL_API_BOOKS'] = "http://microservice_api-books_1:5000/";
3$_ENV['URL_API_READERS'] = "http://microservice_api-readers_1:5000/";
4?>
9    $json = file_get_contents($_ENV['URL_API_BOOKS']);

8. Concorrência “Concurrency”

Aplicações web necessitam ser escaláveis horizontalmente. Logo, elas podem ter 1 ou mais instâncias de seus microserviços sendo executadas, simultaneamente. Isso aumenta a disponbilidade e concorrência dos processos.

Como eu estou trabalhando com o docker-compose, ele, por padrão, prepara para um ambiente escalável que pode ser orquestrado no Dokcer Swarm, Kubernetes ou outros. Inclusive, ele sufixa todos os services com numerações de 1 a quantidade de réplicas criadas:

             Name                           Command               State           Ports         
------------------------------------------------------------------------------------------------
microservice_api-books_1          /usr/src/app/entrypoint.sh       Up      0.0.0.0:5000->5000/tcp
microservice_api-readers_1        /usr/src/app/entrypoint.sh       Up      0.0.0.0:5001->5000/tcp
microservice_database-books_1     docker-entrypoint.sh postgres    Up      5432/tcp              
microservice_database-readers_1   docker-entrypoint.sh postgres    Up      5432/tcp              
microservice_web_1                docker-php-entrypoint apac ...   Up      0.0.0.0:80->80/tcp

9. Descartabilidade “Disposability”

De acordo com esse fator, todas as instâncias de microserviços devem ser “descartavéis” para iniciar e parar suas execuções rapidamente e sem maiores complicações, a qualquer tempo. Mais um motivo dos quais eles devem ser stateless (fator 6). Isso facilita o escalonamento rápido.

A própria natureza de containers garanti isso!

10. Dev/prod semelhantes “Dev-Prod Parity”

Manter todas as etapas do desenvolvimento (development, staging,production, etc) mais semalhantes o possível é meta desse fator. Entrega e Desenvolvimento contínuos (CD) dependem de uma Integração contínua (CI). Um time de DevOps tende a priorizar por isso.

Mais, uma vez, o uso de containers, por conta de sua padronização de ambientes em pacotes de imagens, ajuda muito nesse sentido! Viva os containers 😂

Quer aprender a utilizar e administrar containers Docker? Tenho um curso do zero para você! Eu quero o curso agora

Alternativamente, ferramentas de provisionamento declarativo tais como Chef e Puppet combinado com ambientes virtuais leves como Vagrant também permitem desenvolvedores rodar ambientes locais que são bem próximos dos ambientes de produção.

11. Logs

Logs fazem parte do pacote do monitoramento, tão primordial no universo dos microserviços. Além disso, logs são imprescindíveis para debugar e verificar a saúde da aplicação. A partir disso, os logs não devem ser armazenados em um storage central, mas sim tratados como fluxos de eventos contínuos que devem capturados e armazenados em service separado.

No meu cenário simples tenho clareza e acesso aos logs direto da CLI do docker compose. No caso o “docker-compose logs”:

web_1   | 192.168.160.1 - - [11/Nov/2020:14:13:17 +0000] "GET / HTTP/1.1" 200 401 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
web_1   | 192.168.160.1 - - [11/Nov/2020:14:13:18 +0000] "GET /favicon.ico HTTP/1.1" 404 487 "http://localhost/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
api_1       |  * Serving Flask app "api.py"
api_1       |  * Environment: production
database_1  | 2020-11-11 16:25:37.316 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database_1  | 2020-11-11 16:25:37.316 UTC [1] LOG:  listening on IPv6 address "::", port 5432

12. Processos de Admin “Admin Processes”

Execute tarefas de administração/gerenciamento como processos únicos - tarefas como migração de banco de dados ou execução de scripts únicos no ambiente. O objetivo é separar da execução do processo do microserviço de um processo de administração como uma consulta ou update de banco, por exemplo.

No meu caso, eu uso a própria CLI do docker-compose que garante uma execução multiplexada do processo vigente. Isso é a natureza dos containers Docker:

docker-compose exec database-books psql -U test -p 5432 -d books

Referências

Apaixonado por novas tecnologias, Bacharel em Ciência da Computação. Alguns anos na operação, decidi mirar os dados.




Gostou do conteúdo?

Procurando ajuda? Estou disponível para ser contratado como consultor ou freelancer. Quer entrar em contato? Dá uma olhada na minha página de contato. Abraços!

Ah... quase esquecendo, aceito um café

Contribua

Alguma contribuição, crítica ou problema encontrado? Reporte aqui! Basta alterar o arquivo de texto, diretamente no Github e abrir um pull-request!