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

1. מבוא

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

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

מה תלמדו

  • איך מגדירים את OpenTelemetry Plugin לאפליקציית gRPC Java קיימת
  • הפעלת מופע מקומי של Prometheus
  • ייצוא מדדים ל-Prometheus
  • הצגת מדדים ממרכז הבקרה של Prometheus

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

הדרישות

  • git
  • curl
  • JDK v8 ומעלה

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

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y git curl

קבל את הקוד

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

grpc-codelabs

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

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

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

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

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

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

השלב הראשון הוא לרשום את OpenTelemetry Plugin שהוגדר עם Prometheus exporter בלקוח. פותחים את codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryClient.java בעורך המועדף, ואז משנים את main כדי להוסיף קוד להגדרת gRPC Java OpenTelemetry API.

הגדרת מכשור בלקוח

יצירת כלי לייצוא נתונים בפורמט Prometheus

יוצרים PrometheusHttpServer כדי להמיר מדדים של OpenTelemetry לפורמט Prometheus ולחשוף אותם דרך HttpServer. בקטע הקוד הבא נוצר Prometheus Exporter חדש.

// Default prometheus port i.e `prometheusPort` has been initialized to 9465
 
PrometheusHttpServer prometheusExporter = PrometheusHttpServer.builder()
        .setPort(prometheusPort)         
        .build();

יצירת מופע OpenTelemetry SDK

רושמים את create prometheusExporter כ-MetricReader כדי לקרוא מדדים מ-SdkMeterProvider. המחלקות SdkMeterProvider משמשות להגדרת מדדים.

SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        .registerMetricReader(prometheusExporter)
        .build();

יוצרים מופע של OpenTelemetrySdk עם sdkMeterProvider שנוצר למעלה להטמעה של OpenTelemetry ב-SDK.

OpenTelemetrySdk openTelemetrySdk =OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .build();

יצירת מופע של GrpcOpenTelemetry

באמצעות GrpcOpenTelemetry API מגדירים את OpenTelemetry SDK שמשתמש ב-Prometheus Metric exporter.

GrpcOpenTelemetry grpcOpenTelmetry = GrpcOpenTelemetry.newBuilder()
        .sdk(openTelemetrySdk)
        .build();

// Registers gRPC OpenTelemetry globally.
grpcOpenTelmetry.registerGlobal();

אחרי שרושמים באופן גלובלי מופע GrpcOpenTelemetry באמצעות registerGlobal, כל הלקוחות והשרתים של gRPC שייווצרו לאחר מכן יצוידו ב-OpenTelemetry.

כיבוי של OpenTelemetry Sdk

הכיבוי צריך להתבצע בתוך ShutDownHook. ‫openTelemetrySdk.close() משבית את ה-SDK וגם קורא להשבתה ב-SdkMeterProvider.

הגדרת מכשור בשרת

באופן דומה, נוסיף את GrpcOpenTelemetry גם לשרת. פותחים את codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryServer.java ומוסיפים קוד כדי להפעיל את GrpcOpenTelemetry.

יצירת כלי לייצוא נתונים בפורמט Prometheus

יכול להיות שנריץ את ה-codelab הזה מאותה מכונה, ולכן אנחנו משתמשים ביציאה אחרת כדי לארח את המדדים בצד השרת של gRPC, כדי למנוע התנגשויות ביציאות בזמן יצירת PrometheusHttpServer.

// Default prometheus port i.e `prometheusPort` has been set to 9464

PrometheusHttpServer prometheusExporter = PrometheusHttpServer.builder()
        .setPort(prometheusPort)
        .build();

יצירת מופע OpenTelemetry SDK

SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
        .registerMetricReader(prometheusExporter)
        .build();

אתחול GrpcOpenTelemetry באמצעות OpenTelemetry SDK

OpenTelemetrySdk openTelemetrySdk =OpenTelemetrySdk.builder()
        .setMeterProvider(sdkMeterProvider)
        .build();

יצירת מופע של GrpcOpenTelemetry

GrpcOpenTelemetry grpcOpenTelmetry = GrpcOpenTelemetry.newBuilder()
        .sdk(openTelemetrySdk)
        .build();
    // Registers gRPC OpenTelemetry globally.
grpcOpenTelmetry.registerGlobal();

כיבוי של OpenTelemetry Sdk

אחרי שהערוץ gRPC מושבת. הפונקציה Calling openTelemetrySdk.close() משביתה את ה-SDK וגם קוראת להשבתה ב-SdkMeterProvider.

4. הרצת הדוגמה והצגת המדדים

כדי להריץ את השרת, מריצים את הפקודה –

cd start_here
../gradlew installDist
./build/install/start_here/bin/opentelemetry-server

אם ההגדרה הושלמה בהצלחה, הפלט של השרת ייראה כך:

[date and time] io.grpc.codelabs.opentelemetry.OpenTelemetryServer start
INFO: Server started, listening on 50051

בזמן שהשרת פועל, במסוף אחר, מריצים את הלקוח –

./build/install/start_here/bin/opentelemetry-client world

הרצה מוצלחת תיראה כך:

[date and time]io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Greeting: Hello world 
[date and time] io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Will try to greet world ...
[date and time]io.grpc.codelabs.opentelemetry.OpenTelemetryClient greet
INFO: Greeting: Hello world

הגדרנו את הפלאגין gRPC OpenTelemetry לייצוא מדדים באמצעות Prometheus. המדדים האלה יהיו זמינים בכתובת localhost:9464 לשרת ובכתובת localhost:9465 ללקוח.

כדי לראות את מדדי הלקוח –

curl localhost:9465/metrics

התוצאה תהיה מהצורה –

# HELP grpc_client_attempt_duration_seconds Time taken to complete a client call attempt
# TYPE grpc_client_attempt_duration_seconds histogram
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.002"} 0
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.003"} 2
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.004"} 14
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.005"} 29
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.1"} 33
grpc_client_attempt_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="+Inf"} 34
grpc_client_attempt_duration_seconds_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34
grpc_client_attempt_duration_seconds_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 0.46512665300000006
# 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_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
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-java",otel_scope_version="1.66.0"} 442.0
# HELP grpc_client_attempt_sent_total_compressed_message_size_bytes Compressed message bytes sent per client call attempt
# TYPE grpc_client_attempt_sent_total_compressed_message_size_bytes histogram
grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="1024.0"} 34
grpc_client_attempt_sent_total_compressed_message_size_bytes_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 238.0
# HELP grpc_client_attempt_started_total Number of client call attempts started
# TYPE grpc_client_attempt_started_total counter
grpc_client_attempt_started_total{grpc_method="helloworld.Greeter/SayHello",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34.0
# HELP grpc_client_call_duration_seconds Time taken by gRPC to complete an RPC from application's perspective
# TYPE grpc_client_call_duration_seconds histogram
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.0"} 0
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="0.003"} 2
grpc_client_call_duration_seconds_bucket{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0",le="+Inf"} 34
grpc_client_call_duration_seconds_count{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 34
grpc_client_call_duration_seconds_sum{grpc_method="helloworld.Greeter/SayHello",grpc_status="OK",grpc_target="dns:///localhost:50051",otel_scope_name="grpc-java",otel_scope_version="1.66.0"} 0.512708707
# TYPE target_info gauge
target_info{service_name="unknown_service:java",telemetry_sdk_language="java",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="1.40.0"} 1

באופן דומה, לגבי המדדים בצד השרת –

curl localhost:9464/metrics

5. הצגת מדדים ב-Prometheus

במאמר הזה נגדיר מופע של Prometheus שיבצע scraping של הלקוח והשרת לדוגמה של gRPC שמייצאים מדדים באמצעות Prometheus.

מורידים את הגרסה האחרונה של Prometheus לפלטפורמה שלכם, מחלצים אותה ומריצים אותה:

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

יוצרים קובץ תצורה של Prometheus עם הפרטים הבאים:

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

מפעילים את Prometheus עם ההגדרה החדשה –

./prometheus --config.file=grpc_otel_java_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 בלי לחכות להשלמת הבקשות הקודמות. (הפתרון לבעיה הזו לא סופק).