Esempio: usare OpenTelemetry con Prometheus, Grafana e Jaeger
Questo esempio utilizza Prometheus per la raccolta di metriche, Grafana per la creazione di una dashboard e Jaeger per visualizzare la traccia distribuita.
1. Creare il progetto
Creare un semplice progetto API Web usando il modello ASP.NET Core Empty in Visual Studio o il comando dell'interfaccia della riga di comando .NET CLI seguente:
dotnet new web
2. Aggiungere metriche e definizioni di attività
Il codice seguente definisce una nuova metrica (greetings.count
) per il numero di chiamate ricevute dall'API e una nuova origine attività (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. Creare un endpoint 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 definizione dell'API non usa elementi specifici di OpenTelemetry. Usa le API .NET per l'osservabilità.
4. Fare riferimento ai pacchetti OpenTelemetry
Usare Gestione pacchetti NuGet o la riga di comando per aggiungere i pacchetti NuGet seguenti:
<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
Usare le versioni più recenti, perché le API OTel sono in continua evoluzione.
5. Configurare OpenTelemetry con i provider corretti
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();
}
});
Questo codice usa strumentazione ASP.NET Core per ottenere metriche e attività da ASP.NET Core. Registra anche i provider Metrics
e ActivitySource
rispettivamente per le metriche e la traccia.
Il codice usa l'utilità di esportazione Prometheus per le metriche, che a sua volta usa ASP.NET Core per ospitare l'endpoint, quindi è necessario aggiungere:
// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();
6. Eseguire il progetto
Eseguire il progetto e quindi accedere all'API con il browser o il curl.
curl -k http://localhost:7275
Ogni volta che si richiede la pagina, il conteggio verrà incrementato per il numero di messaggi di saluto che sono stati effettuati. È possibile accedere all'endpoint delle metriche usando lo stesso URL di base, con il percorso /metrics
.
6.1 Output del log
Le istruzioni di registrazione del codice vengono restituite usando ILogger
. Per impostazione predefinita, il Provider console è abilitato in modo che l'output venga indirizzato alla console.
Sono disponibili due opzioni per la modalità di uscita dei log da .NET:
- L’output
stdout
estderr
viene reindirizzato ai file di log dai sistemi contenitore, ad esempio Kubernetes. - Usando le librerie di registrazione che si integreranno con ILogger, queste includono Serilog o NLog.
- Uso di provider di registrazione per OTel, ad esempio OTLP o l'utilità di esportazione di Monitoraggio di Azure, illustrata più avanti.
6.2 Accedere alle metriche
È possibile accedere alle metriche usando l'endpoint /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
...
L'output delle metriche è uno snapshot delle metriche effettuato al momento della richiesta dell'endpoint. I risultati vengono forniti in formato di esposizione Prometheus, un formato leggibile ma meglio compreso da Prometheus. Questo argomento viene trattato nella fase successiva.
6.3 Accedere alla traccia
Se si esamina la console per il server, verrà visualizzato l'output dell'utilità di esportazione di traccia della console, che restituisce le informazioni in un formato leggibile. Verranno visualizzate due attività, una dall'oggetto personalizzato ActivitySource
e l'altra da 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 prima è l'attività personalizzata interna creata. La seconda viene creata da ASP.NET per la richiesta e include tag per le proprietà della richiesta HTTP. Si noterà che entrambe hanno lo stesso TraceId
, che identifica una singola transazione e in un sistema distribuito può essere usato per correlare le tracce di ogni servizio coinvolto in una transazione. Gli ID vengono trasmessi come intestazioni HTTP. ASP.NET Core assegna un valore TraceId
se non ne è presente uno al momento della ricezione di una richiesta. HttpClient
include le intestazioni per impostazione predefinita nelle richieste in uscita. Ogni attività ha un SpanId
, che è la combinazione di TraceId
e SpanId
che identifica in modo univoco ogni attività. L'attività Greeter
è padre dell'attività HTTP tramite ParentSpanId
, che esegue il mapping all'oggetto SpanId
dell'attività HTTP.
In una fase successiva questi dati verranno inseriti in Jaeger per visualizzare le tracce distribuite.
7. Raccogliere metriche con Prometheus
Prometheus è una raccolta, un'aggregazione e un sistema di database a serie temporale di metriche. Viene configurato con gli endpoint delle metriche per ciascun servizio e archivia periodicamente i valori nel database a serie temporale. È quindi possibile analizzarli ed elaborarli in base alle esigenze.
I dati delle metriche esposti in formato Prometheus sono un’istantanea puntuale delle metriche del processo. Ogni volta che viene effettuata una richiesta all'endpoint delle metriche, verranno riportati i valori correnti. Pur essendo i valori correnti interessanti, essi acquistano maggiore importanza se confrontati con i valori nel tempo per vedere le tendenze e rilevare se vi sono anomalie. Di norma, i servizi hanno picchi di utilizzo in base all'ora del giorno o agli eventi mondiali, come lo shopping natalizio. Confrontando i valori con le tendenze temporali, è possibile rilevare se vi sono anomalie o se una metrica sta lentamente peggiorando nel tempo.
Il processo non archivia alcuna cronologia di queste istantanee delle metriche. L'aggiunta di tale funzionalità al processo potrebbe comportare un uso intensivo delle risorse. Inoltre, in un sistema distribuito si hanno comunemente più istanze di ogni nodo, quindi si vuole poter raccogliere le metriche da ciascuna di esse per poi aggregarle e confrontarle con i loro valori storici.
7.1 Installare e configurare Prometheus
Scaricare Prometheus nella versione adatta per la propria piattaforma da https://prometheus.io/download/ ed estrarre il contenuto del download.
Esaminare la parte superiore dell'output del server in esecuzione per ottenere il numero di porta per l'endpoint http. Ad esempio:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5212
Modificare il file di configurazione YAML di Prometheus per specificare la porta per l'endpoint di scorporo HTTP e impostare un intervallo di scorporo inferiore. Ad esempio:
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"]
Avviare Prometheus e cercare nell'output la porta in cui è in esecuzione, in genere è 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
Aprire questo URL nel browser. Nell'interfaccia utente di Prometheus dovrebbe ora essere possibile eseguire query per le metriche. Usare il pulsante evidenziato nell'immagine seguente per aprire Esplora metriche, che mostra tutte le metriche disponibili.
Selezionare la metrica greetings_count
per visualizzare un grafico di valori.
8. Usare Grafana per creare una dashboard delle metriche
Grafana è un prodotto di dashboarding grado di creare dashboard e avvisi basati su Prometheus o altre origini dati.
Scaricare e installare la versione OSS di Grafana da https://grafana.com/oss/grafana/ seguendo le istruzioni per la propria piattaforma. Una volta installato, Grafana viene in genere eseguito sulla porta 3000, quindi aprire http://localhost:3000
nel browser. Sarà necessario effettuare l'accesso; il nome utente e la password predefiniti sono entrambi admin
.
Dal menu hamburger scegliere le connessioni, quindi immettere il testo prometheus
per selezionare il tipo di endpoint. Selezionare Crea un'origine dati Prometheus per aggiungere una nuova origine dati.
È necessario impostare le proprietà seguenti:
- URL del server Prometheus:
http://localhost:9090/
modifica della porta in base alle esigenze
Selezionare Salva e test per verificare la configurazione.
Dopo aver visualizzato un messaggio di operazione riuscita, è possibile configurare una dashboard. Fare clic sul collegamento compilazione di una dashboard visualizzato nella finestra popup per il messaggio di operazione riuscita.
Selezionare Aggiungi una visualizzazione, quindi scegliere l'origine dati Prometheus appena aggiunta come origine dati.
Verrà visualizzata la finestra di progettazione del pannello della dashboard. Nella metà inferiore dello schermo è possibile definire la query.
Selezionare la metrica greetings_count
, quindi selezionare Esegui query per visualizzare i risultati.
Con Grafana è possibile progettare dashboard sofisticate che tengono traccia di qualsiasi numero di metriche.
Ogni metrica in .NET può avere dimensioni aggiuntive, ovvero coppie chiave-valore che possono essere usate per partizionare i dati. Le metriche ASP.NET dispongono tutte di una serie di dimensioni applicabili al contatore. Ad esempio, il contatore current-requests
di Microsoft.AspNetCore.Hosting
ha le dimensioni seguenti:
Attributo | Tipo | Descrizione | Esempi | Presenza |
---|---|---|---|---|
method |
string |
Metodo della richiesta HTTP. | GET ; POST ; HEAD |
Sempre |
scheme |
string |
Schema URI che identifica il protocollo usato. | http ; https |
Sempre |
host |
string |
Nome del server HTTP locale che ha ricevuto la richiesta. | localhost |
Sempre |
port |
int |
Porta del server HTTP locale che ha ricevuto la richiesta. | 8080 |
Aggiunta se non è predefinita (80 per http o 443 per https) |
I grafici in Grafana vengono in genere partizionati in base a ogni combinazione univoca di dimensioni. Le dimensioni possono essere usate nelle query Grafana per filtrare o aggregare i dati. Ad esempio, se si esegue un grafico current_requests
, si vedranno i valori suddivisi in base a ciascuna combinazione di dimensioni. Per filtrare solo in base all'host, aggiungere un'operazione di Sum
e usare host
come valore dell'etichetta.
9. Traccia distribuita con Jaeger
Nel passaggio 6 si è visto che le informazioni di traccia distribuite sono state esposte alla console. Tali informazioni tengono traccia delle unità di lavoro con le attività. Alcune attività vengono create automaticamente dalla piattaforma, ad esempio quella di ASP.NET per rappresentare la gestione di una richiesta, e le librerie e il codice dell'applicazione possono anch’esse creare delle attività. L'esempio di saluto ha un'attività Greeter
. Le attività vengono correlate usando i tag TraceId
, SpanId
e ParentId
.
Ogni processo in un sistema distribuito produce un proprio flusso di informazioni sulle attività e, al pari delle metriche, è necessario un sistema che raccolga, archivi e correli le attività per poter visualizzare il lavoro svolto per ogni singola transazione. Jaeger è un progetto open source in grado di abilitare questa raccolta e visualizzazione.
Scaricare l'archivio di distribuzione binaria più recente di Jaeger per la propria piattaforma da https://www.jaegertracing.io/download/.
Estrarre quindi il download in un percorso locale accessibile facilmente. Eseguire il file eseguibile jaeger-all-in-one(.exe):
./jaeger-all-in-one --collector.otlp.enabled
Esaminare l'output della console per trovare la porta in cui è in ascolto del traffico OTLP tramite gRPC. Ad esempio:
{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}
Questo output indica che è in ascolto su 0.0.0.0:4317
: in questo modo è possibile configurare tale porta come destinazione per l'utilità di esportazione OTLP.
Aprire il file AppSettings.json
dedicato al progetto e aggiungere la riga seguente, modificando la porta, se del caso.
"OTLP_ENDPOINT_URL" : "http://localhost:4317/"
Riavviare il processo greeter in modo che possa raccogliere la modifica della proprietà e iniziare a indirizzare le informazioni di traccia a Jaeger.
A questo punto, dovrebbe essere possibile visualizzare l'interfaccia utente Jaeger in http://localhost:16686/
da un browser Web.
Per visualizzare un elenco di tracce, selezionare OTel-Prometheus-grafana-Jaeger
dall'elenco a discesa Servizio. La selezione di una traccia dovrebbe mostrare un diagramma di Gantt delle attività, come parte di tale traccia. Facendo clic su ognuna delle operazioni vengono visualizzati altri dettagli sull'attività.
In un sistema distribuito si vogliono inviare tracce da tutti i processi alla stessa installazione di Jaeger in modo che possa correlare le transazioni nel sistema.
È possibile rendere l'app un po' più interessante facendo in modo che faccia chiamate HTTP a se stessa.
Aggiungere una factory
HttpClient
all'applicazionebuilder.Services.AddHttpClient();
Aggiungere un nuovo endpoint per effettuare chiamate di saluto annidate
app.MapGet("/NestedGreeting", SendNestedGreeting);
Implementare l'endpoint in modo da effettuare chiamate HTTP che possano essere anche tracciate. In questo caso, richiama se stessa in un ciclo artificiale (applicabile solo agli scenari demo).
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"); } }
Ciò comporta un grafico più interessante con una forma a piramide per le richieste, in quanto ogni livello attende la risposta dalla chiamata precedente.