在 gRPC C++ 中設定基本 OpenTelemetry 外掛程式

1. 簡介

在本程式碼研究室中,您將使用 gRPC 建立用戶端和伺服器,做為以 C++ 編寫的路線對應應用程式基礎。

完成本教學課程後,您將擁有一個簡單的 gRPC HelloWorld 應用程式,並使用 gRPC OpenTelemetry 外掛程式進行檢測,還能在 Prometheus 中查看匯出的可觀測性指標。

課程內容

  • 如何為現有的 gRPC C++ 應用程式設定 OpenTelemetry 外掛程式
  • 執行本機 Prometheus 執行個體
  • 將指標匯出至 Prometheus
  • 查看 Prometheus 資訊主頁的指標

2. 事前準備

軟硬體需求

  • git
  • curl
  • build-essential
  • clang
  • bazel,在本程式碼研究室中建構範例

設定前置條件:

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y git curl build-essential clang

bazel 可以透過 bazelisk 安裝。如要查看最新版本,請前往這個頁面

如要輕鬆完成設定,請按照下列步驟,將其安裝為 PATH 中的 bazel 二進位檔:

sudo cp bazelisk-linux-amd64 /usr/local/bin/bazel
sudo chmod a+x /usr/local/bin/bazel

或者,您也可以使用 CMake。如需使用 CMake 的操作說明,請參閱這篇文章

取得程式碼

為簡化學習過程,本程式碼研究室提供預先建構的原始碼架構,協助您踏出第一步。下列步驟會引導您在應用程式中檢測 gRPC OpenTelemetry 外掛程式。

grpc-codelabs

本程式碼研究室的架構原始碼位於這個 GitHub 目錄。如果您不想自行導入程式碼,可以前往 completed 目錄查看已完成的原始碼。

首先,請複製 grpc Codelab 存放區,然後 cd 到 grpc-cpp-opentelemetry 資料夾:

git clone https://github.com/grpc-ecosystem/grpc-codelabs.git
cd grpc-codelabs/codelabs/grpc-cpp-opentelemetry/

或者,您也可以下載只包含 Codelab 目錄的 .zip 檔案,然後手動解壓縮。

使用 bazel 建構 gRPC 程式庫:

bazel build start_here/...

3. 註冊 OpenTelemetry 外掛程式

我們需要 gRPC 應用程式來新增 gRPC OpenTelemetry 外掛程式。在本程式碼研究室中,我們將使用簡單的 gRPC HelloWorld 用戶端和伺服器,並透過 gRPC OpenTelemetry 外掛程式進行檢測。

首先,請在用戶端中註冊已設定 Prometheus 匯出器的 OpenTelemetry 外掛程式。使用您慣用的編輯器開啟 codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_client.cc,然後將 main() 轉換成如下所示的內容:

int main(int argc, char **argv) {
  absl::ParseCommandLine(argc, argv);

  // Codelab Solution: Register a global gRPC OpenTelemetry plugin configured
  // with a prometheus exporter.
  opentelemetry::exporter::metrics::PrometheusExporterOptions opts;
  opts.url = absl::GetFlag(FLAGS_prometheus_endpoint);
  auto prometheus_exporter =
      opentelemetry::exporter::metrics::PrometheusExporterFactory::Create(opts);
  auto meter_provider =
      std::make_shared<opentelemetry::sdk::metrics::MeterProvider>();

  // The default histogram boundaries are not granular enough for RPCs. Override
  // the "grpc.client.attempt.duration" view as recommended by
  // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md.
  AddLatencyView(meter_provider.get(), "grpc.client.attempt.duration", "s");
  meter_provider->AddMetricReader(std::move(prometheus_exporter));
  auto status = grpc::OpenTelemetryPluginBuilder()
                    .SetMeterProvider(std::move(meter_provider))
                    .BuildAndRegisterGlobal();
  if (!status.ok()) {
    std::cerr << "Failed to register gRPC OpenTelemetry Plugin: "
              << status.ToString() << std::endl;
    return static_cast<int>(status.code());
  }

  // Continuously send RPCs.
  RunClient(absl::GetFlag(FLAGS_target));

  return 0;
}

下一步是在伺服器中新增 OpenTelemetry 外掛程式。開啟 codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_server.cc,並將主要內容轉換成如下所示:

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  // Register a global gRPC OpenTelemetry plugin configured with a prometheus
  // exporter.
  opentelemetry::exporter::metrics::PrometheusExporterOptions opts;
  opts.url = absl::GetFlag(FLAGS_prometheus_endpoint);
  auto prometheus_exporter =
      opentelemetry::exporter::metrics::PrometheusExporterFactory::Create(opts);
  auto meter_provider =
      std::make_shared<opentelemetry::sdk::metrics::MeterProvider>();

  // The default histogram boundaries are not granular enough for RPCs. Override
  // the "grpc.server.call.duration" view as recommended by
  // https://github.com/grpc/proposal/blob/master/A66-otel-stats.md.
  AddLatencyView(meter_provider.get(), "grpc.server.call.duration", "s");
  meter_provider->AddMetricReader(std::move(prometheus_exporter));
  auto status = grpc::OpenTelemetryPluginBuilder()
                    .SetMeterProvider(std::move(meter_provider))
                    .BuildAndRegisterGlobal();
  if (!status.ok()) {
    std::cerr << "Failed to register gRPC OpenTelemetry Plugin: "
              << status.ToString() << std::endl;
    return static_cast<int>(status.code());
  }
  RunServer(absl::GetFlag(FLAGS_port));
  return 0;
}

為方便起見,系統已新增必要的標頭檔案和建構依附元件。

#include "opentelemetry/exporters/prometheus/exporter_factory.h"
#include "opentelemetry/exporters/prometheus/exporter_options.h"
#include "opentelemetry/sdk/metrics/meter_provider.h"

#include <grpcpp/ext/otel_plugin.h>

建構依附元件也已新增至 BUILD 檔案 -

cc_binary(
    name = "greeter_callback_client",
    srcs = ["greeter_callback_client.cc"],
    defines = ["BAZEL_BUILD"],
    deps = [
        "//util:util",
        "@com_github_grpc_grpc//:grpc++",
        "@com_github_grpc_grpc//:grpcpp_otel_plugin",
        "@com_google_absl//absl/flags:flag",
        "@com_google_absl//absl/flags:parse",
        "@io_opentelemetry_cpp//exporters/prometheus:prometheus_exporter",
        "@io_opentelemetry_cpp//sdk/src/metrics",
    ],
)

4. 執行範例並查看指標

如要執行伺服器,請執行下列指令:

bazel run start_here:greeter_callback_server

設定成功後,伺服器會輸出下列內容:

Server listening on 0.0.0.0:50051

伺服器執行期間,在另一個終端機中執行用戶端:

bazel run start_here:greeter_callback_client

成功執行的結果如下所示:

Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world
Greeter received: Hello world

我們已設定 gRPC OpenTelemetry 外掛程式,使用 Prometheus 匯出指標。這些指標會顯示在伺服器的 localhost:9464 和用戶端的 localhost:9465。

如要查看用戶端指標,請按照下列步驟操作:

curl localhost:9465/metrics

結果會採用以下形式:

# HELP exposer_transferred_bytes_total Transferred bytes to metrics services
# TYPE exposer_transferred_bytes_total counter
exposer_transferred_bytes_total 0
# HELP exposer_scrapes_total Number of times metrics were scraped
# TYPE exposer_scrapes_total counter
exposer_scrapes_total 0
# HELP exposer_request_latencies Latencies of serving scrape requests, in microseconds
# TYPE exposer_request_latencies summary
exposer_request_latencies_count 0
exposer_request_latencies_sum 0
exposer_request_latencies{quantile="0.5"} Nan
exposer_request_latencies{quantile="0.9"} Nan
exposer_request_latencies{quantile="0.99"} Nan
# HELP target Target metadata
# TYPE target gauge
target_info{otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",service_name="unknown_service",telemetry_sdk_version="1.13.0",telemetry_sdk_name="opentelemetry",telemetry_sdk_language="cpp"} 1 1721958543107
# HELP grpc_client_attempt_rcvd_total_compressed_message_size_bytes Compressed message bytes received per call attempt
# TYPE grpc_client_attempt_rcvd_total_compressed_message_size_bytes histogram
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev"} 1248 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="0"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="5"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="10"} 0 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="25"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="50"} 96 1721958543107
grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-c++",otel_scope_version="1.67.0-dev",le="75"} 96 1721958543107

同樣地,伺服器端指標也是如此:

curl localhost:9464/metrics

5. 在 Prometheus 上查看指標

在這裡,我們將設定 Prometheus 執行個體,用於擷取 gRPC 範例用戶端和伺服器,這些用戶端和伺服器會使用 Prometheus 匯出指標。

下載適用於您平台的最新版 Prometheus,然後解壓縮並執行:

tar xvfz prometheus-*.tar.gz
cd prometheus-*

建立 Prometheus 設定檔,並加入下列內容:

cat > grpc_otel_cpp_prometheus.yml <<EOF
scrape_configs:
  - job_name: "prometheus"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: "grpc-otel-cpp"
    scrape_interval: 5s
    static_configs:
      - targets: ["localhost:9464", "localhost:9465"]
EOF

使用新設定啟動 Prometheus -

./prometheus --config.file=grpc_otel_cpp_prometheus.yml

這會設定每 5 秒擷取一次用戶端和伺服器 Codelab 程序中的指標。

前往 http://localhost:9090/graph 查看指標。例如,查詢 -

histogram_quantile(0.5, rate(grpc_client_attempt_duration_seconds_bucket[1m]))

會顯示圖表,其中以 1 分鐘做為分位數計算的時間視窗,顯示嘗試次數的中位數延遲時間。

查詢率 -

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (選用) 使用者練習

在 Prometheus 資訊主頁中,您會發現 QPS 偏低。看看您是否能在範例中找出限制 QPS 的可疑程式碼。

對於熱衷於此的開發人員,用戶端程式碼會限制在特定時間點只能有一個待處理的 RPC。您可以修改這項設定,讓用戶端傳送更多 RPC,而不必等待先前的 RPC 完成。(這項問題的解決方案尚未提供)。