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

1. מבוא

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

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

מה תלמדו

  • איך מגדירים את הפלאגין OpenTelemetry לאפליקציית 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 וגם קוראת לפונקציה shutdown ב-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 בלי לחכות להשלמת הבקשות הקודמות. (לא סופק פתרון לבעיה הזו).