4 pontos por GN⁺ 2025-01-11 | 1 comentários | Compartilhar no WhatsApp
  • 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 Map Java, extrair como Jaeger SpanContext e então ativá-lo manualmente com GlobalExtendedTracer.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 123 e 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 o Tracer do OTel parecer um Tracer do 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$ContextOnlySpan
    • Expected 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.class do Lightbend Telemetry
  • No código decompilado, a condição central era o estado de var5
    • Se var5, um SpanContext do OpenTracing, não fosse null, o novo span seria ligado como filho do span ativo atual
    • Se var5 fosse null, ele se tornaria um span sem relação com o trace existente
    • Se var5 não fosse um Jaeger SpanContext, o Jaeger falharia

Ligando OTel e OpenTracing com conversão manual

  • O SpanContext era obtido de this.traceLocal.currentContext(), e traceLocal era inicializado a partir do ExtendedTracer do Lightbend
  • A documentação do Lightbend mostrava uma forma de acessar o ExtendedTracer globalmente
    • GlobalExtendedTracer.get()
  • GlobalExtendedTracer.get() retorna um ExtendedTracer, e local() retorna o mesmo TraceLocal usado pela biblioteca de instrumentação
  • Esse TraceLocal tem um método activateContext que recebe um SpanContext do OpenTracing
  • O fluxo da solução teve quatro etapas
    • Não usar o shim do OTel
    • Injetar o Context atual do OpenTelemetry em um Map Java
    • Extrair um Jaeger SpanContext usando os valores desse Map
    • Ativar o Jaeger SpanContext no TraceLocal do 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 HashMap vazio
    • 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

Comportamento confirmado e recomendação operacional

  • Após a ligação manual, o trace passou a continuar como esperado
    • Desde a chamada REST /submit no 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
  • 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.name e cinnamon.application para 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

 
GN⁺ 2025-01-11
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

    • Vejo o OTEL como algo próximo de uma forma de captura por fornecedores. Para criar um padrão, não havia alternativa além de colar tudo com fita e cola, conciliando as exigências de várias big techs e startups desleixadas
      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
    • Sobre “nem existe outro concorrente”, dependendo do caso vale olhar o Apache SkyWalking. Ele não consome muitos recursos e a configuração e operação são bem simples, mas a UI e a documentação são menos polidas: https://skywalking.apache.org/
      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
    • Minha conclusão é a mesma. Basta usar Prometheus com a biblioteca cliente da linguagem que você quiser, e é 1000x mais simples do que qualquer conversa sobre OTEL
    • Estou usando otel em C++, e para obter métricas cumulativas de vários aplicativos criei atributos inteiros de cardinalidade relativamente baixa, como process.vpid. Dá para coordenar isso com algo como um objeto global para que o valor seja único enquanto o app estiver vivo
      Depois é 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
    • Isso bate com a minha experiência também. Foi muito difícil entender o que era necessário para obter o efeito desejado
  • 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

    • Então esse produto resolve um problema criado pelo fato de que todo mundo deixou tudo muito fácil, tornando tudo complexo? ;P
    • Eu ainda prefiro uma boa coleta de métricas a Datadog. Por outro lado, entendo em teoria que o OTEL tem valor acima da abordagem que eu prefiro
      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

    • Pode ficar tão complexo quanto você quiser, mas não é tão fácil quanto eu gostaria. O nível mínimo de dificuldade é bem alto
      Ainda estou procurando um endpoint para enviar métricas simples e pontuais de uma parte da infraestrutura que ainda não pode ser scrapeada
    • Não senti que a instrumentação manual fosse mais simples. É mais uma troca em que a curva de aprendizado começa muito antes de você conseguir mostrar resultados, mas em compensação você passa a entender melhor o custo de performance gerado por essa máquina de Rube Goldberg
      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
    • Você pode compartilhar qual é esse serviço de terceiros barato?
    • É um conselho muito sensato. A maioria já deve ter alguma coisa para métricas e logs, e se não houver retorno sobre o investimento em trocar, não há motivo para fazer isso
    • “Evitar a mágica e usar só um subconjunto” só funciona quando todas as bibliotecas que você usa também usam apenas esse subconjunto. Na minha experiência, isso esmagadoramente não acontece, e o texto mostra bons exemplos concretos do porquê
      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

    • Para esse caso de uso, este projeto é realmente muito bom: https://github.com/grafana/docker-otel-lgtm
    • Também existe https://github.com/CtrlSpice/otel-desktop-viewer
    • É só usar https://github.com/openobserve/openobserve
      Leva 5 minutos para configurar na máquina local de desenvolvimento na primeira vez, e depois disso basta executar /path/to/openobserve em 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
    • O .NET Aspire não me convence. Ele resolve um problema pequeno — service discovery e orquestração de vários projetos de serviço no desenvolvimento local — transformando isso em uma preocupação em nível de aplicação
      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
    • Existe uma imagem Docker oficial all-in-one com tudo incluído
  • 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-js e 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 customizado

    • O repositório do SigNoz está aqui: https://github.com/signoz/signoz
    • Em runtimes assíncronos multithread, a propagação de contexto não é simples. Existem várias formas de fazer isso, mas o motivo de os agentes JVM que instrumentam bytecode serem populares é que funcionam de forma transparente
  • O 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

    • Cramer quer tirar os traces do OTel. É irônico, considerando que ele é um dos criadores do OpenTracing
      https://cra.mr/the-problem-with-otel/
    • Acho realmente útil dar a métricas e logs uma posição dentro do trace
      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, requests etc.), foi muito fácil. Bastou colocar alguns imports no topo do serviço, e tudo se conectou automaticamente para rastrear tudo sem alarde

    • Sim, mas isso só vale quando tudo na stack oferece suporte à instrumentação automática. Por exemplo, aiohttp está na versão 3.11.X, e a instrumentação automática diz suportar 3.X, mas [0] na prática o resultado depende de quão nova é a versão de aiohttp que você usa e do estado da instrumentação automática
      Quando tudo encaixa, funciona como mágica, mas esse buraco de agulha é bem estreito
      [0]: https://github.com/open-telemetry/opentelemetry-python-contr...
    • Recentemente tive que integrar isso a um app Flask bem simples. Como ele só pode rodar dentro da intranet da empresa, estou executando otel-collector-contrib, jaeger-all-in-one e prometheus em um único servidor com docker compose
      Os 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?
    • Isso só vale até você colocar o servidor atrás de algo como gunicorn. Nesse momento, todos os imports automáticos param de funcionar, e você precisa fazer tudo manualmente
  • O 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

    • Não sei se concordo. Os vendors provavelmente estavam satisfeitos com seu próprio código, seus agentes e seus backends. Por causa do lock-in, o custo de migração ficava muito alto, já que era preciso reescrever todo o código novo
    • Pelo que vi dos contribuidores, não parecia ser esse o caso
      Na verdade, acho que os backends demoraram bastante para adotar isso
    • Pode soar pedante, mas antigamente ‘implement’ se referia à primeira atividade, isto é, sentar e escrever software conforme uma especificação. Não significava, como no segundo caso, pegar software existente e implantá-lo em um servidor
      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”