Skip to content

Visualizando Trazas de Microservicios en Grafana

En este tutorial, aprenderemos cómo visualizar las trazas generadas por los microservicios de CodeDesignPlus utilizando Grafana. Explicaremos el flujo de trazas desde los microservicios, a través de OpenTelemetry y Tempo, hasta la visualización en Grafana. Esto nos permitirá realizar un seguimiento de las peticiones a través de los distintos servicios de nuestra arquitectura de microservicios, lo que facilita el diagnóstico de problemas y la optimización del rendimiento.

¿Por qué Visualizar Trazas en Grafana?

La visualización de trazas es esencial para la observabilidad y el análisis de sistemas distribuidos como los microservicios. Las trazas nos permiten:

  • Seguimiento de peticiones: Ver cómo se propagan las peticiones a través de los distintos servicios y componentes del sistema.
  • Identificar cuellos de botella: Detectar cuellos de botella de rendimiento y latencia en los diferentes servicios.
  • Diagnosticar problemas: Investigar el origen de errores y problemas en las peticiones.
  • Analizar la dependencia: Visualizar las dependencias entre microservicios y el flujo de datos entre ellos.

Grafana, con su capacidad de crear dashboards y visualizar datos de diferentes fuentes, es una herramienta ideal para analizar las trazas de nuestros microservicios.

¿Qué Aprenderás?

  1. Cómo los microservicios exportan las trazas a OpenTelemetry usando gRPC.
  2. Cómo OpenTelemetry direcciona las trazas a Tempo.
  3. Cómo visualizar las trazas de los microservicios en Grafana.
  4. Cómo analizar las trazas para identificar problemas y optimizar el rendimiento.

Arquitectura de Monitoreo de Trazas

El flujo de trazas en nuestro entorno de microservicios sigue la siguiente arquitectura:

  1. Microservicios: Los microservicios, construidos con el framework de CodeDesignPlus, generan trazas que son enviadas a través de gRPC al OpenTelemetry Collector.
  2. OpenTelemetry Collector: Recibe las trazas de los microservicios y las direcciona a Tempo. El OpenTelemetry Collector implementa el protocolo OTLP (OpenTelemetry Protocol).
  3. Tempo: Recibe y almacena las trazas del OpenTelemetry Collector, optimizadas para el almacenamiento de trazas a gran escala.
  4. Grafana: Consulta las trazas almacenadas en Tempo y las visualiza en diagramas de Gantt, que muestran el flujo de la petición en el tiempo.
Entorno de Desarrollo

Configuración

CodeDesignPlus ya incluye la configuración necesaria para el monitoreo de trazas. La librería CodeDesignPlus.Net.Observability se encarga de exportar las trazas de los microservicios a OpenTelemetry, que a su vez las envía a Tempo.

using CodeDesignPlus.Net.Core.Abstractions.Options;
using CodeDesignPlus.Net.Core.Extensions;
using CodeDesignPlus.Net.Observability.Abstractions.Options;
using CodeDesignPlus.Net.Observability.Exceptions;
using Confluent.Kafka.Extensions.OpenTelemetry;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace CodeDesignPlus.Net.Observability.Extensions;
/// <summary>
/// Provides extension methods for adding observability services to the service collection.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds observability services to the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="environment">The host environment.</param>
/// <param name="metricsBuilder">Optional metrics builder action.</param>
/// <param name="traceBuilder">Optional trace builder action.</param>
/// <returns>The OpenTelemetry builder.</returns>
/// <exception cref="ObservabilityException">Thrown when the observability section is missing in the configuration.</exception>
public static IOpenTelemetryBuilder AddObservability(this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment, Action<MeterProviderBuilder> metricsBuilder = null, Action<TracerProviderBuilder> traceBuilder = null)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(environment);
var observabilitySection = configuration.GetSection(ObservabilityOptions.Section);
if (!observabilitySection.Exists())
throw new ObservabilityException($"The section {ObservabilityOptions.Section} is required.");
var observabilityOptions = observabilitySection.Get<ObservabilityOptions>();
var coreOptions = configuration.GetSection(CoreOptions.Section).Get<CoreOptions>();
services
.AddOptions<ObservabilityOptions>()
.Bind(observabilitySection)
.ValidateDataAnnotations();
services.AddCore(configuration);
var otel = services.AddOpenTelemetry()
.ConfigureResource(resource =>
{
resource.AddService(serviceName: coreOptions.AppName, serviceVersion: coreOptions.Version);
});
otel.ConfigureMetrics(environment, observabilityOptions, metricsBuilder);
otel.ConfigureTracing(observabilityOptions, environment, traceBuilder);
return otel;
}
/// <summary>
/// Configures metrics for OpenTelemetry.
/// </summary>
/// <param name="otel">The OpenTelemetry builder.</param>
/// <param name="environment">The host environment.</param>
/// <param name="observabilityOptions">The observability options.</param>
/// <param name="builder">Optional metrics builder action.</param>
private static void ConfigureMetrics(this IOpenTelemetryBuilder otel, IHostEnvironment environment, ObservabilityOptions observabilityOptions, Action<MeterProviderBuilder> builder)
{
if (!observabilityOptions.Metrics.Enable)
return;
otel.WithMetrics(metricsBuilder =>
{
metricsBuilder.AddMetricsAspNetCoreInstrumentation(observabilityOptions.Metrics.AspNetCore);
metricsBuilder.AddOtlpExporter(x =>
{
x.Endpoint = observabilityOptions.ServerOtel;
x.Protocol = OtlpExportProtocol.Grpc;
});
if (environment.IsDevelopment())
metricsBuilder.AddConsoleExporter();
builder?.Invoke(metricsBuilder);
});
}
/// <summary>
/// Adds ASP.NET Core instrumentation for metrics.
/// </summary>
/// <param name="metricsBuilder">The metrics builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddMetricsAspNetCoreInstrumentation(this MeterProviderBuilder metricsBuilder, bool enable)
{
if (enable)
metricsBuilder.AddAspNetCoreInstrumentation();
}
/// <summary>
/// Configures tracing for OpenTelemetry.
/// </summary>
/// <param name="otel">The OpenTelemetry builder.</param>
/// <param name="observabilityOptions">The observability options.</param>
/// <param name="environment">The host environment.</param>
/// <param name="builder">Optional trace builder action.</param>
private static void ConfigureTracing(this IOpenTelemetryBuilder otel, ObservabilityOptions observabilityOptions, IHostEnvironment environment, Action<TracerProviderBuilder> builder)
{
if (!observabilityOptions.Trace.Enable)
return;
otel.WithTracing(tracing =>
{
tracing.AddTraceAspNetCoreInstrumentation(observabilityOptions.Trace.AspNetCore);
tracing.AddTraceGrpcClientInstrumentation(observabilityOptions.Trace.GrpcClient);
tracing.AddTraceSqlClientInstrumentation(observabilityOptions.Trace.SqlClient);
tracing.AddTraceCodeDesignPlusSdkInstrumentation(observabilityOptions.Trace.CodeDesignPlusSdk);
tracing.AddTraceRedisInstrumentation(observabilityOptions.Trace.Redis);
tracing.AddTraceKafkaInstrumentation(observabilityOptions.Trace.Kafka);
tracing.AddOtlpExporter(x =>
{
x.Endpoint = observabilityOptions.ServerOtel;
x.Protocol = OtlpExportProtocol.Grpc;
});
if (environment.IsDevelopment())
tracing.AddConsoleExporter();
builder?.Invoke(tracing);
});
}
/// <summary>
/// Adds ASP.NET Core instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceAspNetCoreInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
{
tracing.AddAspNetCoreInstrumentation(options =>
{
options.Filter = httpContext => httpContext.Request.Path.StartsWithSegments("/api");
});
tracing.AddHttpClientInstrumentation();
}
}
/// <summary>
/// Adds gRPC client instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceGrpcClientInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
{
tracing.AddGrpcClientInstrumentation();
tracing.AddHttpClientInstrumentation();
}
}
/// <summary>
/// Adds SQL client instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceSqlClientInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
tracing.AddSqlClientInstrumentation();
}
/// <summary>
/// Adds CodeDesignPlus SDK instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceCodeDesignPlusSdkInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
{
tracing.AddSource("CodeDesignPlus.Net.Mongo.Diagnostics");
tracing.AddSource("CodeDesignPlus.Net.PubSub");
}
}
/// <summary>
/// Adds Redis instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceRedisInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
tracing.AddRedisInstrumentation();
}
/// <summary>
/// Adds Kafka instrumentation for tracing.
/// </summary>
/// <param name="tracing">The tracing builder.</param>
/// <param name="enable">Indicates whether to enable the instrumentation.</param>
private static void AddTraceKafkaInstrumentation(this TracerProviderBuilder tracing, bool enable)
{
if (enable)
tracing.AddConfluentKafkaInstrumentation();
}
}

Pasos para la Visualización de Trazas en Grafana

  1. Asegúrate de que los Microservicios estén en Ejecución: Verifica que los microservicios, OpenTelemetry Collector, Loki y Grafana estén en ejecución. El microservicio puede ser ejecutado de forma local o en un contenedor Docker.

    Docker ps
  2. Accede a Grafana: Abre un navegador y ve a la URL http://localhost:3000 (o la dirección donde se está ejecutando Grafana).

    Grafana Home
  3. Explora las Trazas: En la barra de navegación de Grafana, haz clic en Explore (icono de la brújula) y selecciona la fuente de datos de Tempo.

    Grafana Tempo Data Source
  4. Realiza una Consulta: Puedes buscar trazas por TraceId. Los TraceId son generados automáticamente por la librería CodeDesignPlus.Net.Observability y son enviados en la cabecera de la petición con el nombre traceparent en el caso de REST y en el Metadata en el caso de gRPC.

    Grafana Tempo Trace Id
  5. Visualiza la Traza: Una vez que seleccionas la traza, Grafana mostrará un diagrama de Gantt que representa el flujo de la petición a través de los diferentes microservicios.

    Grafana Tempo Response
  6. Analiza la Traza: Examina el diagrama de Gantt para identificar cuellos de botella, errores y latencia en la petición. Puedes hacer clic en cada span (cada servicio en la traza) para ver información detallada como el tiempo de ejecución y los atributos asociados.

  7. Crea Dashboards: Para un monitoreo continuo, puedes crear paneles en Grafana que muestren las trazas de forma permanente o con base a métricas. Crea un nuevo dashboard y añade un panel con la fuente de datos de Tempo y las queries que necesites.

Conclusiones

En este tutorial, hemos aprendido cómo visualizar las trazas de los microservicios de CodeDesignPlus utilizando Grafana. Hemos comprendido el flujo de trazas desde los microservicios, a través de OpenTelemetry y Tempo, hasta la visualización final en Grafana. Ahora, puedes utilizar Grafana para realizar un seguimiento de las peticiones a través de los diferentes servicios, lo que te permitirá diagnosticar problemas, optimizar el rendimiento y mejorar la observabilidad de tu sistema.

Esta configuración proporciona una base sólida para la observabilidad de tus microservicios y facilita la gestión de tu sistema.