OpenTelemetry na nova plataforma de Integração
No mundo da computação distribuída, uma das grandes preocupações que aflige profissionais de desenvolvimento e operações são as famosas lentidões. Numa arquitetura de sistemas monolíticos, isso pode não ser um grande problema, pois basta analisar os logs de determinada aplicação e suas saídas de bordas e facilmente será identificada uma query ou um serviço que estão com um elevado tempo de resposta. Porém, falando de uma arquitetura de microsserviços, a complexidade aumenta exponencialmente a medida que nós são adicionados nessa comunicação, por isso o ideal é que toda e qualquer integração já nasça com meios para rastrear requisições, tornando assim o dia dia dos times de desenvolvimento e operação mais tranquilos. Se baseando um pouco na cultura devops, nos “princípios das três maneiras”, mais especificamente na “segunda maneira”, na qual se incentiva o exercício do feedback rápido, a OpenTelemetry nos ajuda a resolver toda essa questão dentro de um ecossistema heterogêneo, fornecendo meios e padrões que possibilitam a correta interpretação dos tracers dentre diferentes tecnologias.
O que é OpenTelemetry?
Basicamente, Opentelemetry é a junção dos projetos OpenTracing e o OpenCensus, o primeiro focado em trace distribuído e o segundo em métricas. O projeto visa tornar toda a observabilidade das aplicações agnósticas aos vendors do mercado, evitando assim o lock in com o fornecedor, ou seja, qualquer implementação será interpretada pelos grandes provedores de nuvens e pelos principais fornecedores dos Applications Performance Management (APM).
Alguns termos da OpenTelemetry
- Span: Quando visualizamos tracers em algum APM, o span é representado visualmente pela “barrinha”, que ilustra a determinada operação dentro de uma transação;
- ParentSpan: assim como a própria tradução deixa explícito, é um span “pai”, e como todo pai tem um filho, o parent span é a operação que origina uma outra operação dependente, por exemplo: a ação Consultar Endereço é pai da operação Chamar serviço de CEP.
- ChildSpan: span filho; conforme o exemplo acima é a própria operação Chamar serviço de CEP.
- SpanContext: podemos dizer que é uma carga de informações pertinentes ao contexto do trace que deverá ser propagado entre os componentes de integração.
No SpanContext podemos encontrar as seguintes propriedades:
- TraceId: identificador gerado randomicamente, podendo ser binário, contendo 16 bits, ou no nosso caso um hexadecimal de 32 bits, que identifica o trace de forma global associado à toda a transação.
- SpanId: assim como o TraceId, trata-se de um identificador randomicamente gerado, podendo ser binário de 8 bits ou no nosso caso um hexadecimal de 16 bits, que identifica a operação.
- TraceFlags: é um valor binário 0 ou 1 que sinaliza se o trace deve ser enviado ao APM ou não.
- Tracestate: carrega informações adicionais do rastreador. São metadados que podem ser injetados pelos desenvolvedores para melhorar a carga de informação.
Contexto de Propagação
Para a realizar a propagação do SpanContext entre os nós da integração, utilizamos dois modelos:
Ambos são suportados pelo projeto OpenTelemetry e orientados aos headers das requisições, ou seja, aproveitam a “carona” ou fluxo da request para realizar a propagação através dos seus headers.
O OpenTelemetry Propagator B3 é utilizado na primeira metade da integração, onde foi instrumentado com o auxílio do conector do Zipkin. Já o padrão W3C está embutido nas bibliotecas do projetos Opentracing e está sendo usado dentro dos microsserviços.
OpenTelemetry Propagator B3
- x-b3-traceid: como já mencionado acima, consiste no identificador global do trace, ao qual todo span criado estará atrelado ao traceId.
- x-b3-parentSpanId: identificador do span pai.
- x-b3-spanid: é o identificador da operação corrente.
- X-B3-Sampled: é o TraceFlag, que sinaliza se o trace em questão deve ser enviado ou não para o Cloud Trace.
W3C
- Traceparent: concatenação de informações de TraceId, SpanId e Sampled.
Contextualização
Na nova plataforma de integração do Grupo Boticário está sendo seguida a seguinte arquitetura:
API Management -> Camada de Proxy Reverso -> Microservices -> Legados.
Utilizamos o Google Cloud Trace para visualização dos tracers distribuídos, primeiramente por ser compatível ao Opentelemetry, por não ter necessidade do provisionamento de nenhuma infraestrutura, por suportar uma larga escala de tecnologias e por oferecer conectividade quase que plug and play com um coletor zipkin, o que se torna uma carta na manga para cenários atípicos de integração que necessitem de coleta de tracers.
Como visto anteriormente, estão sendo tratados quatro componentes de integração distintos e sem um padrão fortemente definido. Seria um tarefa dolorosa propagar qualquer tipo de tracer nessas condições, por isso o OpenTelemetry veio para domesticar essa complexidade. Porém, vale lembrar que o projeto fornece bibliotecas para diversas linguagens e tecnologias, mas foi necessário enfrentar o desafio de instrumentalizar as camadas do API Management e Proxy Reverso. A causa é justamente a falta de artefatos e bibliotecas para esses dois componentes, lembrando que o OpenTelemetry está na versão 1.0.0 e ainda é uma criança com um potencial enorme de crescimento, mas nem tudo é problema, pois como os padrões já estão definido pelo projeto, foi possível customizar a solução.
API Management (AM)
Como já salientado anteriormente, a instrumentalização do API Management (AM) foi um desafio, pois até a versão 1.0.0 da Opentelemetry não há nada relacionado aos AM. Porém, foram introduzidos manualmente os padrões da Opentelemetry em um fluxo compartilhado que é executado “por baixo dos panos” via Flow Hooks (que se caracteriza pela sua execução em background quando qualquer outro proxy é invocado). Nesse fluxo há uma policy com código Javascript responsável por gerar o identificador único do trace (TraceId), o identificador do span (SpanId) e a condição se o trace deve ser renderizado ou não na Cloud Trace(sampled). Assim, esses valores são injetados no header da requisição que vai pro Reverse Proxy, e após o retorno do response é montado o seu span context, que será enviado através da API administrativa da própria GCP para o Cloud Trace.
Reverse Proxy (RP)
Quando o AM envia os headers contendo os campos definidos pelo Propagator B3, o reverse proxy, que outrora foi fortalecido com bibliotecas contendo módulos opentracing em suas configurações, coleta os campos recebidos do AM. Especificamente no x-b3-spanid acontece uma inteligência, pois ele copia o valor desse campo e adiciona em um novo campo chamado x-b3-parentSpanId, e após isso é gerado automaticamente um novo valor para o campo x-b3-spanid . Dessa forma se cria um elo entre os spans pai e filho.
A partir desse ponto é utilizado o modo de propagação W3C, que injeta no header da requisição um novo parâmetro chamado, agora, de traceparent. O valor desse novo campo é montado conforme abaixo:
Quando o fluxo de response chega até o reverse proxy, os módulos opentracing são encarregados de enviar os tracers em background para um connector do Zipkin, pois esses módulos não conseguem conectividade direta com o Cloud Trace. Por isso, há a necessidade de utilizar um conector Zipkin no meio do caminho que concentra em si as configurações necessárias para se conectar na GCP via services accounts, e assim, propagar os traces gerados no RP para o Cloud Trace via GRPC.
Microsserviços
Como os microsserviços são desenvolvidos em linguagens suportadas pela Opentelemetry, as bibliotecas já conseguem decifrar o header traceparent oriundo do RP, e assim acontece a lógica descrita acima, que cria um elo de spans pai e filho, e a própria lib se encarrega de gerar um novo identificador como spanId.
O grande embate que gira em torno da adoção do opentracing nos códigos de programação é a quantidade de linhas necessárias para instrumentalizar os microsserviços. Para diminuir significativamente esse número de código, foram encapsuladas as libs do http e tracer em uma única lib. Dessa maneira, em toda e qualquer chamada http implicitamente é aberto um novo childSpan para mensurar o tempo de resposta; porém, em chamadas de legados que se utilizam de outros protocolos, está sendo realizada a instrumentação manual pelo desenvolvedor.
Vale lembrar que o envio dos tracers da GCP acontece de forma assíncrona, ou seja, cada nó é responsável pelo seu envio. O próprio Cloud Trace consegue montar a árvore de trace interpretando o encadeamento de spans e seus relacionamentos (pais e filho).
E seguindo todas essas estratégias, conseguimos ter um rastreamento ponta à ponta do fluxo de integração, identificando gargalos e lentidões, com possibilidade de controlar spans gravados através de políticas de sampling definido no início do fluxo.
Abaixo segue um exemplo de amostragem.