範例:將 OpenTelemetry 與 Prometheus、Grafana 和 Jaeger 結合使用

此範例會將 Prometheus 用於計量收集、將 Grafana 用於建立儀表板,以及將 Jaeger 用來顯示分散式追蹤。

1. 建立專案

使用 Visual Studio 中的 ASP.NET Core Empty 範本或使用下列 .NET CLI 命令,建立簡單的 Web API 專案:

dotnet new web

2.新增計量和活動定義

下列程式碼會針對呼叫 API 的次數定義新的計量 (greetings.count),以及定義新的活動來源 (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.建立 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!";
}

注意

API 定義並未使用任何針對 OpenTelemetry 的具體內容。 其會針對可檢視性使用 .NET API。

4.參考 OpenTelemetry 套件

使用 NuGet 套件管理員或命令列來新增下列 NuGet 套件:

dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore --prerelease
dotnet add package OpenTelemetry.Exporter.Zipkin
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

5.使用正確的提供者設定 OpenTelemetry

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")
    // Metrics provided by System.Net libraries
    .AddMeter("System.Net.Http")
    .AddMeter("System.Net.NameResolution")
    .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();
    }
});

此程式碼會使用 ASP.NET Core 檢測,從 ASP.NET Core 取得計量和活動。 其也會針對計量和追蹤分別註冊 Metrics 和 ActivitySource 提供者。

程式碼會針對計量使用 Prometheus 匯出工具,其會使用 ASP.NET Core 來裝載端點,因此您也需要新增:

// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();

6.執行專案

執行專案,然後使用瀏覽器或 curl 存取 API。

curl -k http://localhost:7275

每次你申請該頁面時,問候次數都會增加。 你可以用相同的基礎網址(路徑 /metrics)存取指標端點。

6.1 記錄輸出

來自程式碼的記錄陳述式是使用 ILogger 的輸出。 根據預設,會啟用主控台提供者,以便將輸出導向至主控台。

有幾種方式可以讓日誌從 .NET 出口:

  • stdout 和 stderr 輸出是由 Kubernetes 等容器系統重新導向至記錄檔。
  • 使用與 ILogger 整合的日誌函式庫。 這些函式庫包括 Serilog 和 NLog。
  • 使用 OTel 日誌提供者,例如 OTLP 或後面展示的 Azure Monitor 匯出器。

6.2 存取計量

您可以使用 /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
...

計量輸出是要求端點時計量的快照集。 結果是以 Prometheus 說明格式提供,這是人類可讀取,但 Prometheus 更加了解的格式。 下一個階段將討論該主題。

6.3 存取追蹤

如果查看伺服器的主控台,您會看到主控台追蹤匯出工具的輸出,此匯出工具會以人類可讀取的格式輸出資訊。 這應該會顯示兩個活動,一個來自您的自訂 ActivitySource,另一個則來自 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

第一個是您建立的內部自訂活動。 第二個是由 ASP.NET 針對要求所建立的,其中包含 HTTP 要求屬性的標籤。 你會看到兩者有相同的 TraceId,這代表單一交易。 在分散式系統中,追蹤 ID 可用來關聯交易中各服務的追蹤。 ID 是以 HTTP 標頭的形式傳輸。 ASP.NET Core 會在收到要求時指派 TraceId (如果沒有的話)。 根據預設,HttpClient 會在輸出要求上包含標頭。 每個活動都有 SpanId,這是 TraceId 和 SpanId 的組合,專門識別每個活動。 Greeter 活動會透過其 ParentSpanId 成為 HTTP 活動的父代,這會對應至 HTTP 活動的 SpanId。

在稍後的階段中,您會將此資料饋送至Jaeger,以視覺化分散式追蹤。

7.使用 Prometheus 收集計量

Prometheus 是計量集合、彙總和時間序列資料庫系統。 您可以使用每個服務的計量端點對其進行設定,且其會定期抓取值,並將這些值儲存在時間序列資料庫中。 然後,您可以視需要分析和處理它們。

以 Prometheus 格式公開的計量資料是流程計量的時間點快照集。 每次向指標端點提出請求時,它會回報目前的數值。 雖然目前的值很有趣,但相較於歷史值,這些值會變得更有價值,可查看趨勢並偵測值是否異常。 通常,服務會根據一天中的時間或世界事件 (例如假日購物潮) 出現使用高峰。 透過將數值與歷史趨勢比較,你可以判斷這些指標是否異常,或是某項指標隨時間逐漸惡化。

流程不會儲存這些計量快照集的任何歷程記錄。 將該功能新增至流程可能會耗用大量資源。 此外,在分散式系統中,您通常具有每個節點的多個執行個體,因此您希望能夠從所有節點中收集計量,然後將其彙總並與其歷程記錄值進行比較。

7.1 安裝和設定 Prometheus

從 https://prometheus.io/download/ 下載適用於您平台的 Prometheus,並解壓縮下載內容。

看看你正在執行的伺服器輸出頂部,取得 HTTP 端點的埠號。 例如:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5212

修改 Prometheus YAML 組態檔,以指定 HTTP 抓取端點的連接埠,並設定較低的抓取間隔。 例如:

  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"]

啟動 Prometheus,並在輸出中尋找其執行所在的連接埠,通常是 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

在瀏覽器中開啟此 URL。 在 Prometheus 的 UI 中,你現在應該可以查詢你的指標。 使用下圖中的醒目提示按鈕來開啟計量總管,其中顯示所有可用的計量。

Prometheus 計量瀏覽器

選取 greetings_count 計量來查看值的圖表。

greetings_count 圖表

8.使用 Grafana 建立計量儀表板

Grafana 是一項儀表板產品,可根據 Prometheus 或其他資料來源建立儀表板和警示。

遵循您平台的指示,從 https://grafana.com/oss/grafana/ 下載並安裝 Grafana 的 OSS 版本。 一旦安裝,Grafana 通常就會在連接埠 3000 上執行,因此請在瀏覽器中開啟 http://localhost:3000。 你需要登入;預設的使用者名稱和密碼都是 admin。

在漢堡選單中選擇連接,然後輸入文字 prometheus 以選擇你的端點類型。 選取 [建立 Prometheus 資料來源] 以新增資料來源。

與 prometheus 的 Grafana 連線

將 Prometheus 伺服器網址設為 http://localhost:9090/,並依適用方式更改埠口。

選取 [儲存並測試] 以驗證設定。

一旦取得成功訊息,您就可以設定儀表板。 請選擇跳出視窗中顯示的 建立儀表板 連結以顯示成功訊息。

選取 [新增視覺效果],然後選擇您剛才新增為資料來源的 Prometheus 資料來源。

儀表板面板設計工具應該會出現。 在畫面的下半部,您可以定義查詢。

使用 greetings_count 的 Grafana 查詢

選取 greetings_count 計量,然後選取 [執行查詢] 以查看結果。

使用 Grafana,您可以設計複雜的儀表板,追蹤任意數目的計量。

.NET 中的每個計量都可以具有額外的維度,這些維度是可以用來分割資料的機碼值組。 ASP.NET 計量全都配有一些適用於計數器的維度。 例如,來自 current-requests 的 Microsoft.AspNetCore.Hosting 計數器具有下列維度:

屬性 類型 描述 範例 目前狀態
method string HTTP 要求方法。 GET  }, 永遠
scheme string 識別已用通訊協定的 URI 配置。 % 永遠
host string 收到要求的本機 HTTP 伺服器名稱。 localhost 永遠
port int 收到要求的本機 HTTP 伺服器連接埠。 8080 如果不是預設值,則新增 (80 用於 http 或 443 用於 https)

Grafana 中的圖表通常會根據每個唯一維度組合來分割。 維度可以用於 Grafana 查詢,以篩選或彙總資料。 例如,如果您繪製 current_requests 的圖表,則會看到根據每個維度組合分割的值。 若要僅根據主機進行篩選,請新增 Sum 的作業,並使用 host 做為標籤值。

主機的 Grafana current_requests

9.使用 Jaeger 的分散式追蹤

在步驟 6 中,您看到分散式追蹤資訊正在公開至主控台。 此資訊會追蹤工作單位和活動。 某些活動是由平台自動建立,例如 ASP.NET 建立的活動,代表要求的處理,而程式庫和應用程式程式碼也可以建立活動。 問候語範例具有 Greeter 活動。 活動會使用 TraceId、SpanId 和 ParentId 標籤相互關聯。

分散式系統中的每個流程都會產生自己的活動資訊串流,且與計量一樣,您需要系統來收集、儲存及相互關聯活動,才能視覺化針對每項交易完成的工作。 Jaeger 是一項開放原始碼專案,可啟用此集合和視覺效果。

從 https://www.jaegertracing.io/download/ 為您的平台下載 Jaeger 的最新二進位散發封存。

然後,將下載項目解壓縮到易於存取的本機位置。 執行 jaeger-all-in-one(.exe) 可執行檔:

./jaeger-all-in-one --collector.otlp.enabled

查看主控台輸出,以尋找其透過 gRPC 接聽 OTLP 流量的連接埠。 例如:

{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}

此輸出會告訴您其正在 0.0.0.0:4317 進行接聽,因此您可以將該連接埠設定為 OTLP 匯出工具的目的地。

為我們的專案開啟 AppSettings.json 檔案,然後新增下列一行,變更連接埠 (如果適用)。

"OTLP_ENDPOINT_URL" :  "http://localhost:4317/"

重新啟動問候語流程,以便其可以挑選屬性變更,並開始將追蹤資訊導向至 Jaeger。

現在,您應該能夠從網頁瀏覽器於 http://localhost:16686/ 看到 Jaeger UI。

Jaeger 追蹤查詢

若要查看追蹤清單,請從 [服務]OTel-Prometheus-grafana-Jaeger 下拉式清單中選取 。 選取一個追蹤應該會顯示該追蹤活動的甘特圖。 按一下每個作業會顯示更多有關活動的詳細資料。

Jaeger 作業詳細資料

在分散式系統中,您想要將追蹤從所有流程傳送至相同的 Jaeger 安裝,讓其可將整個系統的交易相互關聯。

您可以使應用程式自行進行 HTTP 呼叫,讓應用程式更有趣一點。

  • 在應用程式中新增工廠 HttpClient :

    builder.Services.AddHttpClient();
    
  • 新增一個端點來進行巢狀問候通話:

    app.MapGet("/NestedGreeting", SendNestedGreeting);
    
  • 實作此端點,讓其進行也可以追蹤的 HTTP 呼叫。 在這種情況下,它會以人工迴圈的方式回喚自己(實際上只適用於示範場景):

    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");
        }
    }
    

這會產生一個更有趣的圖表,其中包含要求的金字塔形狀,因為每一層都會等待上一個呼叫的回應。

Jaeger 巢狀相依性結果