הגדרה של פלאגין OpenTelemetry בסיסי ב-gRPC C++

1. מבוא

ב-Codelab הזה נשתמש ב-gRPC כדי ליצור לקוח ושרת שיהוו את הבסיס לאפליקציה למיפוי מסלולים שנכתבה ב-C++.

בסיום המדריך תהיה לכם אפליקציית gRPC HelloWorld פשוטה עם אינסטרומנטציה של הפלאגין gRPC OpenTelemetry, ותוכלו לראות את מדדי הניראות (observability) שמיוצאים ב-Prometheus.

מה תלמדו

  • איך מגדירים את הפלאגין OpenTelemetry באפליקציית gRPC C++‎ קיימת
  • הפעלת מכונה מקומית של Prometheus
  • ייצוא מדדים אל Prometheus
  • הצגת מדדים מלוח הבקרה של Prometheus

‫2. לפני שמתחילים

הדרישות

  • git
  • curl
  • build-essential
  • clang
  • bazel כדי לבנות דוגמאות ב-Codelab הזה

מתקינים את הדרישות המוקדמות:

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

אפשר להתקין את bazel דרך bazelisk. הגרסה העדכנית זמינה כאן.

דרך פשוטה להגדיר אותו היא להתקין אותו כקובץ הבינארי של bazel בנתיב שלכם באופן הבא:

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

אפשר גם להשתמש ב-CMake. הוראות לשימוש ב-CMake זמינות כאן.

קבל את הקוד

כדי לייעל את הלמידה, בשיעור Codelab הזה מוצעת מסגרת קוד מקור מוכנה מראש שתעזור לכם להתחיל. השלבים הבאים יעזרו לכם להוסיף אינסטרומנטציה לפלאגין gRPC OpenTelemetry באפליקציה.

grpc-codelabs

קוד המקור של ה-scaffold ל-codelab הזה זמין בספרייה הזו ב-GitHub. אם אתם מעדיפים לא להטמיע את הקוד בעצמכם, קוד המקור המלא זמין בספרייה completed.

קודם, משכפלים את מאגר ה-codelab של grpc ועוברים לתיקייה grpc-cpp-opentelemetry:

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

אפשרות אחרת היא להוריד את קובץ ה-‎ .zip שמכיל רק את ספריית ה-codelab ולבטל את הדחיסה שלו באופן ידני.

יוצרים את ספריית gRPC באמצעות Bazel:

bazel build start_here/...

3. רישום הפלאגין OpenTelemetry

כדי להוסיף את הפלאגין gRPC OpenTelemetry, צריך אפליקציית gRPC. בשיעור Codelab הזה נשתמש בלקוח ובשרת פשוטים של gRPC HelloWorld, ונוסיף להם אינסטרומנטציה באמצעות הפלאגין gRPC OpenTelemetry.

השלב הראשון הוא לרשום את הפלאגין OpenTelemetry שהוגדר עם כלי לייצוא נתונים של Prometheus בלקוח. פותחים את 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 ומשנים את main כך שייראה כך:

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

כדי שיהיה לכם נוח, כבר הוספנו את קובצי הכותרות ואת יחסי התלות ב-build שנדרשים.

#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 כבר נוספו לקובץ 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

הפעולה הזו תגדיר את המדדים מתהליכי ה-Codelab של הלקוח והשרת כך שהם ייגרדו כל 5 שניות.

כדי לראות את המדדים, עוברים אל http://localhost:9090/graph. לדוגמה, השאילתה –

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

יוצג תרשים עם חציון זמן האחזור של הניסיון, כשחלון הזמן לחישוב הכמותי הוא דקה אחת.

קצב השאילתות –

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (אופציונלי) תרגיל למשתמש

במרכזי הבקרה של Prometheus, תראו שערך השאילתות לשנייה נמוך. בודקים אם אפשר לזהות בקוד לדוגמה קוד חשוד שמגביל את מספר השאילתות לשנייה.

למי שרוצה להעמיק, קוד הלקוח מוגבל כך שרק בקשת RPC אחת יכולה להיות בהמתנה בכל רגע נתון. אפשר לשנות את ההגדרה כך שהלקוח ישלח יותר בקשות RPC בלי לחכות להשלמת הבקשות הקודמות. (לא סופק פתרון לבעיה הזו).