OpenTelemetry na nova plataforma de Integração

Guilherme Marcelino
gb.tech
Published in
6 min readAug 17, 2021
Telescópio metálico apontando para cima e paisagem pouco nítida ao fundo
Telescópio metálico apontando para cima e paisagem pouco nítida ao fundo | Foto de Matthew Ansley na Unsplash

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.

Demonstração da criação de um relacionamento entre um span e seu pai no contexto do OpenTelemetry.
Demonstração da criação de um relacionamento entre um span e seu pai no contexto do OpenTelemetry.

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:

Representação do header de propagação interpretado entre o B3 Propagation e W3C
Representação do header de propagação interpretado entre o B3 Propagation e W3C

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.

--

--

Guilherme Marcelino
gb.tech
Writer for

Software architect, Microservice specialist and Isabella’s Father