À medida que passamos de uma aplicação monolítica para uma arquitetura de microsserviços, enfrentamos novos desafios.
Em uma aplicação monolítica, geralmente é bastante fácil determinar em qual parte do sistema ocorreu o erro. Muito provavelmente, o problema está no código do próprio monólito ou no banco de dados. Mas quando começamos a procurar um problema numa arquitetura de microsserviços, tudo já não é tão óbvio. Precisamos encontrar todo o caminho que a solicitação percorreu do início ao fim e selecioná-lo entre centenas de microsserviços. Além disso, muitos deles também possuem instalações de armazenamento próprias, o que também pode causar erros lógicos, bem como problemas de desempenho e tolerância a falhas.
Há muito tempo que procuro uma ferramenta que me ajude a lidar com esses problemas (escrevi sobre isso no Habré:
O rastreamento distribuído é uma solução comum para o problema de localização de erros em sistemas distribuídos. Mas e se esta abordagem de recolha de informação sobre interacções de rede ainda não tiver sido implementada no sistema, ou, pior, em parte do sistema já funciona correctamente, mas em parte não, uma vez que não foi adicionada a serviços antigos ? Para determinar a causa raiz exata de um problema, é necessário ter uma visão completa do que está acontecendo no sistema. É especialmente importante compreender quais microsserviços estão envolvidos nos principais caminhos críticos para os negócios.
Aqui a abordagem da malha de serviços pode vir em nosso auxílio, que lidará com todo o mecanismo de coleta de informações da rede em um nível inferior ao dos próprios serviços. Essa abordagem nos permite interceptar todo o tráfego e analisá-lo em tempo real. Além disso, os aplicativos nem precisam saber nada sobre isso.
Abordagem de malha de serviço
A ideia principal da abordagem service mesh é adicionar outra camada de infraestrutura à rede, o que nos permitirá fazer qualquer coisa com a interação entre serviços. A maioria das implementações funciona da seguinte maneira: um contêiner secundário adicional com um proxy transparente é adicionado a cada microsserviço, através do qual todo o tráfego de entrada e saída do serviço é passado. E é aqui que podemos fazer o balanceamento de clientes, aplicar políticas de segurança, impor restrições ao número de solicitações e coletar informações importantes sobre a interação dos serviços em produção.
Soluções
Já existem várias implementações desta abordagem:
Como resultado, analisamos exatamente quais recursos precisávamos no momento e decidimos que o principal motivo pelo qual começamos a implementar essas soluções era a capacidade de coletar informações de rastreamento de todo o sistema de forma transparente. Queríamos também ter controle sobre a interação dos serviços e fazer diversas manipulações com os cabeçalhos que são transferidos entre os serviços.
Como resultado, chegamos à nossa decisão:
Netramesh
Os principais objetivos da nova solução eram baixa sobrecarga de recursos e alto desempenho. Entre os principais recursos, queríamos imediatamente poder enviar spans de rastreamento de forma transparente para nosso sistema Jaeger.
Hoje, a maioria das soluções em nuvem são implementadas em Golang. E, claro, existem razões para isso. Escrever aplicativos de rede em Golang que funcionam de forma assíncrona com E/S e escalam entre núcleos conforme necessário é conveniente e bastante simples. E, o que também é muito importante, o desempenho é suficiente para resolver este problema. É por isso que também escolhemos Golang.
Desempenho
Concentramos nossos esforços em alcançar a máxima produtividade. Para uma solução implantada próxima a cada instância do serviço, é necessário um pequeno consumo de RAM e tempo de CPU. E, claro, o atraso na resposta também deve ser curto.
Vamos ver quais resultados obtivemos.
RAM
Netramesh consome aproximadamente 10 MB sem tráfego e no máximo 50 MB com carga de até 10000 RPS por instância.
O proxy enviado do Istio sempre consome aproximadamente 300 MB em nossos clusters com milhares de instâncias. Isso não permite que ele seja dimensionado para todo o cluster.
Com Netramesh obtivemos uma redução de aproximadamente 10x no consumo de memória.
CPU
O uso da CPU é relativamente igual sob carga. Depende do número de solicitações por unidade de tempo para o sidecar. Valores em 3000 solicitações por segundo no pico:
Há mais um ponto importante: Netramesh - uma solução sem plano de controle e sem carga não consome tempo de CPU. Com o Istio, os sidecars sempre atualizam os endpoints de serviço. Como resultado, podemos ver esta imagem sem carga:
Usamos HTTP/1 para comunicação entre serviços. O aumento no tempo de resposta do Istio ao fazer proxy por meio do envoy foi de 5 a 10 ms, o que é bastante para serviços que estão prontos para responder em um milissegundo. Com Netramesh, esse tempo diminuiu para 0.5-2ms.
Escalabilidade
A pequena quantidade de recursos consumidos por cada proxy permite colocá-lo próximo a cada serviço. Netramesh foi criado intencionalmente sem um componente de plano de controle para simplesmente manter cada sidecar leve. Freqüentemente, em soluções de malha de serviço, o plano de controle distribui informações de descoberta de serviço para cada sidecar. Junto com ele vêm informações sobre tempos limite e configurações de balanceamento. Tudo isso permite que você faça muitas coisas úteis, mas, infelizmente, aumenta o tamanho dos carros laterais.
Descoberta de serviço
Netramesh não adiciona nenhum mecanismo adicional para descoberta de serviço. Todo o tráfego é proxy de forma transparente por meio do netra sidecar.
Netramesh suporta protocolo de aplicativo HTTP/1. Para defini-lo, é utilizada uma lista configurável de portas. Normalmente, o sistema possui várias portas através das quais ocorre a comunicação HTTP. Por exemplo, usamos 80, 8890, 8080 para interação entre serviços e solicitações externas. Neste caso, eles podem ser definidos usando uma variável de ambiente NETRA_HTTP_PORTS
.
Se você usar o Kubernetes como orquestrador e seu mecanismo de entidade de serviço para comunicação intracluster entre serviços, o mecanismo permanecerá exatamente o mesmo. Primeiro, o microsserviço obtém um endereço IP de serviço usando kube-dns e abre uma nova conexão com ele. Esta conexão é estabelecida primeiro com o netra-sidecar local e todos os pacotes TCP chegam inicialmente ao netra. Em seguida, o netra-sidecar estabelece uma conexão com o destino original. O NAT no pod IP no nó permanece exatamente o mesmo que sem netra.
Rastreamento distribuído e encaminhamento de contexto
Netramesh fornece a funcionalidade necessária para enviar intervalos de rastreamento sobre interações HTTP. Netra-sidecar analisa o protocolo HTTP, mede atrasos nas solicitações e extrai as informações necessárias dos cabeçalhos HTTP. Em última análise, obtemos todos os rastros em um único sistema Jaeger. Para uma configuração refinada, você também pode usar as variáveis de ambiente fornecidas pela biblioteca oficial
Mas há um problema. Até que os serviços gerem e enviem um cabeçalho uber especial, não veremos intervalos de rastreamento conectados no sistema. E é disso que precisamos para encontrar rapidamente a causa dos problemas. Aqui, novamente, Netramesh tem uma solução. Os proxies leem cabeçalhos HTTP e, se não contiverem o ID do uber trace, geram um. Netramesh também armazena informações sobre solicitações de entrada e saída em um arquivo secundário e as combina, enriquecendo-as com os cabeçalhos de solicitação de saída necessários. Tudo que você precisa fazer nos serviços é enviar apenas um cabeçalho X-Request-Id
, que pode ser configurado usando uma variável de ambiente NETRA_HTTP_REQUEST_ID_HEADER_NAME
. Para controlar o tamanho do contexto no Netramesh, você pode definir as seguintes variáveis de ambiente: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS
(o tempo durante o qual o contexto será armazenado) e NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL
(frequência de limpeza de contexto).
Também é possível combinar vários caminhos no seu sistema marcando-os com um token de sessão especial. Netra permite que você instale HTTP_HEADER_TAG_MAP
para transformar cabeçalhos HTTP em tags de span de rastreamento correspondentes. Isso pode ser especialmente útil para testes. Depois de passar no teste funcional, você poderá ver qual parte do sistema foi afetada pela filtragem pela chave de sessão correspondente.
Determinando a origem da solicitação
Para determinar de onde veio a solicitação, você pode usar a funcionalidade de adicionar automaticamente um cabeçalho com a origem. Usando uma variável de ambiente NETRA_HTTP_X_SOURCE_HEADER_NAME
Você pode especificar um nome de cabeçalho que será instalado automaticamente. Usando NETRA_HTTP_X_SOURCE_VALUE
você pode definir o valor para o qual o cabeçalho X-Source será definido para todas as solicitações de saída.
Isto permite que a distribuição deste cabeçalho útil seja distribuída uniformemente por toda a rede. Depois, você pode usá-lo em serviços e adicioná-lo a logs e métricas.
Roteamento de tráfego e componentes internos do Netramesh
Netramesh consiste em dois componentes principais. O primeiro, netra-init, define regras de rede para interceptar o tráfego. Ele usa INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS
.
A ferramenta também possui um recurso interessante - roteamento probabilístico. Se você usar o Netramesh exclusivamente para coletar intervalos de rastreamento, em um ambiente de produção você poderá economizar recursos e ativar o roteamento probabilístico usando variáveis NETRA_INBOUND_PROBABILITY
и NETRA_OUTBOUND_PROBABILITY
(de 0 a 1). O valor padrão é 1 (todo o tráfego é interceptado).
Após a interceptação bem-sucedida, o netra sidecar aceita a nova conexão e usa SO_ORIGINAL_DST
opção de soquete para obter o destino original. O Netra então abre uma nova conexão com o endereço IP original e estabelece comunicação TCP bidirecional entre as partes, ouvindo todo o tráfego que passa. Se a porta for definida como HTTP, o Netra tentará analisá-la e rastreá-la. Se a análise HTTP falhar, o Netra recorre ao TCP e faz proxy transparente dos bytes.
Construindo um gráfico de dependência
Depois de receber uma grande quantidade de informações de rastreamento no Jaeger, desejo obter um gráfico completo das interações no sistema. Mas se o seu sistema estiver bastante carregado e bilhões de intervalos de rastreamento se acumularem por dia, agregá-los não será uma tarefa tão fácil. Existe uma maneira oficial de fazer isso:
Se você estiver usando o Elasticsearch para armazenar intervalos de rastreamento, poderá usar
Como usar o Netramesh
O Netra pode ser facilmente adicionado a qualquer serviço executando qualquer orquestrador. Você pode ver um exemplo
No momento, a Netra não tem a capacidade de implementar automaticamente sidecars nos serviços, mas há planos para implementação.
O futuro do Netramesh
Objetivo principal
No futuro, o Netramesh oferecerá suporte a outros protocolos da camada de aplicação além do HTTP. O roteamento L7 estará disponível em um futuro próximo.
Use o Netramesh se encontrar problemas semelhantes e escreva-nos com perguntas e sugestões.
Fonte: habr.com