Contêineres, microsserviços e malhas de serviço

Na internet pilha artigos о malha de serviço (malha de serviço), e aqui está outro. Viva! Mas por que? Então, quero expressar minha opinião de que teria sido melhor se as malhas de serviço tivessem surgido há 10 anos, antes do advento das plataformas de contêineres como Docker e Kubernetes. Não estou dizendo que meu ponto de vista seja melhor ou pior que os outros, mas como as malhas de serviço são animais bastante complexos, múltiplos pontos de vista ajudarão a compreendê-los melhor.

Falarei sobre a plataforma dotCloud, que foi construída em mais de cem microsserviços e oferece suporte a milhares de aplicativos em contêineres. Explicarei os desafios que enfrentamos ao desenvolvê-lo e lançá-lo e como as malhas de serviço poderiam (ou não) ajudar.

História do dotCloud

Escrevi sobre a história do dotCloud e as opções de arquitetura para esta plataforma, mas não falei muito sobre a camada de rede. Se você não quer mergulhar na leitura último artigo sobre dotCloud, aqui está a essência: é uma plataforma como serviço PaaS que permite aos clientes executar uma ampla gama de aplicações (Java, PHP, Python...), com suporte para uma ampla gama de dados serviços (MongoDB, MySQL, Redis...) e um fluxo de trabalho como Heroku: você carrega seu código na plataforma, ela cria imagens de contêiner e as implanta.

Vou contar como o tráfego foi direcionado para a plataforma dotCloud. Não porque fosse particularmente legal (embora o sistema funcionasse bem para a época!), mas principalmente porque com ferramentas modernas tal design pode ser facilmente implementado em um curto espaço de tempo por uma equipe modesta se eles precisarem de uma maneira de rotear o tráfego entre um grupo de microsserviços ou um monte de aplicativos. Dessa forma, você pode comparar as opções: o que acontece se você desenvolver tudo sozinho ou usar uma malha de serviço existente. A escolha padrão é fazer você mesmo ou comprar.

Roteamento de tráfego para aplicativos hospedados

Os aplicativos no dotCloud podem expor endpoints HTTP e TCP.

Pontos de extremidade HTTP adicionado dinamicamente à configuração do cluster do balanceador de carga Hipache. Isso é semelhante ao que os recursos fazem hoje Ingresso no Kubernetes e um balanceador de carga como Traefik.

Os clientes se conectam a endpoints HTTP por meio de domínios apropriados, desde que o nome de domínio aponte para balanceadores de carga dotCloud. Nada especial.

Pontos de extremidade TCP associado a um número de porta, que é então passado para todos os contêineres nessa pilha por meio de variáveis ​​de ambiente.

Os clientes podem se conectar a endpoints TCP usando o nome de host apropriado (algo como gateway-X.dotcloud.com) e o número da porta.

Este nome de host é resolvido para o cluster de servidores “nats” (não relacionado a NATS), que roteará 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 o lembrará dos Serviços Porta do nó.

Não havia serviços equivalentes na plataforma dotCloud ClusterIP: Para simplificar, os serviços foram acessados ​​da mesma forma tanto dentro quanto fora da plataforma.

Tudo foi organizado de forma bastante simples: as implementações iniciais de redes de roteamento HTTP e TCP provavelmente continham apenas algumas centenas de linhas de Python cada. Algoritmos simples (eu diria ingênuos) que foram refinados à medida que a plataforma crescia e requisitos adicionais apareciam.

A refatoração extensiva do código existente não foi necessária. Em particular, Aplicativos de 12 fatores pode usar diretamente o endereço obtido por meio de variáveis ​​de ambiente.

Como isso difere de uma malha de serviço moderna?

Limitado visibilidade. Não tínhamos nenhuma métrica para a malha de roteamento TCP. Quando se trata de roteamento HTTP, versões posteriores introduziram 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 ao lançar novos recursos. É sobre segurança implantação azul-verde и implantação canário.

Eficiência de roteamento também é limitado. Na malha de roteamento dotCloud, todo o tráfego tinha que passar por um cluster de nós de roteamento dedicados. Isso significava cruzar potencialmente vários limites de AZ (zona de disponibilidade) e aumentar significativamente a latência. Lembro-me do código de solução de problemas que fazia mais de cem consultas SQL por página e abria uma nova conexão com o servidor SQL para cada consulta. Ao ser executada localmente, a página carrega instantaneamente, mas no dotCloud leva alguns segundos para carregar porque cada conexão TCP (e consulta SQL subsequente) leva dezenas de milissegundos. Neste caso específico, as conexões persistentes resolveram o problema.

As malhas de serviço modernas são melhores para lidar com esses problemas. Primeiro de tudo, eles verificam se as conexões estão roteadas na fonte. O fluxo lógico é o mesmo: клиент → меш → сервис, mas agora a malha funciona localmente e não em nós remotos, então a conexão клиент → меш é local e muito rápido (microssegundos em vez de milissegundos).

As malhas de serviço modernas também implementam algoritmos de balanceamento de carga mais inteligentes. Ao monitorar a integridade dos back-ends, eles podem enviar mais tráfego para back-ends mais rápidos, resultando em melhor desempenho geral.

segurança melhor também. A malha de roteamento dotCloud foi executada inteiramente no EC2 Classic e não criptografou o tráfego (com base na suposição de que se alguém conseguisse colocar um sniffer no tráfego de rede do EC2, você já estaria em apuros). 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, já discutimos o tráfego entre aplicativos, mas e a própria plataforma dotCloud?

A própria plataforma consistia em cerca de cem microsserviços responsáveis ​​por diversas funções. Alguns aceitaram solicitações de outros e alguns eram trabalhadores em segundo plano que se conectavam a outros serviços, mas não aceitavam conexões. Em qualquer caso, cada serviço deve conhecer os pontos finais dos endereços aos quais precisa se conectar.

Muitos serviços de alto nível podem utilizar a malha de roteamento descrita acima. Na verdade, muitos dos mais de cem microsserviços do dotCloud foram implantados como aplicativos regulares na própria plataforma dotCloud. Mas um pequeno número de serviços de baixo nível (particularmente aqueles que implementam essa malha de roteamento) precisavam de algo mais simples, com menos dependências (já que não podiam depender de si mesmos para funcionar - o bom e velho problema do ovo e da galinha).

Esses serviços de missão crítica de baixo nível foram implantados executando contêineres diretamente em alguns nós principais. Neste caso, não foram utilizados serviços padrão da plataforma: linker, agendador e executor. Se você quiser comparar com plataformas de contêineres modernas, é como executar um plano de controle com docker run diretamente nos nós, em vez de delegar a tarefa ao Kubernetes. É muito semelhante em conceito módulos estáticos (pods), que utiliza kubeadm ou bootkube ao inicializar um cluster independente.

Esses serviços foram expostos de forma simples e rudimentar: um arquivo YAML listava seus nomes e endereços; e cada cliente teve que fazer uma cópia deste arquivo YAML para implantação.

Por um lado, é extremamente confiável porque não requer o suporte de um armazenamento externo de chave/valor como o Zookeeper (lembre-se, etcd ou Consul não existiam naquela época). Por outro lado, dificultou a movimentação de serviços. Cada vez que uma mudança era feita, todos os clientes recebiam um arquivo YAML atualizado (e potencialmente reinicializavam). Não é muito confortável!

Posteriormente, começamos a implementar um novo esquema, onde cada cliente se conectava a um servidor proxy local. Em vez de endereço e porta, ele só precisa saber o número da porta do serviço e conectar-se via localhost. O proxy local trata dessa conexão e a encaminha para o servidor real. Agora, ao mover o backend para outra máquina ou escalar, em vez de atualizar todos os clientes, você só precisa atualizar 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, bem como verificar os certificados TLS sem a participação do serviço receptor, que está configurado para aceitar conexões apenas em localhost. Mais sobre isso mais tarde).

Isto é muito semelhante a SmartStackGenericName do Airbnb, mas a diferença significativa é que o SmartStack é implementado e implantado na produção, enquanto o sistema de roteamento interno do dotCloud foi arquivado quando o dotCloud se tornou Docker.

Pessoalmente, considero o SmartStack um dos antecessores de sistemas como Istio, Linkerd e Consul Connect porque todos seguem o mesmo padrão:

  • Execute um proxy em cada nó.
  • Os clientes se conectam ao proxy.
  • O plano de controle atualiza a configuração do proxy quando os back-ends mudam.
  • ... Lucro!

Implementação moderna de uma malha de serviço

Se precisássemos implementar uma rede semelhante hoje, poderíamos usar princípios semelhantes. Por exemplo, configure uma zona DNS interna mapeando nomes de serviços para endereços no espaço 127.0.0.0/8. Em seguida, execute o HAProxy em cada nó do cluster, aceitando conexões em cada endereço de serviço (nessa sub-rede 127.0.0.0/8) e redirecionando/equilibrando a carga para os back-ends apropriados. A configuração do HAProxy pode ser controlada conf, permitindo que você armazene informações de back-end no etcd ou Consul e envie automaticamente a configuração atualizada para o HAProxy quando necessário.

É basicamente assim que o Istio funciona! Mas com algumas diferenças:

  • Usos Procurador do enviado em vez de HAProxy.
  • Armazena a configuração de back-end por meio da API Kubernetes em vez de etcd ou Consul.
  • Os serviços são endereços alocados na sub-rede interna (endereços Kubernetes ClusterIP) em vez de 127.0.0.0/8.
  • Possui um componente adicional (Citadel) para adicionar autenticação TLS mútua entre o cliente e os servidores.
  • Suporta novos recursos, como interrupção de circuito, rastreamento distribuído, implantação canário, etc.

Vamos dar uma olhada rápida em algumas das diferenças.

Procurador do enviado

O Envoy Proxy foi escrito por Lyft [concorrente do Uber no mercado de táxis - aprox. faixa]. É semelhante em muitos aspectos a outros proxies (por exemplo, HAProxy, Nginx, Traefik...), mas Lyft escreveu o deles porque precisava de recursos que faltavam em outros proxies e parecia mais inteligente criar um novo em vez de estender o existente.

O Envoy pode ser usado sozinho. Se eu tiver um serviço específico que precisa se conectar a outros serviços, posso configurá-lo para se conectar ao Envoy e, em seguida, configurar e reconfigurar dinamicamente o Envoy com a localização de outros serviços, enquanto obtenho muitas funcionalidades adicionais excelentes, como visibilidade. Em vez de uma biblioteca cliente personalizada ou de injetar rastreamentos de chamadas no código, enviamos tráfego para o Envoy e ele coleta métricas para nós.

Mas o Envoy também é capaz de trabalhar como plano de dados (plano de dados) para a malha de serviço. Isso significa que o Envoy agora está configurado para esta malha de serviço plano de controle (plano de controle).

Plano de controle

Para o plano de controle, o Istio depende da API Kubernetes. Isso não é muito diferente de usar confd, que depende do etcd ou do Consul para visualizar o conjunto de chaves no armazenamento de dados. O Istio usa a API Kubernetes para visualizar um conjunto de recursos do Kubernetes.

Entre isso e então: Eu pessoalmente achei isso útil Descrição da API Kubernetesque diz:

O Kubernetes API Server é um “servidor burro” que oferece armazenamento, controle de versão, validação, atualização e semântica para recursos de API.

O Istio foi projetado para funcionar com Kubernetes; e se quiser usá-lo fora do Kubernetes, você precisará executar uma instância do servidor API do Kubernetes (e o serviço auxiliar etcd).

Endereços de serviço

O Istio depende de endereços ClusterIP que o Kubernetes aloca, portanto, os serviços do Istio recebem 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 em um cluster Kubernetes sem Istio é interceptado pelo kube-proxy e enviado ao back-end desse proxy. Se você estiver interessado nos detalhes técnicos, o kube-proxy configura regras de iptables (ou balanceadores de carga IPVS, dependendo de como está configurado) para reescrever os endereços IP de destino das conexões que vão para o endereço ClusterIP.

Depois que o Istio é instalado em um cluster Kubernetes, nada muda até que ele seja explicitamente habilitado para um determinado consumidor, ou mesmo para todo o namespace, por meio da introdução de um contêiner sidecar em pods personalizados. Este contêiner irá gerar uma instância do Envoy e configurar um conjunto de regras de iptables para interceptar o tráfego que vai 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 por nome de serviço e tudo “simplesmente funciona”. Em outras palavras, nosso código emite consultas como http://api/v1/users/4242então api resolver solicitação de 10.97.105.48, as regras do iptables interceptarão as conexões de 10.97.105.48 e as encaminharão para o proxy Envoy local, e esse proxy local encaminhará a solicitação para a API de back-end real. Ufa!

Babados adicionais

O Istio também fornece criptografia e autenticação ponta a ponta via mTLS (TLS mútuo). Um componente chamado Citadela.

Há também um componente Mixer, que o Envoy pode solicitar cada solicitação para tomar uma decisão especial sobre essa solicitação, dependendo de vários fatores, como cabeçalhos, carga de back-end, etc... (não se preocupe: há muitas maneiras de manter o Mixer funcionando e, mesmo se ele travar, o Envoy continuará funcionando bem como um proxy).

E, claro, mencionamos a visibilidade: o Envoy coleta uma grande quantidade de métricas enquanto fornece rastreamento distribuído. Em uma arquitetura de microsserviços, se uma única solicitação de API precisar passar pelos microsserviços A, B, C e D, no login, o rastreamento distribuído adicionará um identificador exclusivo à solicitação e armazenará esse identificador por meio de subsolicitações para todos esses microsserviços, permitindo todas as chamadas relacionadas serão capturadas, atrasos, etc.

Desenvolva ou compre

O Istio tem a reputação de ser complexo. Por outro lado, construir a malha 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 (não precisamos de visibilidade, disjuntor e outras sutilezas), então pensamos em desenvolver nossa própria ferramenta. Mas se usarmos o Kubernetes, pode nem ser necessário porque o Kubernetes já fornece ferramentas básicas para descoberta de serviços e balanceamento de carga.

Mas se tivermos requisitos avançados, então “comprar” uma malha de serviço parece ser uma opção muito melhor. (Isso nem sempre é uma “compra” porque o Istio é de código aberto, mas ainda precisamos investir tempo de engenharia para entendê-lo, implantá-lo e gerenciá-lo.)

Devo escolher Istio, Linkerd ou Consul Connect?

Até agora falamos apenas do Istio, mas este não é o único service mesh. Alternativa popular - linkerd, e há mais Cônsul Conectar.

O que escolher?

Honestamente, não sei. Neste momento não me considero suficientemente competente para responder a esta pergunta. Existem alguns interessante artigos com uma comparação dessas ferramentas e até mesmo benchmarks.

Uma abordagem promissora é usar uma ferramenta como Supergloo. Implementa uma camada de abstração para simplificar e unificar as APIs expostas pelas malhas de serviço. Em vez de aprender APIs específicas (e, na minha opinião, relativamente complexas) de diferentes malhas de serviço, podemos usar construções mais simples do SuperGloo - e alternar facilmente de uma para outra, como se tivéssemos um formato de configuração intermediário descrevendo interfaces HTTP e back-ends capazes de de gerar a configuração real para Nginx, HAProxy, Traefik, Apache...

Eu me envolvi um pouco com Istio e SuperGloo, e no próximo artigo quero mostrar como adicionar Istio ou Linkerd a um cluster existente usando SuperGloo, e como este último faz o trabalho, ou seja, permite que você mude de uma malha de serviço para outra sem substituir configurações.

Fonte: habr.com

Adicionar um comentário