Existem
vários artigos sobre a malha de serviço
na Internet, e aqui está outro. Viva! Mas porque? Então, o que quero expressar minha opinião é que seria melhor que malhas de serviço aparecessem há 10 anos, antes do surgimento de plataformas de contêineres como Docker e Kubernetes. Não afirmo que meu ponto de vista seja melhor ou pior que outros, mas como as malhas de serviço são animais bastante complexos, a multiplicidade de pontos de vista ajudará a melhor entendê-los.
Falarei sobre a plataforma dotCloud, que foi construída em mais de cem microsserviços e suportou milhares de aplicativos em contêineres. Explicarei os problemas que encontramos durante seu desenvolvimento e lançamento e como as malhas de serviço podem ajudar (ou não).
História do dotCloud
Eu já escrevi sobre a história do dotCloud e a escolha da arquitetura para esta plataforma, mas falei um pouco sobre o nível da rede. Se você não quiser ler o
artigo anterior sobre o dotCloud, aqui está um breve resumo: é uma plataforma PaaS como serviço que permite aos clientes iniciar uma ampla gama de aplicativos (Java, PHP, Python ...), com suporte para uma ampla variedade de serviços de dados (MongoDB, MySQL, Redis ...) e um fluxo de trabalho como o Heroku: você carrega seu código na plataforma, cria imagens de contêineres e as implementa.
Vou lhe dizer como o tráfego foi direcionado para a plataforma dotCloud. Não porque foi especialmente legal (embora o sistema funcionou bem por seu tempo!), Mas principalmente porque, com a ajuda de ferramentas modernas, esse design pode ser facilmente implementado em pouco tempo por uma equipe modesta, se eles precisam de uma maneira de direcionar o tráfego entre vários microsserviços ou um monte de aplicativos. Assim, você pode comparar as opções: o que acontece se você desenvolver tudo sozinho ou usar a malha de serviço existente. Escolha padrão: faça você mesmo ou compre.
Roteamento de tráfego para aplicativos hospedados
Os aplicativos DotCloud podem fornecer pontos de extremidade HTTP e TCP.
Os pontos de extremidade HTTP são adicionados dinamicamente à configuração de cluster do balanceador de carga
Hipache . Isso é semelhante ao que os recursos do Kubernetes
Ingress e um balanceador de carga como o
Traefik fazem hoje .
Os clientes se conectam aos pontos de extremidade HTTP por meio de seus respectivos domínios, desde que o nome do domínio aponte para os balanceadores de carga do dotCloud. Nada de especial.
Os pontos de extremidade TCP são associados a um número de porta, que é passado para todos os contêineres dessa pilha por meio de variáveis de ambiente.
Os clientes podem se conectar aos pontos de extremidade do TCP usando o nome do host apropriado (algo como gateway-X.dotcloud.com) e o número da porta.
Esse nome de host é resolvido para um cluster de servidores "nats" (não relacionados ao
NATS ) que encaminham as conexões TCP de entrada para o contêiner correto (ou, no caso de serviços com balanceamento de carga, para os contêineres corretos).
Se você estiver familiarizado com o Kubernetes, isso provavelmente lembrará os serviços do
NodePort .
Não havia serviços
ClusterIP equivalentes na
plataforma dotCloud : por simplicidade, o acesso aos serviços era o mesmo, tanto de dentro como de fora da plataforma.
Tudo foi organizado de maneira simples: as implementações iniciais das redes de roteamento HTTP e TCP, provavelmente apenas algumas centenas de linhas de Python. Algoritmos simples (eu diria ingênuos) que foram finalizados com o crescimento da plataforma e o advento de requisitos adicionais.
Não foi necessária uma refatoração extensiva do código existente. Em particular,
aplicativos de 12 fatores podem usar diretamente o endereço obtido por meio de variáveis de ambiente.
Como isso difere de uma malha de serviço moderna?
Visibilidade limitada. Geralmente, não tínhamos métricas para a grade de roteamento TCP. Quanto ao roteamento HTTP, versões posteriores têm métricas HTTP detalhadas com códigos de erro e tempos de resposta, mas as malhas de serviço modernas vão ainda mais longe, fornecendo integração com sistemas de coleta de métricas como o Prometheus, por exemplo.
A visibilidade é importante não apenas do ponto de vista operacional (para ajudar a solucionar problemas), mas também quando novos recursos são lançados. Trata-se de uma
implantação azul esverdeada segura e
implantação de canários .
A eficiência de roteamento também
é limitada. Na grade de roteamento dotCloud, todo o tráfego precisava passar por um cluster de nós de roteamento dedicados. Isso significou um possível cruzamento de várias fronteiras do AZ (zonas de acessibilidade) e um aumento significativo no atraso. Lembro-me de como corrigi problemas com o código que fazia mais de cem consultas SQL por página e, para cada consulta, abriu uma nova conexão com o servidor SQL. Quando iniciada localmente, a página é carregada instantaneamente, mas no dotCloud, o carregamento leva alguns segundos, porque leva dezenas de milissegundos para cada conexão TCP (e consulta SQL subsequente). Nesse caso em particular, conexões persistentes resolveram o problema.
As malhas de serviço modernas se saem melhor com esses problemas. Primeiro, eles verificam se as conexões são roteadas
na origem . O fluxo lógico é o mesmo:
→ →
, mas agora a malha funciona localmente e não em nós remotos, portanto, a conexão
→
é local e muito rápida (microssegundos em vez de milissegundos).
As malhas de serviço modernas também implementam algoritmos de balanceamento de carga mais inteligentes. Ao controlar o desempenho dos back-ends, eles podem enviar mais tráfego para back-ends mais rápidos, o que leva a um aumento no desempenho geral.
A segurança também é melhor. A grade de roteamento do dotCloud funcionou completamente no EC2 Classic e não criptografou o tráfego (supondo que se alguém conseguisse detectar o tráfego de rede do EC2, você já terá grandes problemas). As malhas de serviço modernas protegem de forma transparente todo o nosso tráfego, por exemplo, com autenticação TLS mútua e criptografia subsequente.
Roteamento de tráfego para serviços de plataforma
Ok, discutimos o tráfego entre aplicativos, mas e a plataforma dotCloud?
A plataforma em si consistia em cerca de cem microsserviços responsáveis por várias funções. Alguns receberam pedidos de outros, e outros eram trabalhadores em segundo plano que se conectaram a outros serviços, mas não aceitaram conexões. De qualquer forma, cada serviço deve conhecer os pontos de extremidade dos endereços aos quais é necessário se conectar.
Muitos serviços de alto nível podem usar a grade de roteamento descrita acima. De fato, muitos dos mais de centenas de microsserviços dotCloud foram implantados como aplicativos regulares na própria plataforma dotCloud. Mas um pequeno número de serviços de baixo nível (em particular, que implementam essa grade de roteamento) precisava de algo mais simples, com menos dependências (já que não podiam depender de si mesmos para trabalhar - um bom e velho problema de galinha e ovo).
Esses serviços importantes e de baixo nível foram implantados executando contêineres diretamente em vários nós principais. Ao mesmo tempo, serviços de plataforma padrão não estavam envolvidos: o vinculador, o planejador e o corredor. Se você deseja comparar com as modernas plataformas de contêineres, é como iniciar um plano de controle com a
docker run
diretamente nos nós, em vez de delegar a tarefa Kubernetes. Isso é bastante semelhante ao conceito de
módulos estáticos (lares) que o
kubeadm ou o
bootkube usa ao carregar um cluster independente.
Esses serviços foram expostos de maneira simples e grosseira: seus nomes e endereços foram listados no arquivo YAML; e cada cliente teve que tirar uma cópia desse arquivo YAML para implantação.
Por um lado, é extremamente confiável, porque não requer suporte a um armazenamento de chave / valor externo como o Zookeeper (não se esqueça, naquele momento o etcd ou o Consul ainda não existiam). Por outro lado, isso dificultava a movimentação de serviços. Sempre que em movimento, todos os clientes devem ter recebido um arquivo YAML atualizado (e potencialmente reinicializados). Não é muito conveniente!
Posteriormente, começamos a introduzir um novo esquema, em que cada cliente se conectava a um servidor proxy local. Em vez do endereço e da porta, basta que ele saiba apenas o número da porta do serviço e conecte-se através do
localhost
. O servidor proxy local processa essa conexão e a encaminha para o servidor real. Agora, ao mover o back-end para outra máquina ou escalar em vez de atualizar todos os clientes, é necessário atualizar apenas todos esses proxies locais; e uma reinicialização não é mais necessária.
(Também foi planejado encapsular o tráfego nas conexões TLS e colocar outro servidor proxy no lado receptor, além de verificar os certificados TLS sem a participação do serviço receptor, configurado para aceitar conexões apenas no
localhost
. Mais sobre isso mais adiante).
Isso é muito semelhante ao
SmartStack do Airbnb, mas a diferença significativa é que o SmartStack é implementado e implantado na produção, enquanto o sistema interno de roteamento dotCloud foi colocado em uma caixa quando o dotCloud se transformou no Docker.
Pessoalmente, considero o SmartStack um dos antecessores de sistemas como Istio, Linkerd e Consul Connect, porque todos seguem o mesmo padrão:
- Executando proxies em cada nó.
- Clientes se conectam ao proxy.
- O plano de gerenciamento atualiza a configuração do proxy ao alterar back-end.
- ... Lucro!
Implementação moderna de uma malha de serviço
Se precisarmos implementar uma grade semelhante hoje, podemos usar princípios semelhantes. Por exemplo, configure a zona DNS interna mapeando nomes de serviço para endereços em
127.0.0.0/8
. Em seguida, execute o HAProxy em cada nó do cluster, aceitando conexões com cada endereço de serviço (
127.0.0.0/8
nesta sub-rede) e redirecionando / equilibrando a carga para os back-end correspondentes. A configuração do HAProxy pode ser controlada pelo
confd , permitindo armazenar informações de back-end no etcd ou Consul e enviar automaticamente a configuração atualizada ao HAProxy quando necessário.
É assim que o Istio funciona! Mas com algumas diferenças:
- Usa o Envoy Proxy em vez do HAProxy.
- Salva a configuração de back-end via API do Kubernetes, em vez do etcd ou Consul.
- Os serviços são endereços alocados na sub-rede interna (endereços do Kubernetes ClusterIP) em vez de 127.0.0.0/8.
- Possui um componente opcional (Citadel) para adicionar autenticação TLS mútua entre o cliente e os servidores.
- Suporta novos recursos, como quebra de circuito, rastreamento distribuído, implantação de canários, etc.
Vamos dar uma olhada rápida em algumas das diferenças.
Proxy enviado
O Enftoy Proxy foi escrito por Lyft [concorrente da Uber no mercado de táxis - aprox. trans.]. É muito semelhante a outros proxies de várias maneiras (por exemplo, HAProxy, Nginx, Traefik ...), mas Lyft escreveu o seu próprio porque precisava de funções que não estão em outros proxies, e parecia mais razoável criar um novo do que expandir o existente.
O enviado pode ser usado sozinho. Se eu tiver um serviço específico que deve se conectar a outros serviços, posso configurá-lo para conectar-se ao Envoy e, em seguida, configurar e reconfigurar dinamicamente o Envoy com a localização de outros serviços, enquanto recebo muitos recursos adicionais excelentes, por exemplo, visibilidade. Em vez de uma biblioteca-cliente personalizada ou incorporação de rastreamento de chamadas no código, direcionamos o tráfego para o Envoy e ele coleta métricas para nós.
Mas o Envoy também pode trabalhar como um plano de dados para uma malha de serviço. Isso significa que, para essa malha de serviço, o Envoy agora está configurado
pelo plano de controle.
Plano de controle
No plano de gerenciamento, o Istio conta com a API Kubernetes.
Isso não é muito diferente do uso de confd , que depende do etcd ou Consul para exibir um conjunto de chaves em um data warehouse. O Istio, por meio da API do Kubernetes, exibe o conjunto de recursos do Kubernetes.
Entre o caso : eu pessoalmente achei
útil essa
descrição da API do Kubernetes , que diz:
O servidor de API do Kubernetes é um "servidor burro" que oferece armazenamento, versão, validação, atualização e semântica de recursos da API.
O Istio foi projetado para trabalhar com o Kubernetes; e se você quiser usá-lo fora do Kubernetes, precisará executar uma instância do servidor da API do Kubernetes (e serviço auxiliar etcd).
Endereços de Serviço
O Istio conta com os endereços ClusterIP que o Kubernetes aloca, para que os serviços do Istio obtenham um endereço interno (não no intervalo
127.0.0.0/8
).
O tráfego para o endereço ClusterIP de um serviço específico no cluster Kubernetes sem o Istio é interceptado pelo kube-proxy e enviado à parte do servidor desse proxy. Se você estiver interessado em detalhes técnicos, o kube-proxy definirá as regras do iptables (ou balanceadores de carga IPVS, dependendo de como você a configura) para reescrever os endereços IP de destino das conexões que vão para o endereço ClusterIP.
Depois de instalar o Istio no cluster Kubernetes, nada muda até que seja explicitamente ativado para o consumidor em questão ou mesmo para todo o espaço para nome, introduzindo o contêiner
sidecar
em lareiras personalizadas. Esse contêiner iniciará uma instância do Envoy e definirá uma série de regras do iptables para interceptar o tráfego para outros serviços e redirecionar esse tráfego para o Envoy.
Quando integrado ao DNS do Kubernetes, isso significa que nosso código pode se conectar pelo nome do serviço e tudo "simplesmente funciona". Em outras palavras, nosso código emite solicitações como
http://api/v1/users/4242
, depois a
api
resolve a solicitação para
10.97.105.48
, as regras do iptables interceptam conexões de 10.97.105.48 e as redirecionam para o proxy local do Envoy, e esse proxy local direciona solicitação para a API de back-end real. Fuh!
Coisinhas extras
O Istio também fornece criptografia e autenticação de ponta a ponta através do mTLS (TLS mútuo). O componente chamado
Citadel é responsável por isso.
Também existe um componente do
Mixer que a Envoy pode solicitar para
cada solicitação, a fim de tomar uma decisão especial sobre essa solicitação, dependendo de vários fatores, como cabeçalhos, carregamento de back-end, etc. ... (não se preocupe: existem várias maneiras de garantir que o Mixer funcione e até se travar, o Enviado continuará funcionando normalmente como proxy).
E, é claro, mencionamos visibilidade: o Envoy coleta um grande número de métricas, fornecendo rastreamento distribuído. Na arquitetura dos microsserviços, se uma solicitação de API precisar passar pelos microsserviços A, B, C e D, quando você efetuar login no sistema, o rastreio distribuído adicionará um identificador exclusivo à solicitação e salvará esse identificador através de subconsultas em todos esses microsserviços, permitindo registrar todas as chamadas relacionadas, seus atrasos etc.
Desenvolver ou comprar
Istio tem uma reputação de ser um sistema complexo. Por outro lado, a construção de uma grade de roteamento, que descrevi no início deste post, é relativamente simples usando as ferramentas existentes. Então, faz sentido criar sua própria malha de serviço?
Se tivermos necessidades modestas (você não precisa de visibilidade, um disjuntor e outras sutilezas), então pensamos em desenvolver sua própria ferramenta. Mas se usarmos o Kubernetes, pode até não ser necessário, porque o Kubernetes já fornece ferramentas básicas para descoberta de serviços e balanceamento de carga.
Porém, se tivermos requisitos avançados, "comprar" uma malha de serviço parece ser uma opção muito melhor. (Isso nem sempre é uma "compra", porque o Istio vem com código-fonte aberto, mas ainda precisamos investir tempo de engenharia para entender seu trabalho, implantá-lo e gerenciá-lo).
O que escolher: Istio, Linkerd ou Consul Connect?
Até agora, falamos apenas do Istio, mas essa não é a única malha de serviço. Uma alternativa popular é o
Linkerd , e também o
Consul Connect .
O que escolher?
Honestamente, eu não sei. No momento, não me considero competente o suficiente para responder a essa pergunta. Existem alguns
artigos interessantes comparando essas ferramentas e até
referências .
Uma abordagem promissora é usar uma ferramenta como o
SuperGloo . Ele implementa uma camada de abstração para simplificar e unificar as APIs fornecidas pelas malhas de serviço. Em vez de estudar APIs específicas (e, na minha opinião, relativamente complexas) de várias malhas de serviço, podemos usar construções SuperGloo mais simples - e alternar facilmente de uma para outra, como se tivéssemos um formato de configuração intermediário que descreve interfaces HTTP e back-end capazes de gerar a configuração real para Nginx, HAProxy, Traefik, Apache ...
Aceitei um pouco o Istio e o SuperGloo e, no próximo artigo, quero mostrar como adicionar o Istio ou o Linkerd a um cluster existente usando o SuperGloo, e quanto o último suportará seu trabalho, ou seja, permite alternar de uma malha de serviço para outra sem reescrever as configurações.