Thiết lập trình bổ trợ OpenTelemetry cơ bản trong gRPC C++

1. Giới thiệu

Trong lớp học lập trình này, bạn sẽ sử dụng gRPC để tạo một máy khách và máy chủ tạo thành nền tảng của một ứng dụng lập bản đồ tuyến đường được viết bằng C++.

Khi kết thúc hướng dẫn này, bạn sẽ có một ứng dụng HelloWorld gRPC đơn giản được trang bị plugin gRPC OpenTelemetry và có thể xem các chỉ số quan sát được xuất trong Prometheus.

Kiến thức bạn sẽ học được

  • Cách thiết lập OpenTelemetry Plugin cho ứng dụng gRPC C++ hiện có
  • Chạy một phiên bản Prometheus cục bộ
  • Xuất chỉ số sang Prometheus
  • Xem các chỉ số trên trang tổng quan Prometheus

2. Trước khi bắt đầu

Bạn cần có

  • git
  • curl
  • build-essential
  • clang
  • bazel để tạo các ví dụ trong lớp học lập trình này

Cài đặt các điều kiện tiên quyết:

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

Bạn có thể cài đặt bazel thông qua bazelisk. Bạn có thể xem phiên bản mới nhất tại đây.

Một cách đơn giản để thiết lập là cài đặt nó dưới dạng tệp nhị phân bazel trong PATH của bạn như sau:

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

Ngoài ra, bạn cũng có thể sử dụng CMake. Bạn có thể xem hướng dẫn về cách sử dụng CMake tại đây.

Lấy mã

Để đơn giản hoá quá trình học tập, lớp học lập trình này cung cấp một cấu trúc mã nguồn được tạo sẵn để giúp bạn bắt đầu. Các bước sau đây sẽ hướng dẫn bạn cách đo lường Trình bổ trợ OpenTelemetry gRPC trong một ứng dụng.

grpc-codelabs

Mã nguồn giàn giáo cho lớp học lập trình này có trong thư mục này trên GitHub. Nếu bạn không muốn tự triển khai mã, thì mã nguồn hoàn chỉnh có trong thư mục completed.

Trước tiên, hãy sao chép kho lưu trữ codelab grpc và chuyển đến thư mục grpc-cpp-opentelemetry:

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

Ngoài ra, bạn có thể tải tệp .zip chỉ chứa thư mục codelab xuống rồi giải nén theo cách thủ công.

Tạo thư viện gRPC bằng bazel:

bazel build start_here/...

3. Đăng ký trình bổ trợ OpenTelemetry

Chúng ta cần một ứng dụng gRPC để thêm trình bổ trợ gRPC OpenTelemetry. Trong lớp học lập trình này, chúng ta sẽ sử dụng một máy chủ và ứng dụng gRPC HelloWorld đơn giản mà chúng ta sẽ đo bằng trình bổ trợ OpenTelemetry gRPC.

Bước đầu tiên là đăng ký OpenTelemetry Plugin được định cấu hình bằng một trình xuất Prometheus trong ứng dụng. Mở codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_client.cc bằng trình chỉnh sửa yêu thích của bạn và chuyển đổi main() để có dạng như sau:

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

Bước tiếp theo là thêm trình bổ trợ OpenTelemetry vào máy chủ. Mở codelabs/grpc-cpp-opentelemetry/start_here/greeter_callback_server.cc và biến đổi main để có dạng như sau:

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

Các tệp tiêu đề và phần phụ thuộc bản dựng bắt buộc đã được thêm vào để thuận tiện cho bạn.

#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>

Các phần phụ thuộc bản dựng cũng đã được thêm vào tệp 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. Chạy ví dụ và xem các chỉ số

Để chạy máy chủ, hãy chạy –

bazel run start_here:greeter_callback_server

Nếu thiết lập thành công, bạn sẽ thấy kết quả sau đây cho máy chủ –

Server listening on 0.0.0.0:50051

Trong khi máy chủ đang chạy, trên một thiết bị đầu cuối khác, hãy chạy ứng dụng –

bazel run start_here:greeter_callback_client

Một lần chạy thành công sẽ có dạng như sau:

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

Vì chúng ta đã thiết lập trình bổ trợ gRPC OpenTelemetry để xuất các chỉ số bằng Prometheus. Các chỉ số đó sẽ có trên localhost:9464 cho máy chủ và localhost:9465 cho ứng dụng.

Cách xem các chỉ số về khách hàng –

curl localhost:9465/metrics

Kết quả sẽ có dạng –

# 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

Tương tự, đối với các chỉ số phía máy chủ –

curl localhost:9464/metrics

5. Xem các chỉ số trên Prometheus

Ở đây, chúng ta sẽ thiết lập một phiên bản prometheus để thu thập dữ liệu từ máy chủ và ứng dụng gRPC mẫu đang xuất các chỉ số bằng prometheus.

Tải bản phát hành mới nhất của Prometheus xuống cho nền tảng của bạn, sau đó giải nén và chạy bản phát hành đó:

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

Tạo một tệp cấu hình prometheus có nội dung sau:

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

Khởi động prometheus bằng cấu hình mới –

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

Thao tác này sẽ định cấu hình các chỉ số từ quy trình lớp học lập trình máy chủ và ứng dụng để được thu thập dữ liệu sau mỗi 5 giây.

Truy cập vào http://localhost:9090/graph để xem các chỉ số. Ví dụ: truy vấn –

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

sẽ cho thấy một biểu đồ có độ trễ trung bình của lượt thử bằng cách sử dụng 1 phút làm khung thời gian để tính toán phân vị.

Tỷ lệ truy vấn –

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (Không bắt buộc) Bài tập cho người dùng

Trong trang tổng quan prometheus, bạn sẽ nhận thấy QPS thấp. Hãy xem liệu bạn có thể xác định một số mã đáng ngờ trong ví dụ đang giới hạn QPS hay không.

Đối với những người có hứng thú, mã ứng dụng khách chỉ có một RPC đang chờ xử lý tại một thời điểm nhất định. Bạn có thể sửa đổi để ứng dụng gửi nhiều RPC hơn mà không cần đợi các RPC trước đó hoàn tất. (Chúng tôi chưa cung cấp giải pháp cho vấn đề này.)