4 puntos por GN⁺ 2025-01-11 | 1 comentarios | Compartir por WhatsApp
  • La solicitud de un cliente para soporte de OpenTelemetry dejó en evidencia que la observabilidad existente basada en Prometheus, Jaeger y OpenTracing no era exactamente igual a los requisitos de métricas, logs y trazas de OTel
  • Los logs y las métricas se migraron con relativa facilidad agregando un appender y configuración, pero el tracing distribuido fue mucho más difícil porque Spring y Akka debían compartir el contexto de tracing dentro de la misma JVM
  • OTel y Lightbend Telemetry/OpenTracing usan distintas API de Tracer e implementaciones de SpanContext, por lo que una misma transacción se dividía en trazas separadas
  • La solución fue injectar el contexto de OTel en un Map de Java, extraerlo como Jaeger SpanContext y luego activarlo manualmente con GlobalExtendedTracer.get().local().activateContext() de Lightbend
  • OTel ayuda a estandarizar la observabilidad, pero al mezclarlo con instrumentación existente basada en Akka y Jaeger puede hacer falta una conexión explícita de contexto

Las señales de observabilidad que OTel intenta unificar

  • OpenTelemetry (OTel) es un framework y toolkit de observabilidad que busca estandarizar áreas de herramientas que antes estaban separadas en tres señales: métricas, logs y trazas
  • La combinación tradicional estaba dividida por rol
    • Prometheus: métricas
    • Agregadores centrales como Logstash y Elasticsearch: logs
    • OpenTracing: tracing distribuido
  • OTel no solo ofrece especificaciones, sino también componentes centrales
    • OpenTelemetry Protocol (OTLP): protocolo con el que las aplicaciones reportan datos de telemetry
    • OpenTelemetry Collector: componente neutral frente a proveedores que recibe, procesa y exporta datos de telemetry
    • SDK para más de 10 lenguajes: implementación de OTLP y exportación de telemetry
  • Las aplicaciones envían señales al OTel Collector mediante instrumentación automática, API y SDK de lenguaje, y la infraestructura también puede enviar señales al Collector

El soporte existente de IPF y la solicitud del cliente

  • IPF ya contaba con integración con ELK Stack, monitoreo y documentación de observabilidad, y los clientes también lo venían usando en entornos de producción
  • Por las características del framework, no se puede saber en qué entorno se desplegarán las aplicaciones que usan la librería, así que era necesario no imponer un proveedor específico y ofrecer abstracciones y valores predeterminados redefinibles
  • La recomendación predeterminada era enviar logs a agregadores como Elasticsearch o LogScale, y habilitar y dar soporte a Prometheus, que se convirtió prácticamente en el estándar para métricas
  • Los clientes hicieron casi al mismo tiempo tres solicitudes de tracing basado en OTel, y al usar OTel para tracing surgió el flujo de migrar también logs y métricas
  • Los logs y las métricas se resolvieron con relativa facilidad agregando un nuevo appender y configuración, pero el tracing quedó como un problema aparte

Por qué hace falta propagar contexto en tracing distribuido

  • Para trazar una sola transacción en un sistema distribuido, hay que pasar entre sistemas información que conecte una llamada específica con la transacción; a esto se le llama propagación de contexto
  • Una traza es una estructura padre que envuelve varias unidades de trabajo llamadas spans; puede haber varios spans y pueden estar anidados
  • En un sitio de comercio electrónico, el flujo de presionar el botón “buy now” puede agruparse en una sola traza
    • Un span en el que el frontend se comunica con el backend
    • Spans hijos en los que el backend se comunica con servicios de pagos, envíos y gestión de pedidos
    • Spans hijos adicionales en los que cada servicio downstream vuelve a comunicarse con otros sistemas
  • Al usar OTel, cada servicio distribuido reporta su parte de la traza al OTel Collector, y OTel compone todo el flujo bajo un único trace ID

Cuatro estándares de propagación de contexto y la huella de OpenTracing

  • Hay cuatro implementaciones de propagación de contexto que OTel debe soportar obligatoriamente
  • El área de Akka en IPF ya soportaba OpenTracing, que está destinado a quedar obsoleto, y usaba el soporte de OpenTracing de Lightbend Telemetry
  • Lightbend Telemetry 2.20.0 agregó soporte para logs/events y métricas de OpenTelemetry, pero faltaba el importante soporte de tracing
  • En teoría, el tracing de OTel se parece a un rebranding de OpenTracing, y OpenTracing de Lightbend Telemetry también soporta los cuatro métodos de propagación, así que parecía que iba a funcionar, pero el resultado real fue distinto

La traza que se cortaba entre Spring y Akka

  • IPF usa Spring y Akka en conjunto
    • Spring Boot y Spring IoC: bootstrap de la aplicación, configuración y composición de dependencias
    • Akka: event sourcing, scheduling, clustering, sharding, integración, etc.
  • Un cliente puede iniciar un flujo de pagos desde un Spring REST controller o desde un método marcado con @KafkaListener
  • Antes de OTel, las tres áreas funcionaban sin grandes conflictos
    • Metrics: Spring y Akka exponían endpoints de Prometheus distintos
    • Logs: ambos frameworks usaban SLF4J y Logback
    • Traces: ambos usaban OpenTracing
  • Al usar OTel, las métricas y los logs se transmitían correctamente porque Spring y Akka funcionaban de manera independiente, pero el tracing debía compartir el mismo trace context dentro de la JVM
  • El problema real era que dentro de la aplicación el mismo concepto de tracing estaba representado por dos API distintas, y esas dos API no se comunicaban entre sí
  • El comportamiento correcto habría sido que el cliente HTTP de Akka reutilizara el trace ID existente 123 y solo generara un nuevo span ID para mostrarlo como una nueva unidad de trabajo de la misma traza; en la práctica, se creaban dos trazas sin relación

opentracing-shim y conflictos de implementación

  • Del lado de OTel existe opentracing-shim, que hace que el Tracer de OTel parezca un Tracer de OpenTracing
  • Sin embargo, Lightbend Telemetry usaba una implementación custom de Tracer, y por eso tanto el shim como Jaeger fallaban
  • En los logs se mostraban los siguientes errores
    • Expected to have an OpenTelemetry Span but got cinnamon.opentracing.TraceLocal$ContextOnlySpan
    • Expected to have a JaegerSpanContext but got io.opentelemetry.opentracingshim.SpanContextShim
  • Jaeger fallaba al recibir el contexto del shim de OTel, y OTel fallaba al recibir el contexto de Lightbend

Investigación del Java Agent y del tracing interno de Lightbend

  • Tanto la instrumentación de OTel como la de Lightbend Telemetry usan Java Agent para enganchar llamadas a métodos específicos de clases específicas y reportar actividad al tracer
  • La instrumentación de Lightbend Telemetry no es open source, así que para entender cómo funcionaba fue necesario investigar el código decompilado del tracer
  • Se creó un ejemplo mínimo reproducible para identificar en qué punto se cortaba el paso del trace context de OTel del lado de Spring hacia Akka
  • El punto donde fallaba Jaeger estaba en este código de JaegerTracer.java
  • El llamador era OpenTracingAkkaPersistenceActorInstrumentation.class, una clase de instrumentación propietaria de Lightbend Telemetry
  • En el código decompilado, la condición clave era el estado de var5
    • Si var5, el OpenTracing SpanContext, no era null, el nuevo span se adjuntaba como hijo del span activo actual
    • Si var5 era null, se convertía en un span sin relación con la traza existente
    • Si var5 no era un Jaeger SpanContext, Jaeger fallaba

Conectar OTel y OpenTracing con una conversión manual

  • El SpanContext se obtenía desde this.traceLocal.currentContext(), y traceLocal se inicializaba desde el ExtendedTracer de Lightbend
  • En la documentación de Lightbend había una forma de acceder globalmente a ExtendedTracer
    • GlobalExtendedTracer.get()
  • GlobalExtendedTracer.get() devuelve un ExtendedTracer, y local() devuelve el mismo TraceLocal que usa la librería de instrumentación
  • Ese TraceLocal tenía un método activateContext que recibe un SpanContext de OpenTracing
  • El flujo de la solución tuvo cuatro pasos
    • No usar el shim de OTel
    • Injectar el Context actual de OpenTelemetry en un Map de Java
    • Usar los valores de ese Map para extraer un Jaeger SpanContext
    • Activar el Jaeger SpanContext en el TraceLocal de Lightbend antes de entrar a Akka
  • La operación usada de la API de propagators de OTel fue inject and extract operations
  • El código clave sigue este flujo
    • Crear un HashMap vacío
    • Inyectar el contexto de OTel en el map con GlobalOpenTelemetry.get().getPropagators().getTextMapPropagator().inject(...)
    • Crear un JaegerSpanContext con new TextMapCodec(false).extract(new TextMapAdapter(otelContext))
    • Activar el context antes de entrar a Akka con GlobalExtendedTracer.get().local().activateContext(openTracingContext)

Comportamiento confirmado y recomendación operativa

  • Después de la conexión manual, la traza continuó como se esperaba
    • Todo el flujo, que empezaba desde la llamada REST de Spring /submit, quedó conectado como una única traza
    • Se podían mezclar partes instrumentadas con la API de OTel y partes instrumentadas con la API de OpenTracing
    • La traza se propagaba más allá del límite HTTP
  • En el ejemplo se usaron nombres distintos para diferenciar los dos tipos de instrumentación, pero en entornos reales de clientes se recomienda alinear otel.service.name y cinnamon.application para que desde afuera se vean como una sola aplicación

Por qué fue complicado y preocupaciones restantes

  • Reescribir Lightbend Telemetry con la API de OTel podría ser un trabajo grande, porque habría que portar a la API de OTel mucha instrumentación que hoy está atada a la API de Jaeger
  • Como el OTel Collector soporta la recolección de trazas en el formato legacy de Zipkin, Lightbend puede apoyarse en ese soporte legacy para ofrecer soporte completo de OTel
  • La causa directa de la complejidad fue intentar combinar dos sistemas de instrumentación que usan librerías de tracing distintas
  • El proyecto OTel intenta ordenar el área de observabilidad mediante esfuerzos de estandarización como las semantic conventions, y se lo evalúa como un proyecto FOSS útil, aunque algo complejo de entender al principio
  • Todavía queda la preocupación de si Akka propaga correctamente el trace context entre threads dentro del actor model
    • En pruebas de carga pequeñas funcionó como se esperaba
    • Se dejó abierto un ticket relacionado del lado de Akka

1 comentarios

 
GN⁺ 2025-01-11
Opiniones de Hacker News
  • Durante todo el tiempo que estuve aprendiendo y portando Otel, sentí que había vuelto al mundo de Java. Cada vez que seguía el código línea por línea parecía EnterpriseFizzBuzz, no había ninguna posibilidad de descubrimiento, y hasta su terminología propia parecía hecha por gente bajo los efectos de algo.
    En NodeJS, el uso de CPU era unas 4 veces mayor que con StatsD, así que al final hice mi propia agregación para bajar el consumo y también reducir la explosión de etiquetas. StatsD no tiene problema si varios procesos reportan las mismas etiquetas, pero OTEL las sobrescribe.
    En carga pico, un CPU corría con 60~80% de uso, y antes de que algo cambiara ni siquiera podíamos escalar verticalmente. Para lenguajes que usan un proceso por core, OTEL parece activamente hostil, casi un chiste. Es mejor simplemente usar Prometheus y, en la práctica, tampoco hay otro competidor.

    • Veo a OTEL como algo cercano a una forma de captura por proveedor. Porque para crear un estándar no quedaba otra que conciliar las necesidades de varias grandes empresas y startups flojas, y pegarlas con pistola de silicón.
      Intenté hacer una configuración simple de otel en .NET y, después de leer durante horas la documentación del proveedor que había elegido la organización sin entenderla, entré al Discord que administraba un colega. Parte de su modelo de negocio era “paga si quieres usar otel bien en un producto open source”, y de inmediato sentí que valía la pena sin importar el costo.
      Antes que implementar OTEL, preferiría crear otra librería estable de eventos/pub-sub sin experiencia previa.
    • Sobre eso de que “tampoco hay otro competidor”, según el caso también vale la pena mirar Apache SkyWalking. No consume muchos recursos y su configuración y operación son bastante simples, aunque la UI y la documentación están menos pulidas: https://skywalking.apache.org/
      La configuración mínima en realidad también es bastante pequeña: alcanza con una UI web, una instancia de servidor y una DB que ya conozcas: https://skywalking.apache.org/docs/main/latest/en/setup/back...
      Tiene algo parecido a Zabbix en el área de monitoreo. Ninguno de los dos va a impresionar a nadie, pero son suficientemente prácticos.
    • Mi conclusión es la misma. Basta con usar Prometheus y la librería cliente del lenguaje que prefieras; es 1000 veces más simple que toda la historia de OTEL.
    • Estoy usando otel en C++, y para obtener métricas acumulativas en varias aplicaciones creé un atributo entero de cardinalidad relativamente baja, como process.vpid. Se puede coordinar con algo como un objeto global para que ese valor sea único mientras la app esté viva.
      Luego basta con tener algo que lo sume y elimine ese atributo. Con statsd/delta, si se pierde un envío de señal se distorsionan todos los datos, pero con el enfoque acumulativo solo se pierde precisión.
      Mi caso de uso no son procesos de larga duración que se puedan scrapear, sino métricas push-based provenientes de herramientas “batch”.
    • Coincide con mi experiencia. Fue muy difícil entender qué hacía falta para lograr el efecto que quería.
  • La razón por la que Otel parece complejo es que varios proveedores de observabilidad hicieron que implementar observabilidad fuera muy fácil con sus SDK, agentes y API propietarios. Otel intenta resolver ese problema, y creo que quienes lo hicieron lo están logrando muy bien.
    También es digno de elogio que Grafana haya adoptado OpenTelemetry como un componente de primera clase del ecosistema.
    Durante años impulsé el uso de Datadog, pero para una escala entre empresa mediana y grande el precio se volvió difícil de sostener. Con el tiempo, a medida que las API y SDK de OpenTelemetry se estabilizaron, lo adoptamos como estándar de observabilidad de aplicaciones.
    Dicho eso, la documentación en general podría mejorar, y no es ideal que la documentación de onboarding sea distinta según el lenguaje.
    Actualmente mi equipo usa un stack NodeJS/Typescript, e hicimos un paquete y un stack Grafana de ejemplo para poder empezar rápido con OpenTelemetry: https://github.com/zonneplan/open-telemetry-js

    • ¿Entonces este producto resuelve un problema causado por que todos lo hicieron muy fácil, haciéndolo complejo? ;P
    • Prefiero simplemente una buena recolección de métricas antes que Datadog. En cambio, en teoría entiendo que OTEL tiene más valor que mi enfoque preferido.
      El mayor problema con todos los proveedores de APM es que, en el momento en que un agente mágico empieza a tener hooks del kernel, comienzan a pasar todo tipo de cosas que los desarrolladores no pueden explicar.
      En una empresa anterior implementamos Dynatrace y, por suerte, la app ya tenía suficientes métricas integradas como para que el SRE líder la considerara un “modelo” de instrumentación. Pero apenas instalamos el agente de Dynatrace en el host de la app, aparecieron varios heisenbugs que requerían reiniciar nodos, y también medimos directamente una degradación de rendimiento.
      Irónicamente, gracias a las métricas evitamos el sufrimiento, pero nadie sabía cómo arreglarlo. Lo peor fue un caso en que, durante una actualización de MSSQL, el failover contaminaba de forma extraña el pool de conexiones de ADO.NET.
  • Es una estructura que se vuelve compleja solo en la medida necesaria. Se pueden evitar las funciones mágicas y usar solo el subconjunto que, según el contexto, aporte más valor y sea más fácil de entender
    En nuestro equipo es muy simple. Usamos una biblioteca que solo envía trazas, y las trazas son lo que más valor aporta para observar la aplicación, además de que pueden contener otros tipos de datos. Básicamente, es como usar un hashmap en lugar de cadenas y números de punto flotante
    Usamos instrumentación manual en vez de instrumentación automática, decidimos intencionalmente qué observar y entendemos bien qué código emite spans. También tenemos convenciones de nombres que encajan con la estructura del código
    Para el backend usamos un servicio barato de terceros junto con una instalación all-in-one de Jaeger para desarrollo local. Esta última solo requiere ejecutar un binario o un contenedor Docker, y no guarda los spans en disco. Principalmente sirve para tranquilizar al equipo y que no sientan que van a saturar el servicio de terceros
    Para monitoreo de infraestructura ya tenemos una configuración existente y, en nuestro caso, no vemos mucho valor en recopilar todos los logs y métricas de infraestructura. Considero que las métricas y logs de OTEL todavía están en una etapa temprana, aunque los proveedores no lo digan así

    • Puede volverse tan complejo como quieras, pero no es tan fácil como yo quisiera. La dificultad mínima es bastante alta
      Todavía estoy buscando un endpoint para enviar métricas simples y puntuales desde algunas partes de la infraestructura que aún no se pueden scrapear
    • No me pareció que la instrumentación manual fuera más simple. Es más bien un intercambio: la curva de aprendizaje empieza mucho antes de que veas resultados, pero a cambio entiendes con más claridad el costo de rendimiento que genera esta máquina de Rube Goldberg
      Otel puede estar bien para un proyecto nuevo, pero activarlo en un servicio en producción que ya tiene telemetría se sintió como cambiarle las llantas a un auto en marcha
    • ¿Puedes compartir cuál es ese servicio barato de terceros?
    • Es un consejo muy razonable. La mayoría ya tendrá algo para métricas y logs, y si no hay retorno de inversión por cambiar, no hay razón para hacerlo
    • “Evita la magia y usa solo un subconjunto” solo es posible si todas las bibliotecas que usas también usan solo ese subconjunto. En mi experiencia, de forma abrumadora no fue así, y el artículo muestra buenos ejemplos concretos de por qué
      Creo que podría ser bueno en un proyecto greenfield que use solo otel y no tenga frameworks no-otel. Pero yo todavía no vivo en ese mundo
  • Uno de los mayores problemas fue la experiencia de desarrollo local. Quería soportar logs, trazas y métricas en local, pero no quería levantar un montón de imágenes Docker para eso. Quería ver en los logs cómo se veían las métricas, trazas, baggage y activity spans antes de desplegar
    Hace poco el equipo de .NET lanzó .NET Aspire y está muy bueno. En un stack de desarrollo local facilita visualizar todo en un solo lugar, y funciona como un orquestador en código
    Al desplegar en k8s, apuntas el endpoint de OTEL al DataDog Agent y todo simplemente funciona. Evitamos la biblioteca y el SDK de trazas personalizadas de DataDog y usamos solo OTEL
    Ahora la experiencia de desarrollo quedó bastante buena
    https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals...
    https://docs.datadoghq.com/opentelemetry/#overview

    • Para ese uso, este proyecto es realmente bueno: https://github.com/grafana/docker-otel-lgtm
    • También está https://github.com/CtrlSpice/otel-desktop-viewer
    • Simplemente puedes usar https://github.com/openobserve/openobserve
      Configurarlo por primera vez en una máquina local de desarrollo toma 5 minutos, y después basta con ejecutar /path/to/openobserve en una pestaña de terminal aparte. Si quieres evitar la enorme complejidad de un binario único enlazado estáticamente, también ofrecen imágenes Docker para ejecución local y remota :P
      Es un backend OpenTelemetry todo en uno con gráficas bonitas, y en mis proyectos todavía no ha fallado de ninguna forma detectable
    • .NET Aspire no me convence. Resuelve el pequeño problema del descubrimiento de servicios y la orquestación de varios proyectos de servicios en desarrollo local, pero lo hace convirtiéndolo en una preocupación a nivel de aplicación
      Si usas Aspire, agregas complejidad innecesaria al nivel de la app y quedas atado a un ecosistema estrecho. Para desarrollo local hay muchas alternativas probadas como docker compose, y Aspire tampoco es mucho más fácil que docker compose y variables de entorno
    • Hay una imagen Docker all-in-one oficial que incluye todo
  • Si vas a hacer otel en Python, conviene usar el cliente de Logfire. Incluso si no usas el servicio de Logfire
    Es open source y puede enviar a cualquier endpoint compatible con otel. Además, el cliente hecho por el equipo de pydantic es 10 veces mejor y más simple que la biblioteca oficial de otel
    También hay una entrevista interesante donde Samuel Colvin explica cómo llegó hasta esto: https://www.bitecode.dev/p/samuel-colvin-on-logfire-mixing-p...

  • Por eso inicié un proyecto open source enfocado en hacer que adoptar OpenTelemetry sea tan fácil como un solo comando: https://github.com/odigos-io/odigos

  • Hoy en día, muchos frameworks web se encargan de la mayor parte de la instrumentación. Por ejemplo, si usas opentelemetry-js y alojas por tu cuenta algo como https://signoz.io, puedes tenerlo funcionando en menos de una hora y obtener muchos datos sin escribir código personalizado

    • El repositorio de SigNoz está aquí: https://github.com/signoz/signoz
    • La propagación de contexto en runtimes asíncronos multihilo no es sencilla. Hay varias formas de hacerlo, pero la razón por la que los agentes de JVM que instrumentan bytecode son populares es que funcionan de manera transparente
  • OpenTelemetry creció a partir de los traces, pero es mucho mejor dejar las métricas y los logs a soluciones especializadas
    Se siente como el problema de una “abstracción con fugas” o un “framework con fugas”. Si quieres meter todo bajo un mismo paraguas, una base de datos SQL también puede hacer todo eso a la vez. Eso no significa que debas hacerlo

    • Cramer quiere sacar los traces de OTel. Es irónico, considerando que es uno de los creadores de OpenTracing
      https://cra.mr/the-problem-with-otel/
    • Me parece realmente útil darle a las métricas y al logging una ubicación dentro de un trace
      Aun así, cada vez que trato con OTel, sigue sin gustarme
  • Si lees hasta el final, te das cuenta de que la mayor parte del dolor se lo causaron ellos mismos. En el stack estándar de Python (mysql, flask, redis, requests, etc.) fue muy fácil. Con solo poner unos cuantos imports al inicio del servicio, todo se conectaba automáticamente y rastreaba todo sin mayor alboroto

    • Es cierto, pero solo cuando todo en el stack soporta instrumentación automática. Por ejemplo, aiohttp va por la versión 3.11.X, y aunque la instrumentación automática dice que soporta 3.X [0], en la práctica el resultado depende de qué tan reciente sea el aiohttp que uses y del estado de la instrumentación automática
      Cuando todo encaja, funciona como magia, pero ese margen es bastante estrecho
      [0]: https://github.com/open-telemetry/opentelemetry-python-contr...
    • Hace poco tuve que agregar esto a una app de Flask muy simple. Como tenía que correr solo dentro de la intranet de la empresa, estoy ejecutando otel-collector-contrib, jaeger-all-in-one y prometheus con docker compose en un único servidor
      Los traces funcionan y también configuré el spanmetrics exporter; si consulto prometheus directamente, veo las spanmetrics. Pero haga lo que haga, no aparecen en la pestaña “monitor” de jaeger
      Después de gastar 3 días en esto, mi jefe dijo: “¿y si simplemente hacemos instrumentación manual y mandamos todo a SQL Server, y armamos un dashboard en Grafana?”. Pero tampoco quiero hacer eso
      Ni siquiera funciona para el caso de uso más simple. ¿Tengo que agregar Grafana encima?
    • Eso solo aplica hasta que corres el servidor detrás de algo como gunicorn. En ese momento, todos los imports automáticos dejan de funcionar y tienes que hacerlo todo tú mismo
  • Es complejo porque no fue diseñado para los ingenieros que lo implementan, sino para las empresas que venden software compatible con Otel

    • No estoy tan seguro. Creo que los vendors habrían estado contentos con su propio código propietario, agentes y backends. Gracias al lock-in, los costos de cambio se vuelven muy altos, porque hay que escribir todo el código nuevo
    • Lo que vi de parte de los contribuidores no fue eso
      Más bien, creo que los backends tardaron bastante en adoptarlo
    • Sonará quisquilloso, pero antes ‘implement’ se refería a la primera actividad: sentarse a escribir software conforme a una especificación. No significaba tomar software existente y desplegarlo en un servidor, como en el segundo caso
      Los significados cambian, así que está bien, pero me parece curioso que ahora ya no quede una palabra para referirse a escribir software conforme a una especificación, y solo quede “desplegar software existente en un servidor”