Microsserviço 8
Quando estamos iniciando em microsserviços, temos inicialmente a visão lógica da arquitetura e, posteriormente, passamos para a visão física. A visão lógica abstrai muito da visão física, ocultando uma série de complexidades que se tornam evidentes quando são executadas em uma infraestrutura real.
Instâncias
O primeiro ponto que devemos observar é ter várias instâncias do microsserviço, já que assim podemos lidar com mais carga de trabalho, além de aumentar a robustez do sistema, pois a falha em uma instância será mais facilmente tolerada.
O número de instâncias necessárias dependerá do contexto da aplicação e de outros aspectos, como a redundância exigida, o workload esperado, entre outros. Por exemplo: permitir distribuir o microsserviço não só entre várias máquinas, mas também em vários datacenters, a fim de mitigar a indisponibilidade de um datacenter inteiro.
Banco de dados
O banco de dados é outro componente importante. O banco utilizado por um serviço para gerenciar o estado será considerado oculto dentro do microsserviço; por isso, não será compartilhado entre microsserviços distintos. Mesmo tendo várias instâncias de um microsserviço acessando o mesmo banco, isso não viola a regra de não compartilhamento porque, nesse contexto, a manipulação dos dados continua restrita a um único microsserviço lógico.
Ainda no contexto de banco de dados, assim como os microsserviços, existe a necessidade de escala, a fim de ter capacidade para lidar com um workload maior. Para isso, uma estratégia pode ser dividir a carga de escrita da carga de leitura em nós distintos. Com isso, encaminhar o tráfego para instâncias de leitura permitirá que a instância de escrita tenha mais capacidade disponível.
Existem situações em que podemos ter a mesma infraestrutura física suportando vários bancos isolados do ponto de vista lógico. Isso permite que o mesmo hardware sirva a vários microsserviços, reduzindo custos de licença e de gerenciamento do próprio banco. Mesmo os bancos executando na mesma engine e no mesmo hardware, ainda assim mantêm o isolamento entre eles do ponto de vista lógico. Claro que, caso a infraestrutura compartilhada pelos bancos falhe, teremos um cenário de indisponibilidade total.
Essa abordagem acaba sendo muito utilizada por empresas que administram e executam o banco de dados on-premises, diferente daquelas que utilizam nuvem pública, que normalmente acabam provisionando uma infraestrutura de banco para cada serviço. O custo é maior, entretanto o controle e o isolamento também são maiores.
Ambiente e Implantação
Finalizado o desenvolvimento, chegou o momento de o microsserviço ser implantado no ambiente. Não existe um número fixo de ambientes que devam existir, já que isso pode variar muito dependendo do tipo de software e de como será entregue ao usuário final. Além disso, dependendo do ambiente de implantação, o modo de implantação também pode variar. Por exemplo: em UAT podemos ter o microsserviço rodando com 2 instâncias no mesmo datacenter; já em produção, temos o microsserviço executando 2 instâncias em 2 datacenters diferentes. Ou seja, a topologia do microsserviço muda de um ambiente para outro.
O ponto é que, dependendo do ambiente em que o microsserviço foi implantado, pode ser que nem todos os serviços de apoio estejam disponíveis. Para esses casos, podem ser utilizados mocks para testes. O cenário ideal é que os ambientes sejam cópias exatas do ambiente de produção, todavia isso acaba sendo inviável pelo alto custo que poderia gerar.
Sendo o ambiente uma cópia ou composto por mocks para testes, o ideal é ter um feedback o mais rápido possível sobre se o microsserviço funciona ou não, já que quanto antes soubermos de um problema, mais rápido será corrigi-lo e menor será o impacto da falha.
Existem diversas abordagens para implantar um microsserviço, mas alguns princípios precisam ser aplicados:
- execução isolada: instâncias de microsserviços têm seus próprios recursos computacionais, e sua execução não causa impacto em outras instâncias de outros microsserviços em execução;
- foco na automação: uso de tecnologias que facilitem a automação, já que será extremamente necessária à medida que o número de microsserviços aumentar;
- infraestrutura como código: represente a infraestrutura como código para facilitar a automação;
- implantação sem downtime: a implantação de uma nova versão do microsserviço deve ser feita sem nenhum downtime para os usuários;
- gerenciamento de estado desejado: mantenha os microsserviços no estado desejado, iniciando novas instâncias, caso necessário.
Execução isolada
Executar várias instâncias de microsserviços na mesma máquina física ou virtual prejudica o princípio de implantação independente, pois podemos ter diversos microsserviços competindo por recursos computacionais. Assim, a execução do microsserviço com o uso de containers acaba sendo a solução ideal.
Foco na automação
A migração para microsserviços gera mais complexidade para a área operacional. À medida que o número de microsserviços aumenta, a automação se torna cada vez mais importante. Além disso, ela permite que os desenvolvedores provisionem serviços de forma mais simples.
Infraestrutura como código
IaC é um conceito em que a infraestrutura é configurada como código. A ideia é definir, de forma textual, como determinado item deve ser provisionado (tamanho, configurações etc.). Além disso, com o versionamento dessa infraestrutura, temos mais transparência sobre quem alterou o quê.
Implantação sem downtime
Queremos fazer mudanças e implantá-las sem impactar o consumidor upstream. Sem isso, seria necessário coordenar a implantação com todos os consumidores upstream, já que haveria interrupção no serviço.
Com a implantação sem downtime, podemos fazer entregas mais frequentes e os consumidores upstream nem perceberão que uma nova versão foi lançada. Vale a pena pensar sempre em uma arquitetura com esse princípio em mente desde a concepção.
Gerenciamento de estado desejado
A ideia é ter a capacidade de especificar os requisitos de uma infraestrutura para sua aplicação e fazer com que esses requisitos sejam mantidos sem intervenção manual. Caso o estado desejado deixe de existir, a plataforma subjacente deve atuar para trazer a aplicação de volta ao estado esperado.
Por exemplo: queremos que sempre existam 3 instâncias da aplicação em execução. Caso aconteça algo que altere esse estado, cabe à plataforma reestabelecê-lo. O Kubernetes é uma ferramenta desse tipo. Também existe o auto scaling group das nuvens públicas.
Opções de implantação
Queremos executar os microsserviços de forma isolada, sem downtime, tendo infraestrutura como código e gerenciamento de estado contínuo. Para atender a esses pontos, a opção de implantação será determinante. Podemos implantar em:
- máquina física: microsserviço executa diretamente em uma máquina física, sem virtualização;
- máquina virtual: microsserviço executa em uma VM;
- container: microsserviço executa em um container separado, podendo estar em uma máquina física ou virtual;
- container de aplicações: microsserviço executa em um container de aplicações que gerencia outras instâncias;
- PaaS: microsserviço executa em uma plataforma como serviço com um nível mais alto de abstração;
- FaaS: microsserviço executa como função e é gerenciado por uma plataforma subjacente.
Máquinas físicas
Uma opção cada vez mais rara, sem nenhuma camada de virtualização ou conteinerização entre o microsserviço e o hardware. Essa abordagem gera subutilização do equipamento, pois recursos restantes ou sobressalentes não são aproveitados.
Para solucionar esse problema, surgiu a virtualização, permitindo a coexistência de várias máquinas virtuais na mesma máquina física. Com isso, temos maior e melhor utilização da infraestrutura.
Máquinas virtuais
Podemos ter um uso mais racional do hardware subjacente dividindo a máquina física já existente em diversas máquinas virtuais menores. Além disso, podemos atribuir partes do hardware físico — CPU, memória, disco — para cada uma das VMs, tendo assim ambientes de execução isolados.
Cada VM contém um S.O. completo e um grau de isolamento entre as instâncias.
Um ponto a ser observado é que, à medida que adicionamos mais VMs, haverá cada vez menos recursos a serem distribuídos entre elas. Isso acontece porque os recursos do hardware físico também são utilizados pelo hipervisor, cuja função é mapear os recursos do host virtual para o host físico e atuar como uma camada de controle. Para isso, o hipervisor precisa reservar recursos. Quanto mais hosts o hipervisor gerencia, mais recursos serão necessários.
Pode ser uma opção de uso caso níveis mais rigorosos de isolamento sejam necessários ou não seja possível conteinerizar o microsserviço.
Container
Para muitos, um padrão consolidado no mercado e a escolha preferida para execução de arquiteturas de microsserviços. Diferente das VMs, com o uso de containers não temos o hipervisor. O container faz uso do kernel da máquina subjacente, e é nesse kernel que está a árvore de processos de cada container.
Tendo em vista a leveza dos containers, podemos ter muito mais deles executando no mesmo hardware, em comparação com as VMs, já que agora não temos recursos sendo consumidos pelo hipervisor.
Os containers podem ser vistos como uma ótima maneira de executar softwares confiáveis.
O Docker foi o grande divisor de águas, tornando o uso de containers muito mais viável. Atividades como provisionamento, rede e armazenamento de imagens em registry foram facilitadas pelo Docker. Além disso, independentemente da linguagem usada para desenvolver um serviço, conseguimos tratar todos da mesma forma, já que, no final, tudo será uma imagem de container.
Entretanto, o gerenciamento de estado precisa ser tratado por outra ferramenta, por exemplo, o Kubernetes, que é uma forma de gerenciar containers distribuídos em várias máquinas.
Container de aplicações
A ideia é reunir aplicações ou serviços distintos em um único container de aplicações. Exemplos: .NET com IIS, Java com Wildfly (JBoss) ou Tomcat.
Usando essa abordagem, temos tempos de inicialização e ciclos de feedback mais lentos. O gerenciamento apropriado do ciclo de vida das aplicações pode ser mais problemático e complexo, além de dificultar a análise e o gerenciamento do uso de recursos e threads, já que muitas aplicações compartilham o mesmo processo.
Pela falta de isolamento, esse modelo vem sendo cada vez menos adotado.
PaaS
Com um alto nível de abstração, apenas geramos um artefato e o disponibilizamos. A plataforma provisiona e executa esse artefato automaticamente. Não temos a preocupação com o gerenciamento da plataforma subjacente. Um ponto de atenção é que, se o microsserviço for muito diferente do padrão esperado pela plataforma, pode acabar não sendo uma opção viável.
FaaS
Termo popularizado com o serverless. Detalhes de gerenciamento e configuração ficam ocultos, o foco maior está no negócio e nas features, não na infraestrutura, diminuindo o overhead operacional.
Com a característica de ser stateless, o código implantado é tratado como uma "função", sendo executado somente quando houver um evento. A plataforma subjacente cuida da inicialização e encerramento dessas funções, além de lidar com execuções concorrentes, permitindo várias cópias em paralelo, quando necessário.
Entretanto, essa abordagem não permite controle sobre os recursos atribuídos a cada chamada da função. Algumas plataformas permitem controlar apenas a memória concedida a cada função, mas o controle direto de CPU e I/O não é permitido. Dessa forma, pode ser necessário conceder mais memória a uma função, mesmo que não precise, somente para que tenha a CPU necessária.
Outro ponto é o tempo de execução da função, já que, dependendo da plataforma, pode haver diferença.
O uso de FaaS pode não fazer sentido quando existe a necessidade de realizar ajustes finos relacionados aos recursos disponíveis às funções.
Um desafio que existe é o chamado cold start, que é o tempo que a função leva para inicializar. Runtimes como a JVM e .NET podem sofrer significativamente com esse problema, enquanto linguagens/plataformas com inicialização rápida (Go, Python, Node.js) conseguem atenuá-lo.
No mapeamento de funções como microsserviços, podemos ter as seguintes abordagens:
- função por microsserviço: uma instância de microsserviço se torna uma função, com todos os serviços disponíveis. O conceito de unidade de implantação é mantido;
- função por agregado: agregados são um conjunto de objetos que são gerenciados como uma única entidade. Assim, teríamos uma função para cada agregado, já que toda a lógica estaria autocontida na função. Teremos várias unidades de implantação independentes, mas com o conceito lógico de que todas compõem um único microsserviço. Não seria um problema se todas as funções compartilhassem o mesmo banco de dados, se considerarmos que a mesma equipe mantém todas as funções e, conceitualmente, continuamos tendo um único serviço, mas composto de várias funções.
Se o uso de FaaS for uma opção, vale a pena explorá-la considerando a facilidade e o menor gerenciamento de infraestrutura para execução do workload. Agora, caso a conteinerização seja o caminho, em grande escala, o Kubernetes acaba sendo a direção natural a seguir.
Kubernetes
Tratando de implantação, o Kubernetes pode ser definido como uma plataforma de orquestração de containers, lidando com o modo e o lugar em que as cargas de trabalho em containers são executadas. Apenas dizemos o que queremos e como queremos. O Kubernetes descobre como escalonar o workload e lidar com o estado desejado definido previamente.
No cluster Kubernetes, temos:
- node: máquinas que compõem o cluster;
- control plane: softwares que gerenciam os nós;
- pod: composto de 1 ou N containers implantados juntos. O padrão é ser um container por pod;
- service: endpoint estável para roteamento, fazendo o mapeamento dos pods em execução para uma interface de rede disponível no cluster. Um pod é efêmero, mas o serviço se mantém em execução. Pods são mapeados para serviços;
- deployment: modo de aplicar mudanças nos pods. Podemos fazer rolling upgrades, rollbacks, aumentar nós, etc.

O conceito de plataforma do Kubernetes está mais relacionado à possibilidade de customização: montar a própria plataforma com a instalação de softwares auxiliares usando, por exemplo, o gerenciador de pacotes Helm. Podemos escolher a ferramenta que quisermos para tarefas específicas. No entanto, também pode levar à tirania da escolha — ficando sobrecarregados com tantas opções. Plataformas como o Openshift eliminam a necessidade de fazermos escolhas, já que algumas decisões já foram tomadas previamente.
Assim, apesar de o Kubernetes fornecer uma camada de abstração portável para execução de containers, na prática não é tão simples quanto esperar que uma aplicação que funciona em um cluster vá funcionar em outro lugar. Aplicações criadas com base no Kubernetes são portáveis entre clusters Kubernetes na teoria, mas nem sempre isso ocorre na prática.
A alta customização do Kubernetes dificulta a portabilidade das aplicações entre clusters.
Usar o Kubernetes exige alguns pontos de atenção. Gerenciar um cluster Kubernetes não é uma atividade simples, e a experiência dos desenvolvedores dependerá da eficiência da equipe que administra o cluster. Pode valer a pena o uso de um cluster gerenciado. Entretanto, tendo poucos desenvolvedores e alguns microsserviços, é provável que o Kubernetes seja um exagero, mesmo com o uso de uma plataforma gerenciada.
Entrega progressiva
Definido o ambiente e a implantação, é hora de entregar a solução. Ao longo dos anos, passamos a implantar softwares para os usuários de modo mais inteligente e com muito menos riscos, resultado de novas técnicas que surgiram.
Há uma série de atividades que executamos antes de disponibilizar o software aos usuários finais, as quais nos ajudam a identificar problemas antes de impactá-los. Os testes em pré-produção desempenham um papel crucial nesse cenário.
Técnicas como blue-green deployment, feature flags, canary releases e parallel runs são algumas abordagens que podemos utilizar para fazer o que chamamos de entrega progressiva. Basicamente, a ideia é separar o conceito de implantação do conceito de entrega.
A implantação é o deploy do software no ambiente. Já o lançamento é fazer com que o sistema ou parte dele se torne disponível aos usuários. Os rollouts, que agora podem ser divididos em fases, podem ser controlados pelo dono do produto.
No blue-green deployment, temos a versão atual ativa (blue) e, em seguida, implantamos a nova versão ao lado da versão antiga (green). Verificamos se a nova versão está funcionando conforme esperado e, caso esteja, redirecionamos os clientes para ela. Caso algum problema seja identificado antes da virada, nenhum cliente será impactado.
No feature flags, as funcionalidades são ocultadas a partir de flags (toggle) que podem ser utilizadas para ativar ou desativar. Essa abordagem permite implantar funcionalidades mesmo que não estejam concluídas, já que estarão ocultas aos usuários. Logo, podemos ativar uma feature em um horário específico ou desativá-la caso algum problema esteja ocorrendo.
Com canary releases, a ideia é que um subconjunto de usuários tenha acesso à funcionalidade. Caso haja algum problema com o rollout, apenas parte dos usuários será impactada. Caso a funcionalidade funcione como esperado para o grupo canário, o rollout poderá ser expandido para todos os usuários.
Com parallel runs, executamos duas implementações diferentes da mesma funcionalidade lado a lado, enviando a mesma requisição para ambas. Em microsserviços, seriam 2 serviços distintos, e comparamos os resultados. Todavia, apenas uma das requisições será a fonte da verdade.
Conclusão
Logo, a adoção de microsserviços exige mais do que fragmentar aplicações: demanda processos sólidos (CI/CD), isolamento e automação, IaC, além de gerenciamento de estado desejado. A escolha da forma de implantação (máquina, VM, containers, PaaS ou FaaS) e do modelo de repositório deve refletir o contexto — escala, equipe, custos e necessidade de portabilidade.
Kubernetes oferece flexibilidade e resiliência, mas acrescenta complexidade operacional; em times menores, soluções gerenciadas ou abordagens mais simples podem ser mais adequadas. Por fim, técnicas de entrega progressiva (blue-green, canary releases, feature flags e parallel runs) reduzem risco. O caminho é uma evolução contínua: medir, automatizar, observar e ajustar.
