Consegui fazer o OpenTelemetry funcionar, mas por que foi tão complicado?
(iconsolutions.com)- O pedido de um cliente por suporte a OpenTelemetry revelou que a observabilidade existente baseada em Prometheus, Jaeger e OpenTracing não era totalmente igual às exigências de métricas, logs e traces do OTel
- Logs e métricas foram migrados com relativa facilidade, adicionando appender e configurações, mas o rastreamento distribuído foi muito mais difícil porque Spring e Akka precisavam compartilhar o contexto de trace dentro da mesma JVM
- OTel e Lightbend Telemetry/OpenTracing usavam APIs de Tracer e implementações de SpanContext diferentes, então a mesma transação acabava dividida em traces separados
- A solução foi injetar o contexto do OTel em um
MapJava, extrair como Jaeger SpanContext e então ativá-lo manualmente comGlobalExtendedTracer.get().local().activateContext()da Lightbend - O OTel ajuda a padronizar a observabilidade, mas ao misturá-lo com instrumentação legada baseada em Akka e Jaeger, pode ser necessário fazer uma ligação explícita de contexto
Os sinais de observabilidade que o OTel tenta integrar
- OpenTelemetry (OTel) é um framework e toolkit de observabilidade que busca padronizar em três sinais — métricas, logs e traces — áreas de ferramentas que antes ficavam separadas
- A combinação anterior era dividida por função
- Prometheus: métricas
- Agregadores centrais como Logstash e Elasticsearch: logs
- OpenTracing: rastreamento distribuído
- O OTel fornece não só especificações, mas também componentes centrais
- OpenTelemetry Protocol (OTLP): protocolo pelo qual aplicações reportam dados de telemetry
- OpenTelemetry Collector: componente neutro em relação a fornecedor para receber, processar e exportar dados de telemetry
- SDKs para mais de 10 linguagens: implementam OTLP e exportação de telemetry
- As aplicações podem enviar sinais ao OTel Collector por meio de instrumentação automática, APIs e SDKs de linguagem, e a infraestrutura também pode enviar sinais ao Collector
Suporte existente no IPF e pedido dos clientes
- O IPF já tinha integração com ELK Stack, monitoramento e documentação de observabilidade, e os clientes já usavam isso em ambientes de produção
- Como o framework não sabe em que ambiente serão implantadas as aplicações que usam a biblioteca, ele precisava fornecer abstrações e padrões sobrescrevíveis sem impor um fornecedor específico
- A recomendação padrão era enviar logs para agregadores como Elasticsearch e LogScale, e habilitar e dar suporte ao Prometheus, que se tornou o padrão de fato para métricas
- Os clientes pediram tracing baseado em OTel em três casos quase ao mesmo tempo, e junto com a adoção de tracing em OTel surgiu o movimento de migrar também logs e métricas
- Logs e métricas foram tratados com relativa facilidade por meio de novos appenders e configurações, mas o tracing permaneceu como um problema separado
Por que a propagação de contexto é necessária no rastreamento distribuído
- Para rastrear uma transação única em um sistema distribuído, é preciso transmitir entre sistemas informações que conectem chamadas específicas à transação; isso é chamado de context propagation
- Um trace é uma estrutura pai que envolve várias unidades de trabalho chamadas span, e pode haver vários spans aninhados
- Em um site de e-commerce, o fluxo de clicar no botão “buy now” pode ser agrupado em um único trace
- Um span da comunicação do frontend com o backend
- Spans filhos do backend ao se comunicar com serviços de pagamento, entrega e gestão de pedidos
- Spans filhos adicionais quando cada serviço downstream se comunica novamente com outros sistemas
- Ao usar OTel, cada serviço distribuído reporta sua parte do trace ao OTel Collector, e o OTel monta o fluxo completo sob um único trace ID
Quatro padrões de propagação de contexto e o legado do OpenTracing
- O OTel precisa oferecer suporte obrigatório a quatro implementações de context propagation
- A área de Akka do IPF já dava suporte ao OpenTracing, que está em processo de descontinuação, e usava o suporte a OpenTracing do Lightbend Telemetry
- O Lightbend Telemetry 2.20.0 adicionou suporte a logs/events e métricas do OpenTelemetry, mas faltava o importante suporte a tracing
- Em teoria, o tracing do OTel parece quase um rebranding do OpenTracing, e o OpenTracing do Lightbend Telemetry também suportava os quatro formatos de propagação, então parecia que funcionaria, mas o resultado real foi diferente
O trace quebrado entre Spring e Akka
- O IPF usa Spring e Akka juntos
- Spring Boot e Spring IoC: bootstrap da aplicação, configuração e composição de dependências
- Akka: event sourcing, agendamento, clustering, sharding, integração etc.
- O cliente pode iniciar o fluxo de pagamento por um controller REST do Spring ou por um método anotado com
@KafkaListener - Antes do OTel, as três áreas funcionavam sem grandes conflitos
- Métricas: Spring e Akka expunham endpoints Prometheus diferentes
- Logs: ambos os frameworks usavam SLF4J e Logback
- Traces: ambos usavam OpenTracing
- Com OTel, métricas e logs funcionavam de forma independente em Spring e Akka e eram enviados corretamente, mas o tracing precisava compartilhar o mesmo trace context dentro da JVM
- O problema real era que, dentro da aplicação, o mesmo conceito de rastreamento era representado por duas APIs diferentes, e essas APIs não se comunicavam entre si
- O comportamento correto seria o cliente HTTP do Akka reutilizar o trace ID existente
123e criar apenas um novo span ID, aparecendo como uma nova unidade de trabalho do mesmo trace, mas na prática eram gerados dois traces sem relação
opentracing-shim e conflito entre implementações
- Do lado do OTel existe o
opentracing-shim, que faz oTracerdo OTel parecer umTracerdo OpenTracing - Mas o Lightbend Telemetry usava uma implementação customizada de Tracer, e por isso tanto o shim quanto o Jaeger falhavam
- Os logs exibiam os seguintes erros
Expected to have an OpenTelemetry Span but got cinnamon.opentracing.TraceLocal$ContextOnlySpanExpected to have a JaegerSpanContext but got io.opentelemetry.opentracingshim.SpanContextShim
- O Jaeger recebia o contexto do shim do OTel e falhava, e o OTel recebia o contexto do Lightbend e também falhava
Investigação do Java Agent e do tracing interno do Lightbend
- Tanto a instrumentação do OTel quanto a do Lightbend Telemetry usam Java Agent para fazer hook em chamadas de métodos específicos de classes específicas e reportar atividades ao tracer
- Como a instrumentação do Lightbend Telemetry não é open source, foi necessário investigar código de tracer decompilado para entender o comportamento
- Foi criado um exemplo mínimo reproduzível para verificar em que ponto o trace context do OTel no lado do Spring se perdia ao passar para o lado do Akka
- O ponto em que o Jaeger falhava era este código em
JaegerTracer.java - O chamador era a classe de instrumentação proprietária
OpenTracingAkkaPersistenceActorInstrumentation.classdo Lightbend Telemetry - No código decompilado, a condição central era o estado de
var5- Se
var5, umSpanContextdo OpenTracing, não fosse null, o novo span seria ligado como filho do span ativo atual - Se
var5fosse null, ele se tornaria um span sem relação com o trace existente - Se
var5não fosse um Jaeger SpanContext, o Jaeger falharia
- Se
Ligando OTel e OpenTracing com conversão manual
- O
SpanContextera obtido dethis.traceLocal.currentContext(), etraceLocalera inicializado a partir doExtendedTracerdo Lightbend - A documentação do Lightbend mostrava uma forma de acessar o
ExtendedTracerglobalmenteGlobalExtendedTracer.get()
GlobalExtendedTracer.get()retorna umExtendedTracer, elocal()retorna o mesmoTraceLocalusado pela biblioteca de instrumentação- Esse
TraceLocaltem um métodoactivateContextque recebe umSpanContextdo OpenTracing - O fluxo da solução teve quatro etapas
- Não usar o shim do OTel
- Injetar o Context atual do OpenTelemetry em um
MapJava - Extrair um Jaeger SpanContext usando os valores desse
Map - Ativar o Jaeger SpanContext no
TraceLocaldo Lightbend antes de entrar no Akka
- A operação da API de propagators do OTel usada foi inject and extract operations
- O código central seguia este fluxo
- Criar um
HashMapvazio - Injetar o contexto OTel no map com
GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().inject(...) - Criar um JaegerSpanContext com
new TextMapCodec(false).extract(new TextMapAdapter(otelContext)) - Ativar o contexto com
GlobalExtendedTracer.get().local().activateContext(openTracingContext)antes de entrar no Akka
- Criar um
Comportamento confirmado e recomendação operacional
- Após a ligação manual, o trace passou a continuar como esperado
- Desde a chamada REST
/submitno Spring, todo o fluxo ficava conectado em um único trace - Era possível misturar partes instrumentadas com a API do OTel e partes instrumentadas com a API do OpenTracing
- O trace era propagado além da fronteira HTTP
- Desde a chamada REST
- No exemplo, nomes diferentes foram usados para distinguir os dois tipos de instrumentação, mas em ambiente real de cliente a recomendação é alinhar
otel.service.nameecinnamon.applicationpara que externamente tudo apareça como uma única aplicação
Por que foi tão complexo e quais preocupações restam
- Reescrever o Lightbend Telemetry com a API do OTel exigiria portar para a API do OTel muita instrumentação hoje acoplada à API do Jaeger, o que pode ser um trabalho grande
- O OTel Collector oferece suporte à coleta de traces no formato legado do Zipkin, então o Lightbend pode fornecer suporte completo a OTel apoiando-se nesse legado
- A causa direta da complexidade foi tentar combinar dois sistemas de instrumentação que usam bibliotecas de tracing diferentes
- O projeto OTel tenta organizar a área de observabilidade com esforços de padronização, como semantic conventions; embora seja um pouco complexo de entender no começo, é avaliado como um projeto FOSS útil
- Ainda resta a preocupação sobre se o Akka propaga corretamente o trace context entre threads dentro do modelo de actors
- Em pequenos testes de carga, funcionou como esperado
- Um ticket relacionado já foi aberto do lado do Akka
1 comentários
Comentários do Hacker News
Durante todo o tempo em que aprendi e portei o Otel, parecia que eu tinha voltado ao mundo Java. Cada linha de código que eu seguia parecia EnterpriseFizzBuzz, não tinha nenhuma descobribilidade, e a terminologia própria parecia ter sido criada por gente chapada em alguma coisa
No NodeJS, o uso de CPU era cerca de 4x maior que com StatsD, e no fim acabei criando agregações manualmente para reduzir o uso e também diminuir a explosão de tags. No StatsD, tudo bem se vários processos reportam a mesma tag, mas no OTEL ele sobrescreve
Em carga de pico, uma CPU ficava rodando com 60–80% de uso, e antes de alguma mudança nem dava para escalar verticalmente. Para linguagens que usam um processo por núcleo, o OTEL parece ativamente hostil, a ponto de virar piada. É melhor simplesmente usar Prometheus, e na prática nem existe outro concorrente
Tentei fazer uma configuração simples de otel em .NET, passei horas lendo a documentação do fornecedor escolhido pela empresa e ainda assim não entendi, então entrei no Discord que um colega administra. Parte do modelo de negócio deles era “se quiser usar otel direito em produto open source, pague”, e eu senti imediatamente que valeria a pena custasse o que custasse
Em vez de implementar OTEL, eu preferiria criar mais uma biblioteca estável de eventos/pub-sub sem experiência prévia
A configuração mínima também é realmente bem pequena, então basta a Web UI, uma instância do servidor e um banco de dados que você já conheça: https://skywalking.apache.org/docs/main/latest/en/setup/back...
Tem algo em comum com o Zabbix na área de monitoramento. Nenhum dos dois vai impressionar alguém, mas ambos são práticos o suficiente
process.vpid. Dá para coordenar isso com algo como um objeto global para que o valor seja único enquanto o app estiver vivoDepois é só ter alguma coisa que some tudo e remova esse atributo. Em statsd/delta, perder uma transmissão distorce os dados inteiros; no modelo cumulativo, você só perde precisão
No meu caso de uso, são métricas push vindas de ferramentas “batch”, não processos de longa duração que podem ser raspados
O motivo de o Otel parecer complexo é que vários fornecedores de observabilidade tornaram a implementação de observabilidade muito fácil com seus SDKs, agentes e APIs proprietários. O Otel tenta resolver esse problema, e acho que quem o criou está fazendo isso muito bem
Também é louvável que a Grafana tenha adotado o OpenTelemetry como componente de primeira classe do ecossistema
Durante anos eu defendi o uso de Datadog, mas para empresas entre médio e grande porte o preço ficou inviável. Com o tempo, conforme a API e o SDK do OpenTelemetry foram amadurecendo, ele acabou virando o padrão de observabilidade de aplicações
Ainda assim, a documentação como um todo poderia ser melhor, e não é ideal que os documentos de onboarding variem entre linguagens
Hoje a equipe usa uma stack NodeJS/Typescript, e criamos um pacote com bundles e uma stack de exemplo do Grafana para começar rapidamente com OpenTelemetry: https://github.com/zonneplan/open-telemetry-js
O maior problema com qualquer fornecedor de APM é que, no momento em que você ganha hooks mágicos no kernel por meio de um agente, começa a acontecer todo tipo de coisa que o desenvolvedor não consegue explicar
Em outra empresa no passado, adotamos Dynatrace e, felizmente, o app já tinha métricas embutidas suficientes para que o SRE líder considerasse a instrumentação um “exemplo” do que fazer. Mas, no momento em que instalaram o agente do Dynatrace no host da aplicação, surgiram vários heisenbugs que exigiam reiniciar o nó, e a degradação de desempenho também foi medida diretamente
Ironicamente, as métricas evitaram mais sofrimento, mas ninguém sabia como corrigir. O pior caso era quando uma atualização do MSSQL fazia o failover corromper de forma estranha o pool de conexões do ADO.NET
É uma estrutura que fica complexa só na medida do necessário. Dá para evitar recursos “mágicos” e usar apenas o subconjunto que faz mais sentido no contexto e é mais fácil de entender
No nosso time, é bem simples. Usamos uma biblioteca que só envia traces, e traces dão o maior valor para observabilidade da aplicação, além de também poderem carregar outros tipos de dados. Basicamente, é como usar um hashmap em vez de strings e números de ponto flutuante
Em vez de instrumentação automática, usamos instrumentação manual, escolhemos de forma intencional o que observar e entendemos bem que código emite spans. Também temos convenções de nomenclatura alinhadas com a estrutura do código
No backend, usamos uma combinação de um serviço de terceiros barato e uma instalação local do Jaeger all-in-one para desenvolvimento. Este último exige apenas um executável ou um contêiner Docker e não grava spans em disco. Serve principalmente para tranquilizar a equipe de que não vai lotar o serviço de terceiros
Já temos uma configuração existente para monitoramento de infraestrutura e, no nosso caso, não vemos muito valor em coletar todos os logs e métricas da infraestrutura. Acho que métricas e logs do OTEL ainda estão em estágio inicial, embora os vendors não digam isso
Ainda estou procurando um endpoint para enviar métricas simples e pontuais de uma parte da infraestrutura que ainda não pode ser scrapeada
O Otel pode ser bom em um projeto novo, mas ativá-lo em um serviço de produção que já tem telemetria foi como trocar o pneu de um carro em movimento
Acredito que isso pode ser bom em um projeto greenfield usando apenas otel e sem frameworks não-otel. Mas eu ainda não vivo nesse mundo
Um dos maiores problemas foi a experiência de desenvolvimento local. Eu queria suporte a logs, traces e métricas localmente, mas não queria subir um monte de imagens Docker para isso. Queria poder verificar nos logs como métricas, traces, baggage e activity spans aparecem antes do deploy
Recentemente a equipe do .NET lançou o .NET Aspire, e ele é muito bom. Fica fácil visualizar tudo em um só lugar na stack local de desenvolvimento, e ele funciona como um orquestrador em código
Ao fazer deploy no k8s, basta apontar o endpoint do OTEL para o DataDog Agent e tudo simplesmente funciona. Evitamos as bibliotecas e SDKs proprietários de trace do DataDog e usamos só OTEL
Agora a experiência de desenvolvimento está bem boa
https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals...
https://docs.datadoghq.com/opentelemetry/#overview
Leva 5 minutos para configurar na máquina local de desenvolvimento na primeira vez, e depois disso basta executar
/path/to/openobserveem uma aba separada do terminal. Se você quiser evitar a enorme complexidade de um binário estático com tudo linkado, eles também oferecem imagens Docker para execução local e remota :PÉ um backend all-in-one de OpenTelemetry com gráficos bonitos, e nos meus projetos ele ainda não falhou de forma perceptível
Ao usar Aspire, você adiciona complexidade desnecessária no nível da aplicação e fica preso a um ecossistema limitado. Para desenvolvimento local já existem alternativas consolidadas, como docker compose, e o Aspire também não é exatamente muito mais fácil do que docker compose e variáveis de ambiente
Se você vai usar otel em Python, é melhor usar o cliente do Logfire. Isso vale mesmo que você não use o serviço Logfire
É open source e pode enviar para qualquer endpoint compatível com otel. Além disso, o cliente feito pelo time do pydantic é umas 10 vezes melhor e mais simples do que a biblioteca oficial do otel
Também há uma entrevista interessante em que Samuel Colvin explica como chegou até aqui: https://www.bitecode.dev/p/samuel-colvin-on-logfire-mixing-p...
Por isso, começamos um projeto open source focado em tornar a adoção do OpenTelemetry tão fácil quanto uma única linha de comando: https://github.com/odigos-io/odigos
Hoje em dia, muitos frameworks web fazem a maior parte da instrumentação por você. Por exemplo, usando
opentelemetry-jse hospedando você mesmo algo como https://signoz.io, dá para colocar no ar em menos de uma hora e obter muitos dados sem escrever código customizadoO OpenTelemetry cresceu a partir de traces, mas para métricas e logs é muito melhor deixar isso para soluções especializadas
Parece um problema de “abstração vazando” ou “framework vazando”. Se você quiser colocar tudo debaixo do mesmo guarda-chuva, um banco de dados SQL também poderia fazer tudo isso ao mesmo tempo. Isso não significa que deva
https://cra.mr/the-problem-with-otel/
Mesmo assim, ainda odeio lidar com OTel toda vez que preciso mexer nele
Se você ler até o fim, dá para ver que a maior parte da dor foi criada por ele mesmo. Na stack padrão do Python (
mysql,flask,redis,requestsetc.), foi muito fácil. Bastou colocar alguns imports no topo do serviço, e tudo se conectou automaticamente para rastrear tudo sem alardeaiohttpestá na versão 3.11.X, e a instrumentação automática diz suportar3.X, mas [0] na prática o resultado depende de quão nova é a versão deaiohttpque você usa e do estado da instrumentação automáticaQuando tudo encaixa, funciona como mágica, mas esse buraco de agulha é bem estreito
[0]: https://github.com/open-telemetry/opentelemetry-python-contr...
otel-collector-contrib,jaeger-all-in-oneeprometheusem um único servidor com docker composeOs traces funcionam, também configurei o spanmetrics exporter, e quando consulto o prometheus diretamente, os spanmetrics aparecem. Mas, por mais que eu tente, eles não aparecem na aba “monitor” do jaeger
Depois de gastar 3 dias nisso, meu chefe perguntou: “por que não fazer logo a instrumentação manual, mandar tudo para o servidor SQL e criar um dashboard no Grafana?”, mas eu também não quero fazer isso
É o caso de uso mais simples possível, e mesmo assim não funciona. Será que preciso colocar o Grafana por cima também?
gunicorn. Nesse momento, todos os imports automáticos param de funcionar, e você precisa fazer tudo manualmenteO motivo de ser complexo não é por causa dos engenheiros que o implementam, mas porque foi projetado para empresas que vendem software compatível com OTel
Na verdade, acho que os backends demoraram bastante para adotar isso
Os significados mudam, então tudo bem, mas é engraçado que agora tenha desaparecido a palavra para escrever software conforme uma especificação e tenha restado apenas “implantar software existente em um servidor”