Ejemplo: Uso de OpenTelemetry con Prometheus, Grafana y Jaeger
En este ejemplo se usa Prometheus para la recopilación de métricas, Grafana para crear un panel y Jaeger para mostrar el seguimiento distribuido.
1. Creación del proyecto
Cree un proyecto de API web sencillo utilizando la plantilla ASP.NET Core Vacía en Visual Studio o el siguiente comando de la CLI de .NET:
dotnet new web
2. Agregar métricas y definiciones de actividad
El código siguiente define una nueva métrica (greetings.count
) para el número de veces que se ha llamado a la API y un nuevo origen de actividad (OtPrGrYa.Example
).
// Custom metrics for the application
var greeterMeter = new Meter("OtPrGrYa.Example", "1.0.0");
var countGreetings = greeterMeter.CreateCounter<int>("greetings.count", description: "Counts the number of greetings");
// Custom ActivitySource for the application
var greeterActivitySource = new ActivitySource("OtPrGrJa.Example");
3. Creación de un punto de conexión de API
app.MapGet("/", SendGreeting);
async Task<String> SendGreeting(ILogger<Program> logger)
{
// Create a new Activity scoped to the method
using var activity = greeterActivitySource.StartActivity("GreeterActivity");
// Log a message
logger.LogInformation("Sending greeting");
// Increment the custom counter
countGreetings.Add(1);
// Add a tag to the Activity
activity?.SetTag("greeting", "Hello World!");
return "Hello World!";
}
Nota
La definición de API no usa nada específico para OpenTelemetry. Usa las API de .NET para la observabilidad.
4. Referencia a los paquetes de OpenTelemetry
Use el Administrador de paquetes NuGet o la línea de comandos para agregar los siguientes paquetes NuGet:
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
</ItemGroup>
Nota
Use las versiones más recientes, ya que las API de OTel evolucionan constantemente.
5. Configuración de OpenTelemetry con los proveedores correctos
var tracingOtlpEndpoint = builder.Configuration["OTLP_ENDPOINT_URL"];
var otel = builder.Services.AddOpenTelemetry();
// Configure OpenTelemetry Resources with the application name
otel.ConfigureResource(resource => resource
.AddService(serviceName: builder.Environment.ApplicationName));
// Add Metrics for ASP.NET Core and our custom metrics and export to Prometheus
otel.WithMetrics(metrics => metrics
// Metrics provider from OpenTelemetry
.AddAspNetCoreInstrumentation()
.AddMeter(greeterMeter.Name)
// Metrics provides by ASP.NET Core in .NET 8
.AddMeter("Microsoft.AspNetCore.Hosting")
.AddMeter("Microsoft.AspNetCore.Server.Kestrel")
.AddPrometheusExporter());
// Add Tracing for ASP.NET Core and our custom ActivitySource and export to Jaeger
otel.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddSource(greeterActivitySource.Name);
if (tracingOtlpEndpoint != null)
{
tracing.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
});
}
else
{
tracing.AddConsoleExporter();
}
});
Este código utiliza la instrumentación de ASP.NET Core para obtener métricas y actividades de ASP.NET Core. También registra los proveedores Metrics
y ActivitySource
para métricas y seguimiento respectivamente.
El código usa el exportador de Prometheus para las métricas, que usa ASP.NET Core para hospedar el punto de conexión, por lo que también debe agregar:
// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();
6. Ejecución el proyecto
Ejecute el proyecto y acceda a la API con el explorador o curl.
curl -k http://localhost:7275
Cada vez que solicite la página, incrementará el recuento del número de saludos que se han realizado. Puede acceder al punto de conexión de métricas mediante la misma dirección URL base, con la ruta de acceso /metrics
.
6.1 Salida del registro
Las instrucciones de registro del código se generan mediante ILogger
. De forma predeterminada, el proveedor de consola está habilitado para que la salida se dirija a la consola.
Existen un par de opciones para la salida de registros desde .NET:
- La salida de
stdout
ystderr
se redirige a archivos de registro por sistemas de contenedores como Kubernetes. - Con bibliotecas de registro que se integrarán con ILogger, se incluyen Serilog o NLog.
- El uso de proveedores de registro para OTel, como OTLP o el exportador de Azure Monitor, se muestra más adelante.
6.2 Acceso a las métricas
Puede acceder a las métricas mediante el punto de conexión /metrics
.
curl -k https://localhost:7275/
Hello World!
curl -k https://localhost:7275/metrics
# TYPE greetings_count counter
# HELP greetings_count Counts the number of greetings
greetings_count 1 1686894204856
# TYPE current_connections gauge
# HELP current_connections Number of connections that are currently active on the server.
current_connections{endpoint="127.0.0.1:7275"} 1 1686894204856
current_connections{endpoint="[::1]:7275"} 0 1686894204856
current_connections{endpoint="[::1]:5212"} 1 1686894204856
...
La salida de las métricas es una instantánea de las métricas en el momento en que se solicita el punto de conexión. Los resultados se proporcionan en formato de exposición Prometheus, que es legible pero mejor comprendido por Prometheus. Ese tema se trata en la siguiente fase.
6.3 Acceso al seguimiento
Si observa la consola del servidor, verá la salida del exportador de seguimiento de la consola, que genera la información en un formato legible. Esto debería mostrar dos actividades, una de la ActivitySource
personalizada y la otra de ASP.NET Core:
Activity.TraceId: 2e00dd5e258d33fe691b965607b91d18
Activity.SpanId: 3b7a891f55b97f1a
Activity.TraceFlags: Recorded
Activity.ParentSpanId: 645071fd0011faac
Activity.ActivitySourceName: OtPrGrYa.Example
Activity.DisplayName: GreeterActivity
Activity.Kind: Internal
Activity.StartTime: 2023-06-16T04:50:26.7675469Z
Activity.Duration: 00:00:00.0023974
Activity.Tags:
greeting: Hello World!
Resource associated with Activity:
service.name: OTel-Prometheus-Grafana-Jaeger
service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.5.0
Activity.TraceId: 2e00dd5e258d33fe691b965607b91d18
Activity.SpanId: 645071fd0011faac
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName: /
Activity.Kind: Server
Activity.StartTime: 2023-06-16T04:50:26.7672615Z
Activity.Duration: 00:00:00.0121259
Activity.Tags:
net.host.name: localhost
net.host.port: 7275
http.method: GET
http.scheme: https
http.target: /
http.url: https://localhost:7275/
http.flavor: 1.1
http.user_agent: curl/8.0.1
http.status_code: 200
Resource associated with Activity:
service.name: OTel-Prometheus-Grafana-Jaeger
service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.5.0
La primera es la actividad personalizada interna que creó. La segunda se crea mediante ASP.NET para la solicitud e incluye etiquetas para las propiedades de la solicitud HTTP. Verá que ambos tienen el mismo TraceId
, que identifica una sola transacción y en un sistema distribuido se puede usar para correlacionar los seguimientos de cada servicio implicado en una transacción. Los identificadores se transmiten como encabezados HTTP. ASP.NET Core asigna un TraceId
si no hay ninguno cuando recibe una solicitud. HttpClient
incluye los encabezados de forma predeterminada en las solicitudes salientes. Cada actividad tiene una SpanId
, que es la combinación de TraceId
y SpanId
que identifican de forma única cada actividad. La actividad Greeter
está emparentada con la actividad HTTP a través de su ParentSpanId
, que se asigna a la SpanId
de la actividad HTTP.
En una fase posterior, distribuirá estos datos en Jaeger para visualizar los seguimientos distribuidos.
7. Recopilación de métricas con Prometheus
Prometheus es un sistema de bases de datos de series temporales, agregación y recopilación de métricas. Puede configurarlo con los puntos de conexión de métricas para cada servicio y extrae periódicamente los valores y los almacena en su base de datos de serie temporal. Después, puede analizarlos y procesarlos según sea necesario.
Los datos de métricas que se exponen en formato Prometheus son una instantánea a un momento dado de las métricas del proceso. Cada vez que se realiza una solicitud al punto de conexión de métricas, notificará los valores actuales. Aunque los valores actuales son interesantes, se vuelven más valiosos en comparación con los valores históricos para ver tendencias y detectar si los valores son anómalos. Por lo general, los servicios tienen picos de uso en función de la hora del día o de ciertas épocas o momentos, como una temporada alta de compras navideñas. Al comparar los valores con respecto a las tendencias históricas, puede detectar si son anómalos o si una métrica se empeora lentamente con el tiempo.
El proceso no almacena ningún historial de estas instantáneas de métricas. Añadir esa capacidad al proceso podría requerir muchos recursos. Además, en un sistema distribuido es habitual tener varias instancias de cada nodo, por lo que conviene poder recopilar las métricas de todos ellos y luego agregarlas y compararlas con sus valores históricos.
7.1 Instalación y configuración de Prometheus
Descargue Prometheus para su plataforma desde https://prometheus.io/download/ y extraiga el contenido de la descarga.
Examine la parte superior de la salida del servidor en ejecución para obtener el número de puerto del punto de conexión http . Por ejemplo:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5212
Modifique el archivo de configuración de YAML de Prometheus para especificar el puerto del punto de conexión de extracción HTTP y establezca un intervalo de extracción inferior. Por ejemplo:
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
scrape_interval: 1s # poll very quickly for a more responsive demo
static_configs:
- targets: ["localhost:5212"]
Inicie Prometheus y busque en la salida el puerto en el que se ejecuta, normalmente 9090:
>prometheus.exe
...
ts=2023-06-16T05:29:02.789Z caller=web.go:562 level=info component=web msg="Start listening for connections" address=0.0.0.0:9090
Abra esta dirección URL en el explorador. En la interfaz de usuario de Prometheus, ahora debería poder consultar las métricas. Use el botón resaltado en la imagen siguiente para abrir el explorador de métricas, que muestra todas las métricas disponibles.
Seleccione la métrica greetings_count
para ver un gráfico de valores.
8. Uso de Grafana para crear un panel de métricas
Grafana es un producto de paneles que puede crear paneles y alertas basados en Prometheus u otros orígenes de datos.
Descargue e instale la versión del sistema operativo de Grafana de https://grafana.com/oss/grafana/ siguiendo las instrucciones de su plataforma. Una vez instalado, Grafana se ejecuta normalmente en el puerto 3000, por lo que se abre http://localhost:3000
en el explorador. Tendrá que iniciar sesión; el nombre de usuario y la contraseña predeterminados son admin
.
En el menú de hamburguesa, elija conexiones y escriba el texto prometheus
para seleccionar el tipo de punto de conexión. Seleccione Crear un origen de datos de Prometheus para agregar un nuevo origen de datos.
Debe establecer las siguientes propiedades:
- Dirección URL del servidor de Prometheus:
http://localhost:9090/
cambiando el puerto según corresponda
Seleccione Guardar y probar para comprobar la configuración.
Una vez que reciba un mensaje de confirmación, puede configurar un panel. Haga clic en el vínculo de creación de un panel que se muestra en la ventana emergente para el mensaje de confirmación.
Seleccione Agregar una visualización y elija el origen de datos de Prometheus que acaba de agregar como origen de datos.
Debería aparecer el diseñador del panel. En la mitad inferior de la pantalla, puede definir la consulta.
Seleccione la métrica greetings_count
y seleccione Ejecutar consultas para ver los resultados.
Con Grafana, puede diseñar paneles sofisticados que realizarán un seguimiento de cualquier número de métricas.
Cada métrica de .NET puede tener dimensiones adicionales, que son pares clave-valor que se pueden usar para particionar los datos. Todas las métricas de ASP.NET presentan una serie de dimensiones aplicables al contador. Por ejemplo, el contador current-requests
de Microsoft.AspNetCore.Hosting
tiene las siguientes dimensiones:
Atributo | Tipo | Descripción | Ejemplos | Presencia |
---|---|---|---|---|
method |
string |
Método de solicitud HTTP. | GET ; POST ; HEAD |
Siempre |
scheme |
string |
Esquema de URI que identifica el protocolo usado. | http ; https |
Siempre |
host |
string |
Nombre del servidor HTTP local que recibió la solicitud. | localhost |
Siempre |
port |
int |
Puerto del servidor HTTP local que recibió la solicitud. | 8080 |
Se agregó si no es el valor predeterminado (80 para http o 443 para https) |
Los gráficos de Grafana suelen particionarse en función de cada combinación única de dimensiones. Las dimensiones se pueden usar en las consultas de Grafana para filtrar o agregar los datos. Por ejemplo, si representa un gráfico de current_requests
, verá los valores particionados en función de cada combinación de dimensiones. Para filtrar solo en función del host, agregue una operación de Sum
y use host
como valor de etiqueta.
9. Seguimiento distribuido con Jaeger
En el paso 6, vio que la información de seguimiento distribuido se estaba exponiendo a la consola. Esta información realiza un seguimiento de las unidades de trabajo con actividades. La plataforma crea automáticamente algunas actividades, como la de ASP.NET para representar el control de una solicitud, y las bibliotecas y el código de la aplicación también pueden crear actividades. El ejemplo de saludos tiene una actividad Greeter
. Las actividades se correlacionan mediante las etiquetas TraceId
, SpanId
y ParentId
.
Cada proceso de un sistema distribuido genera su propio flujo de información de actividad y, al igual que las métricas, necesita un sistema para recopilar, almacenar y correlacionar las actividades para poder visualizar el trabajo realizado para cada transacción. Jaeger es un proyecto de código abierto para habilitar esta colección y visualización.
Descargue el archivo de distribución binaria más reciente de Jaeger para su plataforma desde https://www.jaegertracing.io/download/.
A continuación, extraiga la descarga en una ubicación local a la que sea fácil acceder. Ejecute el ejecutable jaeger-all-in-one(.exe):
./jaeger-all-in-one --collector.otlp.enabled
Examine la salida de la consola para buscar el puerto donde escucha el tráfico de OTLP a través de gRPC. Por ejemplo:
{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}
Esta salida indica que está escuchando en 0.0.0.0:4317
, por lo que puede configurar ese puerto como destino para el exportador de OTLP.
Abra el archivo AppSettings.json
de nuestro proyecto y agregue la siguiente línea, cambiando el puerto si procede.
"OTLP_ENDPOINT_URL" : "http://localhost:4317/"
Reinicie el proceso de saludo para que pueda recoger el cambio de propiedad y empezar a dirigir la información de seguimiento a Jaeger.
Ahora, debería poder ver la interfaz de usuario de Jaeger en http://localhost:16686/
desde un explorador web.
Para ver una lista de seguimientos, seleccione OTel-Prometheus-grafana-Jaeger
en la lista desplegable Servicio . Al hacer un seguimiento, saldrá un gráfico de Gantt de las actividades como parte de dicho seguimiento. Al hacer clic en cada una de las operaciones se muestran más detalles sobre la actividad.
En un sistema distribuido, es conveniente enviar las trazas de todos los procesos a la misma instalación de Jaeger para que pueda correlacionar las transacciones en todo el sistema.
Puede hacer que la aplicación sea un poco más interesante teniendo que realizar llamadas HTTP a sí misma.
Adición de una fábrica
HttpClient
a la aplicaciónbuilder.Services.AddHttpClient();
Agregar un nuevo punto de conexión para realizar llamadas de saludo anidadas
app.MapGet("/NestedGreeting", SendNestedGreeting);
Implemente el punto de conexión para que realice llamadas HTTP de las que también se puedan realizar seguimientos. En este caso, llama a sí mismo en un bucle artificial (realmente solo se aplica a escenarios de demostración).
async Task SendNestedGreeting(int nestlevel, ILogger<Program> logger, HttpContext context, IHttpClientFactory clientFactory) { // Create a new Activity scoped to the method using var activity = greeterActivitySource.StartActivity("GreeterActivity"); if (nestlevel <= 5) { // Log a message logger.LogInformation("Sending greeting, level {nestlevel}", nestlevel); // Increment the custom counter countGreetings.Add(1); // Add a tag to the Activity activity?.SetTag("nest-level", nestlevel); await context.Response.WriteAsync($"Nested Greeting, level: {nestlevel}\r\n"); if (nestlevel > 0) { var request = context.Request; var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}?nestlevel={nestlevel - 1}"); // Makes an http call passing the activity information as http headers var nestedResult = await clientFactory.CreateClient().GetStringAsync(url); await context.Response.WriteAsync(nestedResult); } } else { // Log a message logger.LogError("Greeting nest level {nestlevel} too high", nestlevel); await context.Response.WriteAsync("Nest level too high, max is 5"); } }
Esto da como resultado un gráfico más interesante con una forma piramidal para las solicitudes, ya que cada nivel espera la respuesta de la llamada anterior.