Service Mesh Architectures

Se você está construindo seu software e seus times de desenvolvimento utilizando conceitos de microsserviços, então está procurando maneiras de iterar mais rapidamente e escalar com flexibilidade. Um Service Mesh pode ajudá-lo a fazer isso, mantendo (ou aprimorando) visibilidade e controle. Neste blog, vou falar sobre o que realmente é Service Mesh e o que você deve considerar ao escolher e implantar um.

Esse texto é  uma tradução autorizada do artigo escrito por Andrew Jenkins. Para acessar a versão original em inglês, clique aqui

Então, o que é um Service Mesh? Como ele difere do que você já conhece? Service Mesh é uma camada de comunicação que roda sobre solicitação/resposta, possibilitando o uso de alguns padrões essenciais para microsserviços robustos. Alguns dos meus favoritos:

  • Segurança de confiabilidade zero que não assume um perímetro confiável
  • Rastreamento que mostra como e por que cada microsserviço conversou com outro microsserviço
  • Injeção e tolerância a falhas que permitem verificar experimentalmente a resiliência de sua aplicação
  • Roteamento avançado que deverá permitir por exemplo: fazer teste A/B, agilidade no controle de versão e implantação e ofuscamento de chamadas

Por que o novo termo “Service Mesh”?

Olhando para essa lista, você pode pensar “eu posso fazer tudo isso sem Service Mesh”, e você está correto. A mesma lógica se aplica aos “Sliding Window Protocols” ou ao “Request Framing”. Mas, uma vez que há um padrão emergente que faz o que você quer, é mais eficiente confiar nessa camada em vez de implementá-la por conta própria. Service Mesh é essa camada emergente para padrões de microsserviços.

Service Mesh ainda é nova o suficiente para que os padrões codificados ainda não tenham emergido, mas há experiência suficiente de que algumas práticas recomendadas estão começando a ficar claras. Como os líderes de ponta desenvolvem suas próprias abordagens, muitas vezes é útil comparar notas e destilar as melhores práticas. Vimos o Kubernetes emergir como a maneira padrão de executar contêineres para aplicações da web de produção. Meus padrões favoritos são os emergentes ao invés dos obrigatórios: é definitivamente uma arte não ser nem cedo nem tarde demais para concordar com APIs, protocolos e conceitos comuns.

Pense na história das redes de computadores. Após a inovação das redes de comutação de pacotes de melhor esforço, descobrimos que muitos de nós estávamos criando circuitos virtuais sobre eles – usando handshaking, retransmissão e interconexão de redes para transformar uma pilha de pacotes em um fluxo ordenado de bytes. Por uma questão de interoperabilidade e simplicidade, surgiram  as melhores práticas de “fluxo contínuo sobre pacotes”: TCP (a Introdução do  RFC675  fez um bom trabalho para explicar suas camadas sobrepostas). Existem alternativas –  usei o  Licklider Transmission Protocol  em redes espaciais onde o controle de congestionamento distribuído não é nem necessário nem eficiente. Seu navegador talvez já esteja usando o  QUIC. A padronização do TCP, no entanto, liberou uma geração de programadores de mexer com implementações de janelas deslizantes, tentativas (retries), e colapso de congestionamento (bem, exceto por aqueles programadores que o implementaram).

Em seguida, encontramos muitos protocolos de solicitação/resposta executando sobre o TCP. Muitos deles eventualmente migraram para o HTTP (ou continuações como HTTP/2 ou gRPC). Se você puder fatorar sua comunicação em “método, metadados, corpo”, você deve estar olhando para um protocolo semelhante a HTTP para gerenciar o enquadramento, separar os metadados do corpo e endereçar o primeiro pacote (head-of-line blocking). Isso vai além de apenas aplicativos de navegador – bancos de dados como o Mongo fornecem interfaces HTTP porque a ubiquidade do HTTP libera uma enorme quantidade de ferramental e conhecimento do desenvolvedor.

Você pode pensar em Service Mesh como sendo o léxico, a API e a implementação em torno do próximo nível de padrões de comunicação para microsserviços.

OK, então onde é que essa camada vive? Você tem algumas opções:

  • Em uma biblioteca onde os aplicativos de seus microsserviços importam e usam.
  • Em um Agente de nó ou daemon que atende todos os contêineres em um nó/máquina específico.
  • Em um contêiner do Sidecar que é executado ao lado do contêiner do seu aplicativo.

Biblioteca

A abordagem da biblioteca é a original. É simples e direta. Nesse caso, cada aplicativo de microsserviço inclui um código de biblioteca que implementa recursos de Service Mesh. Bibliotecas como  Hystrix  e Ribbon  seriam exemplos dessa abordagem.

Isso funciona bem para aplicativos que são escritos exclusivamente em uma linguagem pelas equipes que os executam (assim fica fácil inserir as bibliotecas). A abordagem da biblioteca também não requer muita cooperação da infraestrutura subjacente – o gerenciador de contêineres (como o Kubernetes) não precisa estar ciente de que você está executando um aplicativo Hystrix aprimorado.

Há algum trabalho em bibliotecas multilinguagens (reimplementações dos mesmos conceitos). O desafio aqui é a complexidade e o esforço envolvidos na replicação do mesmo comportamento repetidamente.

Observamos uma adoção muito limitada do modelo de biblioteca em nossa base de usuários porque a maioria de nossos usuários está executando aplicativos escritos em muitas linguagens diferentes (poliglota) e também estão executando pelo menos alguns aplicativos que não foram escritos por eles e, portanto, injetar bibliotecas não é viável.

Esse modelo tem uma vantagem na contabilidade de trabalho: o código que executa o trabalho em nome do microsserviço está realmente em execução nesse próprio microsserviço. O limite de confiança também é pequeno – você só precisa confiar em chamar uma biblioteca em seu próprio processo, não necessariamente um serviço remoto em algum ponto distante da rede. Esse código tem apenas tantos privilégios quanto o microsserviço em nome do qual está executando o trabalho. Esse trabalho também é executado no contexto do microsserviço, então é fácil alocar recursos como tempo de CPU ou memória para esse trabalho – o Sistema Operacional provavelmente faz isso por você.

Agente do Nó

O modelo do agente do nó é a próxima alternativa. Nesta arquitetura, há um agente separado (geralmente um processo de espaço de usuário) em execução em cada nó, atendendo a uma mistura heterogênea de cargas de trabalho. Para fins de comparação, é o oposto do modelo de biblioteca: ele não se importa com a linguagem da sua aplicação, mas atende a vários microsserviços inquilinos diferentes.

A implantação recomendada do Linkerd no Kubernetes funciona assim. Do mesmo modo que o Application Service Proxy (ASP) da F5 e o Kube-proxy padrão do Kubernetes.

Como você precisa de um agente de nó em cada nó, essa implantação requer alguma cooperação da infraestrutura – esse modelo não funciona sem um pouco de coordenação. Por analogia, a maioria dos aplicativos não pode simplesmente escolher sua própria pilha TCP, adivinhar um número de porta efêmero e enviar ou receber pacotes TCP diretamente – eles delegam isso à infraestrutura (sistema operacional).

Em vez de uma boa contabilidade de trabalho, esse modelo enfatiza o compartilhamento de recursos de trabalho – se um agente de nó alocar alguma memória para armazenar dados para meu microsserviço, ele poderá se virar e usar esse buffer para dados de seu serviço em poucos segundos. Isso pode ser muito eficiente, mas possibilita um caminho para o abuso. Se meu microsserviço solicitar todo o espaço do buffer, o agente do nó precisará garantir que ele dê ao seu microsserviço uma chance no espaço do buffer primeiro. Você precisa de um pouco mais de código para gerenciar isso para cada recurso compartilhado.

Outro recurso de trabalho que se beneficia do compartilhamento é a informação de configuração. É mais barato distribuir uma cópia da configuração para cada nó do que distribuir uma cópia da configuração para cada pod em cada nó.

Muitas funcionalidades nas quais os microsserviços que estão em contêineres dependem são fornecidas por um Agente do Nó ou algo topologicamente equivalente. Pense no kubernetes inicializando seu pod, seu daemon de Interface de Rede de Contêiner (CNI) favorito como Flannel ou, expandindo um pouco seu cérebro, até mesmo o próprio kernel do sistema operacional seguindo este modelo de agente do nó.

Sidecar

Sidecar é a novidade do momento. Esse é o modelo usado pela Istio com o Envoy. Conduit também usa uma abordagem de Sidecar. Nas implantações de Sidecar, você tem um contêiner adjacente implantado para cada contêiner de aplicativo. Para uma Service Mesh, o Sidecar controla todo o tráfego da rede dentro e fora do contêiner do aplicativo.

Essa abordagem é intermediária, entre as abordagens da biblioteca e do agente do nó devido às muitas razões discutidas até agora. Por exemplo, você pode implantar um Sidecar Service Mesh sem ter que executar um novo agente em cada nó (assim você não precisar de cooperação em toda a infraestrutura para implantar esse agente compartilhado), mas estará executando várias cópias de um Sidecar idêntico. Sob outra perspectiva: posso instalar uma Service Mesh para um grupo de microsserviços, e você pode instalar uma Service Mesh diferente e (com algumas ressalvas específicas da implementação) não precisamos coordenar. Isso é poderoso nos primeiros dias da Service Mesh, quando você e eu podemos compartilhar o mesmo cluster do Kubernetes, mas ter objetivos diferentes, exigir conjuntos de recursos diferentes ou ter diferentes tolerâncias para “última geração” versus “testado e aprovado”.

Sidecar é vantajoso para contabilidade de trabalho, especialmente em alguns aspectos relacionados à segurança. Aqui está um exemplo: suponha que eu esteja usando Service Mesh para fornecer segurança do tipo confiança-zero. Eu quero que a Service Mesh verifique ambas as extremidades (cliente e servidor) de uma conexão criptograficamente. Vamos primeiro considerar o uso de um agente do nó: Quando meu pod quer ser o cliente de outro pod servidor, o agente do nó vai autenticar em nome do meu pod. O agente do nó também está servindo outros pods, portanto, é necessário ter cuidado para que outro pod não consiga enganá-lo e autenticá-lo em nome do meu pod. Se pensarmos no caso do Sidecar, o Sidecar do meu pod não servirá outros pods. Podemos seguir o princípio do menor privilégio e dar o mínimo necessário para o único pod que está sendo usado em termos de chaves de autenticação, memória e recursos de rede.

Assim, do lado de fora, o Sidecar tem os mesmos privilégios do aplicativo ao qual ele está conectado. Por outro lado, o Sidecar precisa intervir entre o aplicativo e o exterior. Isso cria alguma tensão de segurança: você quer que o Sidecar tenha o menor privilégio possível, mas é preciso dar privilégios suficientes para controlar o tráfego de/para o aplicativo. Por exemplo, no Istio, o container init, responsável por configurar o Sidecar tem a permissão corrente do NET_ADMIN (para configurar as regras de iptables  necessárias). Essa inicialização usa boas práticas de segurança – ele faz o mínimo necessário e depois desaparece, mas tudo com NET_ADMIN representa a superfície de ataque. (Boas notícias – pessoas inteligentes estão trabalhando para melhorar isso ainda mais).

Depois que o Sidecar é anexado ao aplicativo, ele fica muito próximo de uma perspectiva de segurança. Não tão perto quanto uma chamada de função em seu processo (como biblioteca), mas geralmente mais perto do que chamar um agente de nó multi-inquilino. Ao usar o Istio no Kubernetes, o contêiner do seu aplicativo fala com o Sidecar por meio de uma interface de loopback dentro do namespace de rede compartilhado com o pod – portanto, outros pods e agentes de nó geralmente não conseguem ver essa comunicação.

A maioria dos clusters Kubernetes tem mais de um pod por nó (e, portanto, mais de um Sidecar por nó). Se cada Sidecar precisa saber “a configuração inteira” (seja lá o que isso significa para o seu contexto), então você precisará de mais largura de banda para distribuir essa configuração (e mais memória para armazenar cópias dela). Então, pode ser poderoso limitar o escopo da configuração que você tem que dar a cada Sidecar – mas novamente há uma tensão oposta: algo (no caso do Istio, o Pilot) tem que gastar mais esforço computando o que foi reduzido da configuração de cada Sidecar.

Outras coisas que são replicadas entre Sidecars acumulam uma conta semelhante. Boas notícias – os tempos de execução do contêiner reutilizam coisas como imagens de disco de contêiner quando eles são idênticos e você está usando os drivers corretos, portanto a penalidade de disco não é especialmente significativa em muitos casos e a memória, como páginas de código, também pode ser compartilhada. Mas a memória específica do processo de cada Sidecar será exclusiva para esse Sidecar, por isso é importante manter isso sob controle e evitar tornar o Sidecar “pesado”, fazendo um monte de trabalho replicado em cada Sidecar.

As Service Meshs que dependem do Sidecar fornecem um bom equilíbrio entre um conjunto completo de recursos e um conjunto mais leve.

O agente do nó ou o modelo Sidecar irá prevalecer?

Eu acho que você provavelmente verá um pouco dos dois. Agora parece ser um momento perfeito para a Service Mesh de Sidecar: tecnologia emergente, iteração rápida e adoção gradual. À medida que a Service Mesh amadurece e a taxa de mudança diminui, veremos mais aplicativos do modelo de agente do nó.

As vantagens do modelo de agente do nó são particularmente importantes à medida que as implementações de Service Mesh amadurecem e os clusters se tornam grandes:

  • Menos sobrecarga (especialmente memória) para coisas que poderiam ser compartilhadas através do nó
  • Mais fácil escalar a distribuição de informações de configuração
  • Um agente de nó bem construído pode transferir recursos de forma eficiente de um aplicativo que está servindo para outro aplicativo

Sidecar é uma nova maneira de fornecer serviços (como um proxy de comunicação de alto nível “a laService Mesh) para aplicativos. É especialmente bem adaptado para contêiners e Kubernetes. Algumas de suas maiores vantagens incluem:

  • Pode ser gradualmente adicionado a um cluster existente sem coordenação central
  • O trabalho realizado para um aplicativo é contabilizado para esse aplicativo
  • A comunicação App-to-Sidecar é mais fácil de proteger do que o App-to-Agent

O que vem depois?

Como Shawn falou em  seu post , estivemos pensando em como os microsserviços mudaram os requisitos da infraestrutura de rede nos últimos anos. A onda de apoio e aceitação do Istio nos demonstrou que há uma comunidade pronta para desenvolver e unir-se às especificações de políticas, com uma implementação bem arquitetada para acompanhá-lo.

O Istio está avançando na comunicação de microsserviços de última geração e estamos empolgados em ajudar a tornar essa tecnologia fácil de operar, confiável e adequada para o fluxo de trabalho de sua equipe em nuvem privada, nuvem pública ou híbrida.

Autor: Andrew Jenkins

fique atualizado

Assine nossa newsletter e acompanhe as nossas novidades.

© 2019 Opus Software. Todos os direitos reservados. All rights reserved.