Tempo - aleiis/grafana-stack-validation GitHub Wiki
Tempo es un backend de observabilidad que permite almacenar, indexar y consultar trazas de forma eficiente, escalable y distribuida. En esta sección se explica el proceso de instrumentación del frontend y del backend, así como la configuración y la recolección e ingesta de trazas en Tempo. La documentación técnica de Tempo puede encontrarse en su sitio oficial: https://grafana.com/docs/tempo/latest/
Instrumentación de los servicios
La instrumentación se ha realizado con OpenTelemtry, un framework de código abierto diseñado para instrumentar, generar, recopilar y exportar trazas distribuidas en aplicaciones. A pesar de que solo hemos utilizado OpenTelemetry para realizar la instrumentación de las trazas también soporta instrumentación de logs y métricas en algunos entornos.
Existen distintas formas de instrumentar una aplicación:
-
Instrumentación manual: Consiste en instrumentar la aplicación a base de añadir código a esta que genere manualmente toda la información de trazabilidad. Esto significa que el desarrollador añade y controla manualmente las trazas, los spans, los atributos, los eventos, los errores, etc.
-
Instrumentación semi-automática: Consiste en instrumentar la aplicación a base de añadir código a esta con el objetivo de generar automáticamente la información de trazabilidad. Por ejemplo, importar y configurar una librería que genere automáticamente toda la información de trazabilidad relacionada con las solicitudes HTTP.
-
Instrumentación automática: Constite en instrumentar la aplicación a través de la instalación de un agente externo a esta que genere automáticamente toda la información de trazabilidad.
En el caso del backend, está preparado para exportar trazas de dos formas distintas. Por un lado, se ha realizado una instrumentación manual utilizando la librería de OTEL para Go para instrumentar la parte de la API REST, junto con una instrumentación semi-automática utilizando la librería otelsql
para instrumentar las consultas a la base de datos MySQL. Para exportar trazas de esta forma solo hay que especificar en su configuración un endpoint válido que pueda recibir trazas mediante OpenTelemetry Protocol (OTLP) sobre HTTP. Por otro lado, también se despliega un agente de OpenTelemtry para Go que realiza una instrumentación automática del backend. Es importante que no se utilicen ambos formatos a la vez para que la información de trazabilidad no venga repetida.
En el caso del frontend, se ha realizado una instrumentación semi-automática de las solicitudes HTTP utilizando las librerías de OpenTelemetry para JavaScript para el navegador.
Recolección e ingesta de trazas
La recolección se hace gracias al OpenTelemetry Collector, que se encarga de recibir, procesar y exportar las trazas de los distintos servicios a Tempo. De esta forma, son las aplicaciones o los agentes quienes envían las trazas al OTEL Collector para ser procesadas y posteriormente exportadas a Tempo. Es importante el uso de un OTEL Collector ya que vamos a tener que lidiar con las políticas de CORS para poder recibir las trazas generadas por el frontend desde el navegador del usuario. Además, también colabora en la estandarización y modularización de la recolección de los datos de telemetría. Aun así, aunque no es recomendable, Tempo también está preparado para recibir las trazas directamente.
Por último, Tempo recibe las trazas enviadas por el OTEL Collector para después ser procesadas, almacenadas e indexadas de forma eficiente en una base de datos distribuida diseñada para gestionar grandes volúmenes de trazas de manera eficiente. Si queremos consultar trazas almacenadas en Tempo podemos hacerlo utilizando TraceQL.
Configuración
Exportación de trazas desde el backend
Si queremos apostar por la instrumentación manual en el backend solo tenemos que especificar en su configuración el endpoint del OTEL Collector para recibir trazas con OTLP/HTTP (normalmente, el puerto 4318
). Una vez reiniciemos el backend con la nueva configuración ya estará exportando trazas al OTEL Collector.
otlp:
http_trace_exporter_endpoint: "{hostname}:{port}"
Exportación de trazas desde el agente de OTEL para Go
Si queremos apostar por la instrumentación automática, el contenedor autoinstrumentation-go
definido en docker-compose.yaml
ejecuta un agente de OTEL para Go. Este agente solo necesita acceso al binario que queremos instrumentar y al archivo /proc
para empezar a generar información de telemetría. Utilizando variables de entorno debemos indicar la ruta al binario dentro del contenedor (OTEL_GO_AUTO_TARGET_EXE
), el nombre del servicio al que van asignadas las trazas generadas (OTEL_SERVICE_NAME
) y el endpoint que recibirá las trazas sobre OTLP/HTTP (OTEL_EXPORTER_OTLP_ENDPOINT
).
🚧 Actualmente, este agente sigue en desarrollo y puede estar incompleto.
Más información sobre este agente en su repositorio de GitHub: https://github.com/open-telemetry/opentelemetry-go-instrumentation/
Exportación de trazas desde el frontend
El endpoint al que debe exportar trazas el frontend viene hardcodeado en /webui/src/otel-instrument.js. Esto significa que si queremos cambiarlo debemos editar este archivo y construir una nueva imagen utilizando Dockerfile.frontend
. Una vez despleguemos el frontend con la nueva imagen todas las trazas generadas por los usuarios llegarán al nuevo endpoint.
OTEL Collector
El OTEL Collector solo necesita un YAML de configuración para funcionar. En la configuración hay tres apartados que debemos definir obligatoriamente: recerivers
, exporters
y service
.
En la sección de receivers
, se definen los receptores que queremos habilitar. En nuestro caso, se define un receptor otlp
que puede manejar datos mediante el protocolo OTLP enviados tanto por gRPC como por HTTP. Para gRPC, se escucha en el puerto 4317
, mientras que para HTTP se utiliza el puerto 4318
. Es importante configurar el soporte para CORS, permitiendo solicitudes desde cualquier origen HTTP y con cualquier encabezado.
En la sección de exporters
, se configuran dos exportadores que queremos habilitar. El exportador otlphttp
envía las trazas procesadas a un endpoint concreto mediante OTLP/HTTP.
Finalmente, en la sección service
, se define un pipeline de procesamiento para las trazas. Este pipeline usa el receptor otlp
para recibir los datos y el exportador otlphttp
para enviarlos a los endpoints configurados.
Tempo
Tempo sigue una estructura muy parecida a sus homólogos Cortex y Loki, y al igual que estos, solo necesita un archivo de configuración que defina los módulos que queremos habilitar y su comportamiento. De la misma forma que Cortex y Loki, en este caso también desplegamos Tempo de forma monolítica. El modo monolítico corre todos los componentes indispensables en un único proceso. Es la forma más rápida y sencilla de desplegar Tempo, pero no te permite escalarlo horizontalmente incrementando el número de componentes.
El modo monolítico se activa añadiendo la sentencia target: all
al YAML de configuración (de hecho, all
es el valor por defecto de target
) o añadiendo el parámetro -target=all
a la línea de comandos.
Más información sobre los modos de despliegue de Tempo: https://grafana.com/docs/tempo/latest/setup/deployment/
En cuanto a la configuración, hay muchos valores que pueden mantenerse por defecto y no es necesario declarar. Sí es necesario habilitar y configurar los receptores que vayamos a utilizar dentro de los Distributors. También debemos especificar el tipo de almacenamiento que queremos utilizar y configurarlo debidamente dentro del objeto storage
.
En la documentación oficial de Tempo podemos encontrar distintos casos de uso que nos pueden servir de ejemplo para redactar nuestro propio archivo de configuración. La referencia de configuración también podría ser útil en caso de que desee redactar su propia documentación: https://grafana.com/docs/tempo/latest/configuration/
Service Graph
Un grafo de servicios proporciona una representación visual sobre las interrelaciones entre los distintos servicios de un sistema distribuido. Estos gráficos facilitan la comprensión de la estructura del sistema, las conexiones y dependencias entre sus componentes, y ofrecen una visión general de la salud del sistema al mostrar tasas de error, latencias y otros datos relevantes.
El componente metrics-generator de Tempo procesa las trazas y genera este grafo en forma de métricas de Prometheus. Analiza las trazas buscando relaciones padre-hijo que representan solicitudes entre servicios, utilizando las convenciones semánticas de OpenTelemetry para detectar diversos tipos de solicitudes, incluyendo:
-
Solicitudes directas entre dos servicios, donde el span saliente tiene
span.kind
comoclient
y el entrante comoserver
. -
Solicitudes a través de un sistema de mensajería, con
span.kind
producer
en el span saliente yconsumer
en el entrante. -
Solicitudes a bases de datos, identificadas por spans con
span.kind
=client
y atributos comodb.name
odb.system
.
Los spans que puedan emparejarse para formar una solicitud se almacenan en memoria hasta que se reciba su par correspondiente o se alcance el tiempo máximo de espera. Las series temporales de métricas generadas incluyen las etiquetas client
y server
que corresponden al servicio que realiza la solicitud y al que la recibe, respectivamente.
Además, Tempo también puede identificar nodos virtuales, que son partes del ciclo de vida de una traza cuyos spans no se pueden recopilar, ya sea porque están fuera de alcance (por ejemplo, un servicio externo de procesamiento de pagos) o no están instrumentados (por ejemplo, una base de datos).
En el caso de WASAPhoto, los spans generados por el frontend que representan solicitudes a la API del backend vienen etiquetados con span.kind
=client
, mientras que los spans generados por el backend que representan el manejo de las solicitudes del frontend vienen etiquetados con span.kind
=server
. Del mismo modo, los spans generados por el backend que representen solicitudes a la base de datos deben venir etiquetados con span.kind
=client
y db.name
para identificar el nodo virtual que representa la base de datos.
Para generar el grafo de servicios, es necesario activar el módulo metrics-generator en la configuración. Las métricas generadas se envían a Cortex mediante el protocolo remote write para ser almacenadas. También podemos especificar la ruta en las que se guardan las trazas temporalmente y una lista de etiquetas que queremos que se añadan a las métricas generadas.
En Grafana, el grafo de servicios se configura vinculando dentro del datasource de Tempo el backend de Cortex donde se envían las métricas. Una vez configurados, Grafana utiliza las métricas generadas por Tempo para mostrar visualmente las tasas de solicitudes, tasas de error y duraciones entre servicios.
Más información sobre los grafos de servicio de Tempo: https://grafana.com/docs/tempo/latest/metrics-generator/service_graphs/