範例:將 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 套件:
<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>
注意
使用最新版本,因為 OTel API 會不斷地發展。
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")
.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
每次您要求頁面時,都會針對已發出的問候語數目增加計數。 您可以使用相同的基礎 URL,搭配路徑 /metrics
存取計量端點。
6.1 記錄輸出
來自程式碼的記錄陳述式是使用 ILogger
的輸出。 根據預設,會啟用主控台提供者,以便將輸出導向至主控台。
對於如何從 .NET 輸出記錄,有幾個選項:
stdout
和stderr
輸出是由 Kubernetes 等容器系統重新導向至記錄檔。- 使用將與 ILogger 整合的記錄程式庫,其中包括 Serilog 或 NLog。
- 使用 OTel 的記錄提供者,例如 OTLP 或 Azure 監視器匯出工具,如下所示。
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 是以 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 中,您現在應該能夠查詢計量。 使用下圖中的醒目提示按鈕來開啟計量總管,其中顯示所有可用的計量。
選取 greetings_count
計量來查看值的圖表。
8.使用 Grafana 建立計量儀表板
Grafana 是一項儀表板產品,可根據 Prometheus 或其他資料來源建立儀表板和警示。
遵循您平台的指示,從 https://grafana.com/oss/grafana/ 下載並安裝 Grafana 的 OSS 版本。 一旦安裝,Grafana 通常就會在連接埠 3000 上執行,因此請在瀏覽器中開啟 http://localhost:3000
。 您必須登入;預設的使用者名稱與密碼都是 admin
。
從漢堡功能表中選擇連線,然後輸入文字 prometheus
以選取您的端點類型。 選取 [建立 Prometheus 資料來源] 以新增資料來源。
您必須設定下列屬性:
- Prometheus 伺服器 URL:
http://localhost:9090/
,視需要變更連接埠
選取 [儲存並測試] 以驗證設定。
一旦取得成功訊息,您就可以設定儀表板。 按兩下快顯畫面中顯示的 [正在建置儀表板] 連結,以取得成功訊息。
選取 [新增視覺效果],然後選擇您剛才新增為資料來源的 Prometheus 資料來源。
儀表板面板設計工具應該會出現。 在畫面的下半部,您可以定義查詢。
選取 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
做為標籤值。
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。
若要查看追蹤清單,請從 [服務] 下拉式清單中選取 OTel-Prometheus-grafana-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"); } }
這會產生一個更有趣的圖表,其中包含要求的金字塔形狀,因為每一層都會等待上一個呼叫的回應。