No artigo “Microsserviços: Projetando”, apresentamos aspectos referentes ao desenvolvimento de aplicações baseadas em microsserviços, com foco nos microsserviços e suas interações. Neste post, vamos falar sobre uma das formas de comunicação entre microsserviços: a Service Mesh.
Naquele artigo lembramos que uma das principais características de aplicações baseadas em microsserviços é o conceito de “Smart Endpoints” e “Dump Pipes”, que coloca a inteligência dentro dos microsserviços e deixa a comunicação apenas como o transporte da informação. Citamos que a comunicação não é uma camada independente nessa arquitetura e que os microsserviços são responsáveis por construir e enviar as mensagens, além de coreografar (às vezes orquestrar) essa colaboração/comunicação.
Analisamos o uso da comunicação síncrona e assíncrona e citamos que normalmente a comunicação síncrona é a primeira abordagem a ser utilizada quando se pensa em comunicação. Entretanto, o principal impacto desse modelo é gerar forte acoplamento entre os microsserviços e bloquear a execução do código enquanto espera a resposta necessária. Já a comunicação assíncrona é mais flexível porque não requer necessariamente o resultado de uma ação para iniciar outra, o que gera menos acoplamento entre os microsserviços.
Defendemos que uma boa prática é compor as duas formas de comunicação dentro da aplicação e que, para decidir quando usar cada uma delas, é necessário conhecer muito bem o negócio para projetar uma aplicação que tenha o melhor desempenho possível nesse aspecto da comunicação.
Nesse post partimos do princípio de que em aplicações baseadas em microsserviços a colaboração entre eles é intensa e faz-se necessária a utilização de uma infraestrutura para coordenar os modelos de comunicação. Esse é, então, o verdadeiro desafio da implementação baseada em microsserviços: a comunicação entre eles. A comunicação nessas aplicações é muito complexa e seu gerenciamento é vital para garantir o desempenho e a confiabilidade ponto-a-ponto.
No início, os desenvolvedores usaram message brokers como o canal de comunicação simples, mantendo o microsserviço em si como um terminal inteligente. Esse conceito funcionou bem nas implementações iniciais dos microsserviços, mas com o aumento de escala e de complexidade dessas aplicações, essa solução não foi mais suficiente. O conceito de Service Mesh surgiu justamente para vencer essa limitação!
O que é Service Mesh?
Podemos considerar a Service Mesh como uma infraestrutura distribuída de software que lida com a comunicação entre os microsserviços, entregando mensagens de forma confiável e segura e acrescentando a essa comunicação visibilidade e recursos controle e gerenciamento.
A Service Mesh surgiu com um conjunto de recursos que são necessários para a implementação pragmática de microsserviços. Dessa forma, a Service Mesh implementa a maior parte das funções de rede, permitindo ao código do microsserviço se concentrar na lógica de negócio, e isso libera os desenvolvedores das questões sobre políticas e rede, e provê os operadores com controles para monitoramento, aplicação de políticas, networking e questões sobre resiliência.
Do ponto de vista do modelo de rede, a Service Mesh se situa em uma camada de abstração acima do TCP/IP e assume que as camadas 3 e 4 adjacentes estão presentes e são capazes de entregar bytes de um ponto a outro da rede. Mas ela também assume que a rede e o ambiente onde ela se situa não são confiáveis e, portanto, a Service Mesh deve ser capaz de lidar com falhas. Ademais, um dos objetivos da Service Mesh é trazer a comunicação para um patamar visível, onde essa comunicação pode ser gerenciada, monitorada e controlada.
Arquitetura e Funcionalidades da Service Mesh
Como a Service Mesh é uma infraestrutura de comunicação de rede que implementa a maior parte das funções de rede, quando você faz uma comunicação serviço-a-serviço, não é necessário implementar patterns de comunicação confiável, como Circuit breakers e timeouts no código do seu serviço. Da mesma forma, a Service Mesh fornece outras funcionalidades, como descoberta de serviço, observabilidade, etc. Entretanto, é importante ressaltar que a Service Mesh é simplesmente uma infraestrutura de comunicação entre serviços e, portanto, não possui nenhuma noção do negócio da sua solução.
A Service Mesh é construída utilizando o conceito de proxy reverso, que é colocado ao lado de cada serviço como um Sidecar e é independente da funcionalidade de negócio dos serviços.
Uma boa implementação de Service Mesh deve oferecer no mínimo as seguintes funcionalidades:
- Descoberta de serviços:
- Balanceamento de carga;
- Implementação de Circuit Breaker, de mecanismos de repetição de tentativas em caso de falhas (retries) e temporização de chamadas (timeout);
- Roteamento
- Autenticação e Autorização
- Observabilidade
Essas e outras funcionalidades são disponibilizadas pela Service Mesh sendo executadas no sidecar proxy e podem ser configuradas através de um painel de controle comum. E aqui nos deparamos com outros dois conceitos: o Plano de Dados e o Plano de Controle.
O Plano de Dados é responsável por garantir a entrega das solicitações entre os microsserviços, de forma confiável, segura e em tempo adequado. Para tanto, ele oferece as funcionalidades de descoberta de serviço, verificação de integridade, roteamento, balanceamento de carga, segurança e monitoramento, dentro do sidecar proxy.
O Plano de Controle, por sua vez, fica responsável pela configuração da rede de sidecar proxys e pela efetiva configuração das funcionalidades oferecidas pelo Plano de Dados. O Plano de Controle fornece as políticas e as configurações para todos os Planos de Dados e os transforma em uma rede distribuída (veja figura abaixo).
Plano de Dados e Plano de Controle da Service Mesh
Na Figura, podemos ver que cada microsserviço tem o seu próprio sidecar e ambos são implantados em um mesmo host ou contêiner. Devido ao fato de o framework funcionar em contêineres, ele provê suporte a várias linguagens de programação. Com a utilização da Service Mesh, o microsserviço não precisa mais se preocupar com os assuntos referentes ao ambiente, mas apenas com as suas regras de negócio. Além disso, a configuração da comunicação entre os serviços fica centralizada (no Plano de Controle) e disponível uniformemente para os microsserviços.
Quando e por que usar a Service Mesh?
Vamos analisar um microsserviço que se comunica com outros serviços. Sabemos que seu código compreende:
- Lógica do negócio que implementa as funcionalidades de negócios, cálculos e lógica de composição/integração de serviços;
- Funções de rede responsáveis pelos mecanismos de comunicação entre serviços (chamada de serviço básico através de um determinado protocolo, utilização dos patterns de resiliência e estabilidade, descoberta de serviços, etc.). Essas funções de rede são construídas sobre a pilha de rede subjacente no nível do sistema operacional.
Se tivermos que implementar todas as funcionalidades de rede para cada microsserviço, teremos um enorme trabalho e um desperdício de tempo, que seria melhor empregado se nos concentrássemos na lógica do negócio. Imagine, então, se tivermos que fazer isso para as várias tecnologias normalmente usadas na construção dos microsserviços (como, por exemplo, várias linguagens de programação) da aplicação. Teríamos, então, que replicar os mesmos esforços em diferentes linguagens.
Assim, no intuito de otimizar o desenvolvimento dos microsserviços, podemos transferir todas essas tarefas referentes às funções de rede para uma camada diferente, já que a maioria dos requisitos de comunicação entre serviços é bastante genérica em todas as implementações de microsserviços. Dessa forma, além do ganho com a fatoração, poderemos também manter a independência do código do serviço e, é nesse ponto que encontramos novamente a Service Mesh.
A utilização da Service Mesh implica em muitos benefícios para as aplicações baseadas em microsserviços. Por exemplo, como as funcionalidades de observabilidade, confiabilidade e segurança são fornecidas pela Service Mesh no nível da plataforma, elas são disponibilizadas uniformemente para todos os serviços.
Os desenvolvedores não precisam se preocupar em incluir essas funcionalidades no código dos microsserviços e os operadores da plataforma podem customizar estas funcionalidades de acordo com a necessidade da aplicação.
Todos esses benefícios são bastante atrativos, mas a utilização da Service Mesh tem um custo, pois requer algum trabalho inicial: é preciso entender a nova plataforma, uma nova forma de desenvolvimento dos microsserviços, etc. Portanto, antes de tomar a decisão de utilizar essa solução, pelo menos dois aspectos devem ser considerados: 1) A topologia de microsserviços da aplicação e 2) A integração de um framework de Service Mesh ao ciclo de desenvolvimento de software.
A Topologia da Aplicação
Caso a aplicação tenha sido desenvolvida inicialmente como um monólito e sua evolução inclui microsserviços, enquanto a comunicação for principalmente entre o monólito e os microsserviços, a utilização da Service Mesh não trará grandes benefícios, pois não há comunicação entre os microsserviços. Se um microsserviço falha, é relativamente simples descobri-lo, pois o próprio monólito gerará um indicador do problema. Em contrapartida, se temos uma complexidade maior de microsserviços que se comunicam em vários níveis de profundidade (por exemplo, o microsserviço X chama o microsserviço Y e este chama o microsserviço Z), encontrar o microsserviço que está falhando pode ser uma tarefa mais complicada. Neste caso, os benefícios do uso da Service Mesh já começam a compensar o trabalho inicial de inseri-la no ciclo de desenvolvimento da aplicação.
Integração ao Ciclo de Desenvolvimento de Software
Inserir uma camada de Service Mesh na arquitetura de microsserviços naturalmente impactará a cultura e o ciclo de desenvolvimento de software da organização, pois os benefícios advindos dessa adoção exigirão uma integração maior entre a equipe de desenvolvimento e a equipe de operações, convergindo para a adoção também de práticas de DevSecOps.
Um exemplo dessa necessidade de integração entre as equipes de desenvolvimento e operações diz respeito à facilidade de realização de testes canário, onde uma nova versão de um microsserviço é disponibilizada apenas para um subconjunto pequeno de usuários, coletando-se dados que sustentem a futura liberação da nova versão para todos. A realização desse tipo de testes exige alterar as regras de roteamento, descoberta de serviços e balanceamento de carga configuradas na camada de Service Mesh, sincronizadas com o deploy da nova versão, o que exige coordenação das ações dos dois times. De maneira análoga, a decisão posterior, baseada nas evidências coletadas, de finalmente liberar a nova versão em substituição à antiga, também deverá ser tomada em conjunto entre as duas equipes, e acarretará em nova alteração das regras para configurar o novo cenário.
Outro aspecto diz respeito às configurações apropriadas da camada de Service Mesh. Por exemplo, vamos considerar os aspectos de mitigação de falhas e observabilidade que são tratados na Service Mesh. As estratégias de mitigação de falhas (qual a melhor configuração para o circuit breaker ou para o número de retentativas?) e balanceamento de carga serão mais efetivas se forem configuradas caso a caso para cada microsserviço. Nesse caso, a colaboração entre a equipe de desenvolvimento e a equipe de operações será fundamental para se chegar à melhor solução. De maneira semelhante, a implementação de estratégias de observabilidade como, monitoramento, rastreamento distribuído, alerta/visualização e gravação de logs, devem envolver os autores dos microsserviços na criação de alertas e visualizações mais adequadas.
Assim, podemos concluir que a implantação propriamente dita do framework pode ser realizada diretamente, mas a integração entre os fluxos de trabalho das equipes de desenvolvimento e operações precisa ser considerada.
Outro ponto importante a ser ressaltado no contexto dos microsserviços é que devemos usar a Service Mesh apenas como infraestrutura para comunicação entre os microsserviços, sem adicionar a ela aspectos da lógica de negócios.
Considerando a natural evolução das aplicações baseadas em microsserviços, onde a complexidade e nível de criticidade, além da quantidade de serviços e instâncias, tendem a ser cada vez maiores, a necessidade de uma camada de comunicação serviço-a-serviço, desacoplada do código da aplicação e capaz de lidar com a natureza altamente dinâmica do ambiente em que está inserida, é fundamental. Adicionando-se ainda o fato de que os microsserviços dessas aplicações são comumente desenvolvidos em diferentes tecnologias e são implantados em contêineres (como Docker), com uma camada de orquestração (como Kubernets), o uso da Service Mesh passa a ser praticamente indispensável.
Framework de Service Mesh
Alguns dos principais frameworks de Service Mesh disponíveis atualmente são o Istio, o Linkerd e o Conduit. A identificação da melhor opção para cada caso foge do escopo deste artigo, mas deve levar em consideração os quesitos de desempenho, facilidade de instalação e configuração, outros componentes de infraestrutura exigidos por cada um deles e plataformas suportadas.
Considerações
Apresentamos o conceito e as características de uma camada de Service Mesh e citamos algumas plataformas que a implementam. Obviamente, não existe uma solução perfeita para todos, é necessário ponderar algumas questões antes de adotar uma solução de Service Mesh.
Sabemos que o uso de uma nova ferramenta implica em um custo considerável tanto para aprender a implantá-la, como utiliza-la e mantê-la. Desta forma, é importante identificar se a ferramenta é mesmo necessária. Nesse post falamos de dois aspectos que devem ser considerados antes de optar pela Service Mesh. Uma vez tomada essa decisão, ainda será necessário considerar os frameworks disponíveis e as funcionalidades que eles proporcionam para que seja feita a escolha correta.