Micro Serviços: Camada de Service Mesh

Marcelo M. Gonçalves
9 min readMay 18, 2023

--

Introdução e Histórico

Na era dos micro serviços, cloud-native e deployment em containers, os sistemas distribuídos compõem a base para esta realidade e passam longe de serem novidade, demonstrando idade aparente e conceitualmente datando de décadas atrás (Actor-based Model — 1972, BEAM VM — 1986, Akka Framework — 2009). Junto à popularidade dos micro serviços e complexidade dos sistemas distribuídos nasce uma nova camada (2016), responsável pela infraestrutura de comunicação multidirecional service-to-service: service mesh layer.

O termo service mesh ganhou evidência a partir dos movimentos de transição das aplicações monolíticas para arquiteturas mais granulares (mini, micro, nano services), iniciando sua trajetória (September — 2016 / William Morgan) a partir da implementação de referência no Linkerd, cunhando novos conceitos e tendências as quais passaram a ser objeto de exploração e estudos. Desta forma, quando nos referimos a service mesh layer, o caminho foi invertido: da implementação ao conceito.

As raízes para modelos e funcionalidades modernas de service mesh (data e control plane) foram inicialmente trilhadas a partir de iniciativas como Netflix Prana, Airbnb SmartStack e Lyft Envoy.

Problemas antes centralizados em camadas de infraestrutura usando SOA/ESB passaram a ser implementados a nível de micro serviços. O avanço em direção aos sistemas distribuídos remove a camada de abstração que protegia nossos serviços/componentes das complexidades relacionadas a redes e comunicação. Desse ponto em diante, estamos fundamentalmente resolvendo os mesmos problemas encontrados no SOA com a diferença de estarmos em um ambiente distribuído e adicionando cada vez mais especificidades de infraestrutura à camada de aplicação dos serviços.

Quando não utilizamos tecnologias de service mesh acabamos consequentemente inchando os micro serviços em sua camada de aplicação, gerando containers cada vez mais pesados e rígidos para escalabilidade horizontal.

Service Mesh Layer

Em termos gerais, a service mesh layer resume-se em estabelecer uma infraestrutura de comunicação agnóstica aos micro serviços em funcionamento em alguma plataforma rodando na nuvem. Desta forma, a comunicação entre micro serviços não ocorre diretamente, ficando sob responsabilidade das abstrações impostas pelo mesh. Em alguns casos a utilização de padrões como sidecar proxy pode ser empregado, controlando o input/output a partir da comunicação entre serviços.

A camada de service mesh foi proposta para centralizar e proteger os componentes de complexidades envolvendo a infraestrutura de execução necessária aos serviços. Como vantagem podemos esquecer detalhes de infraestrutura, focando nas regras de negócio de forma a isolar características de um serviço relacionadas a lógica de negócio, deixando para o mesh todo o tracking e health status dos serviços registrados garantindo sua consistência de execução.

O mesh apresenta vantagem de redução em potencial da complexidade adotada na medida em que são utilizadas tecnologias heterogêneas para composição dos micro serviços.

Ao adotarmos os conceitos de service mesh são oferecidos suporte implícito a funcionalidades como service discovery, circuit breaking, resiliency, bulkhead, dynamic routing, service compositions as quais necessitariam de implementações distintas a depender da linguagem utilizada (JVM-based Languages, Node, Python, Go) caso residisse na camada de serviço.

Diferente do SOA/ESB, para que haja capacidade de comunicação mínima entre os micro serviços, mesmo com a presença de uma camada de service mesh, os serviços precisam acoplar uma stack de redes básica em sua camada de aplicação. Ao estabelecermos uma estratégia de service mesh a intenção seria reduzir ao máximo a inserção de bibliotecas extras referentes a infraestrutura + comunicação nas instâncias dos serviços, porém não em sua totalidade.

Infraestrutura e Comunicação

Na prática, plataformas de service mesh compartilham conceitos centrais como data plane e control plane. Sendo o data plane responsável por traduzir e encaminhar pacotes de rede condicionados aos fluxos de origem e destino durante a comunicação entre serviços, responsável por tarefas como: health checking, routing, load balancing, authentication and authorization e observability. Em efeito, sidecar proxy e data plane referem-se a mesma coisa.

A comunicação física em rede, a nível de roteadores e switches, possibilita a entrega de pacotes de ponto A ao ponto B, valendo-se de esforços de software e hardware para garantir seu funcionamento. Análogo ao tráfego de redes, atividades como controle de timeout, circuit breaking, conhecimento sobre rotas, balanceamento condicional de tráfego, etc são executadas em uma camada mais abstrata e atribuídas ao control plane. No contexto de service mesh, control plane trata-se do coração responsável pelo gerenciamento de estado e comunicação dos proxies em ambiente distribuído.

Conceitos como human control plane são comuns e referem-se a execução estática de atividades manuais através de scripts e ferramentas utilizadas por operadores humanos.

Quanto mais avançadas as funcionalidades presentes no control plane maior a abstração aos operadores, resultando em menos trabalhos manuais e redução de erros. Essencialmente, o control plane (management plane) provê políticas para configuração e coordenação necessárias aos data planes/sidecar proxies executando em uma camada de mesh. O papel do control plane torna-se essencial na medida em que o número de sidecar proxies aumenta, facilitando seu gerenciamento.

Todo o roteamento da comunicação/requisição (HTTP, REST, gRPC, etc) ocorre através dos proxies locais distribuídos ao longo do cluster e posicionados ao lado dos serviços/instâncias a nível de infraestrutura. Esta abordagem permite um desacoplamento entre a lógica de aplicação e redes, criando abstrações mínimas e por vezes havendo necessidade dos componentes estarem cientes somente da presença dos proxies.

Mesh: Projetos e Implementações

Linkerd e Istio referem-se a implementações mais populares de service mesh, ambas possuindo arquiteturas semelhantes com funcionamentos distintos, como referência em demonstrarmos o desacoplamento entre data e control plane. Projetos como Nelson e SmartStack, ambos usando Envoy como proxy tendo construindo sua estrutura de control plane, respectivamente, em uma stack HashiCorp (ex: Nomad) ou utilizando HAProxy ou NGINX.

Linkerd 1.0 (2016) serviu como implementação de referência aos conceitos iniciais de service mesh, adotando Finagle como proxy e influenciando campos de inovação, contribuindo com o aumento do interesse em mesh-layer, resultando na evolução de iniciativas tanto de data planes quanto control planes. Em se tratando do Envoy (Lyft2015), embora Linkerd e Envoy sejam comumente mencionados em conjunto, em alguns casos inclusive trabalhando lado a lado, Linkerd 2.0 (Setembro — 2018) não foi construído em cima do Envoy e possui uma implementação de sidecar própria: Micro-proxy — Linkerd2-proxy, baseado em stack Rust.

Em resumo, Linkerd não usa Envoy pois isso não permitiria que construíssemos o mais leve e simples service mesh para Kubernetes do mundo — William Morgan

Istio (2017) entregou uma implementação avançada atuando a nível de control plane, adotando o Envoy como data plane / sidecar proxy padrão. Adicionalmente, outras implementações com possibilidade de integração com Istio foram possíveis visando substituir o Envoy, ex: NGINX. A partir disso, era possível diferentes data planes executarem junto a um único control plane, demonstrando o seu desacoplamento.

Dada sua complexidade de implementação, muitos projetos adotam o Envoy (como data plane) por não estarem dispostos, tecnicamente e financeiramente, a implementarem seu próprio micro-proxy sidecar. Ainda assim, trata-se de uma área bastante fértil com espaço para novos projetos e inovação tanto a nível de data quanto control plane. Os aprendizados adquiridos a partir de produtos como NGINX e HAProxy possibilitaram a concepção do Envoy (cloud-native), tornando-o uma opção viável e simples na adoção de um sidecar proxy.

Data planes: Linkerd, NGINX, HAProxy, Envoy, Traefik, Control planes: Istio, Nelson, SmartStack.

Kubernetes (k8s) vs Service Mesh

Aplicações Cloud-native são estruturalmente complexas, arquiteturalmente distribuídas e nativamente conteinerizadas (docker, podman, etc). No contexto de aplicações em nuvem, somado às arquiteturas de micro serviços, a orquestração e gerenciamento do ciclo de vida dos serviços/containers são endereçadas de maneira padrão a plataformas como Kubernetes.

Evidentemente, containers e micro serviços não são mutuamente excludentes: Não precisamos de containers para usarmos micro serviços nem de micro serviços para usarmos containers. Fundamentalmente, micro serviços são o que são, independente se favorecidos com execução em containers.

Kubernetes pode ser descrito como um gerenciador declarativo do ciclo de vida de containers, escalando, destruindo e recriando a depender das métricas e saúde de execução dos pods/aplicação. A possibilidade de escalabilidade horizontal para ganho de performance bem como a gestão do ciclo de vida dos containers são encapsulados em pods/k8s, oferecendo uma camada de abstração para execução de serviços.

Ao trabalhar em conjunto com Kubernetes, service mesh entra em cena adicionando uma camada extra ao Kubernetes, complementando-o (ex: controle/encaminhamento de tráfego e balanceamento de carga). Na prática, para que seja possível sua execução conjunta, sidecar proxies são injetados como containers adicionais a nível de pods no k8s, transferindo a comunicação (origem/destino) ao data/control plane da solução de mesh adotada.

Basicamente, kube-proxy e soluções de service mesh atuam no processamento de tráfego, sofisticado balanceamento de carga e visibilidade dos pacotes de rede a nível L7 (Layer 7 — Modelo OSI / Protocolos HTTP(S) — gRPC), ambos funcionam como proxies e diferenciando-se na quantidade e qualidade de features oferecidas: segurança, resiliência e confiabilidade, observabilidade, etc. Ao utilizarmos plataforma de mesh adquirimos mais flexibilidade, visto que as configurações para o kube-proxy são globais, não havendo possibilidade de modificações cirúrgicas e individuais.

Geralmente soluções de service mesh rodam em cima Kubernetes cluster, necessitando serem configuradas/adaptadas a realidade de cada cluster, devendo ser encarado como pacote extra de software. Atualmente, existem diversas opções de mesh diferenciando-se entre quantidade de funcionalidades suportadas e maturidade do projeto.

Cilium: Service Mesh e Sidecarless

Conceitualmente, a service mesh layer abrange um vasto número de funcionalidades visando simplificar a comunicação inter-service, ao mesmo tempo em que oferece capacidades como: service discovery, authentication, load balancing, observabilidade, criptografia, etc. Com o apoio do eBPF, Cilium tenta mudar o rumo das tecnologias de service mesh tradicionais, tornando a camada de data plane mais eficiente e favorecendo atividades de deploy e gerenciamento em cluster k8s.

As soluções de service mesh disponíveis no mercado estão restritas a imposição de utilização dos sidecar proxies / data plane (Envoy — Linkerd-proxy) individualmente em cada pod/k8s executando uma aplicação. Como exemplo, ao rodarmos 10 micro serviços com 5 réplicas (pods/k8s), ao longo de 3 nodes/k8s de um cluster, acabaremos com cerca mais de 100 containers rodando proxies/sidecar. A presença de containers extra como proxy são obrigatórios para o funcionamento deste tipo de tecnologia e por mais que não sejam elementos pesados, tal repetição reflete em gastos adicionais de recursos de infraestrutura (memória, cpu, disco, tráfego).

Alternativamente, com Cilium podemos evitar a presença dos proxies em containers individuais, optando pelo sua transferência para dentro do host/kernel do node k8s, eliminando a necessidade dos sidecars como conhecemos.

O termo “sidecarless”, no universo de service mesh, foi cunhado pelo Cilium.

eBPF: trata-se de uma tecnologia para customização de programas rodando no kernel, executando em resposta a uma grande variedade de eventos aos quais um programa eBPF pode ser vinculado. Soluções baseadas em eBPF fornecem capacidade de instrumentação no Kubernetes, endereçando grupos de funcionalidades como observabilidade, segurança e redes sem necessitar da presença de um sidecar.

Quando executando em cluster Kubernetes, existe um kernel por node/k8s, portanto todos os pods/containers rodando neste node compartilham o mesmo kernel. Como exemplo, podemos alterar dinamicamente a lógica de controle e políticas de segurança dentro do kernel linux de um node/k8s sem necessidade de alterações a nível de container/aplicação.

Fundamentalmente, a base para o Cilium refere-se ao eBPF, tecnologia a qual permite pacotes a nível de socket das interfaces de rede logo acima da camada física (eth0). Neste tipo de arquitetura, permitimos ao pod/k8s rodar somente o container do serviço. Cilium não deixa de utilizar Envoy como data plane, porém não há necessidade de manter cópias dentro de cada pod/k8s correspondente a micro serviços individuais, e sim somente uma cópia do Envoy por node/k8s.

Considerações e Conclusões

Evidentemente, a complexidade de um serviço tende a variar de acordo com suas regras de negócio. Em um contexto de sistemas distribuídos podemos assumir que lidar com a comunicação inter-serviços refere-se a algo muitas vezes mais complexo do que a própria implementação dos serviços.

Embora as arquiteturas de service mesh não se restrinjam aos micro serviços, provém um bom exemplo de como esses conceitos podem ser aplicados na prática. Adicionalmente, a implementação de camadas de mesh para gerenciar a comunicação entre serviços podem aumentar a velocidade de deployment, conferindo maior agilidade ao endereçar os desafios relacionados à segurança e observabilidade.

Aplicaçẽs monolíticas e mesmo o SOA/ESB não evidenciaram a necessidade de uma camada de mesh isolada aos seus artefatos pois contavam com uma infraestrutura central dedicada a este tipo de necessidade.

Estruturas de mesh são indicadas para ecossistemas complexos, com números expressivos de serviços e precisamos estar atentos aos trade-offs. Conforme a complexidade aumenta, podemos contar com abordagens e padrões estabelecidos ao longo do tempo visando reduzir os riscos impostos pelos sistemas distribuídos, a nível de comunicação + infraestrutura, superando desafios chave e favorecendo o contexto de implantação das arquiteturas de micro serviços.

Independente da escolha que fizermos, teremos de conviver com ela. Durante o processo de adoção de tecnologias de service mesh, precisamos evitar os ruídos e nos concentrar em entender de maneira concreta o problema para podermos entender a solução. Torna-se essencial termos certeza de estarmos tomando as melhores decisões, baseando-as em trade-offs reais de engenharia e não em tendências passageiras.

--

--