Condividi tramite


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 e stderr 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.

Esplora metriche Prometheus

Selezionare la metrica greetings_count per visualizzare un grafico di valori.

Grafico delle greetings_count

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.

Connessione Grafana a Prometheus

È 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.

Query Grafana con greetings_count

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.

Grafana current_requests dall'host

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.

Query Jaeger per le tracce

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à.

Dettagli operazione Jaeger

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'applicazione

    builder.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.

Risultati delle dipendenze annidate Jaeger