gRPC Java에서 기본 OpenTelemetry 플러그인 설정

1. 소개

이 Codelab에서는 gRPC를 사용하여 Java로 작성된 경로 매핑 애플리케이션의 기반을 형성하는 클라이언트와 서버를 만듭니다.

튜토리얼이 끝나면 gRPC OpenTelemetry 플러그인으로 계측된 간단한 gRPC HelloWorld 애플리케이션이 있고 Prometheus에서 내보낸 관측 가능성 측정항목을 볼 수 있습니다.

학습할 내용

  • 기존 gRPC Java 애플리케이션을 위한 OpenTelemetry 플러그인을 설정하는 방법
  • 로컬 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

이 Codelab의 스캐폴드 소스 코드는 이 GitHub 디렉터리에서 확인할 수 있습니다. 코드를 직접 구현하지 않으려면 완료된 디렉터리에서 완료된 소스 코드를 사용할 수 있습니다.

먼저 grpc codelab 저장소를 클론하고 grpc-java-opentelemetry 폴더로 cd합니다.

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

또는 Codelab 디렉터리만 포함된 .zip 파일을 다운로드하고 수동으로 압축을 풀 수 있습니다.

3. OpenTelemetry 플러그인 등록

gRPC OpenTelemetry 플러그인을 추가하려면 gRPC 애플리케이션이 필요합니다. 이 Codelab에서는 gRPC OpenTelemetry 플러그인으로 계측할 간단한 gRPC HelloWorld 클라이언트와 서버를 사용합니다.

첫 번째 단계는 클라이언트에서 Prometheus 내보내기 도구로 구성된 OpenTelemetry 플러그인을 등록하는 것입니다. 원하는 편집기로 codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryClient.java를 열고 main을 수정하여 gRPC Java OpenTelemetry API를 설정하는 코드를 추가합니다.

클라이언트에서 계측 설정

Prometheus 내보내기 도구 만들기

OpenTelemetry 측정항목을 Prometheus 형식으로 변환하고 HttpServer를 통해 노출하는 PrometheusHttpServer를 만듭니다. 다음 코드 스니펫은 새 Prometheus 내보내기 도구를 만듭니다.

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

OpenTelemetry SDK 인스턴스 만들기

위에서 만든 prometheusExporter를 MetricReader로 등록하여 SdkMeterProvider에서 측정항목을 읽습니다. SdkMeterProvider는 측정항목 설정을 구성하는 데 사용됩니다.

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

OpenTelemetry의 SDK 구현을 위해 위에서 만든 sdkMeterProvider로 OpenTelemetry의 OpenTelemetrySdk 인스턴스를 만듭니다.

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

GrpcOpenTelemetry 인스턴스 만들기

GrpcOpenTelemetry API를 사용하여 Prometheus 측정항목 내보내기 도구를 사용하는 OpenTelemetry SDK를 설정합니다.

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은 동일한 머신에서 실행될 수 있으므로 PrometheusHttpServer를 만드는 동안 포트 충돌을 방지하기 위해 gRPC 서버 측 측정항목을 호스팅하는 데 다른 포트를 사용합니다.

// 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();

OpenTelemetry SDK로 GrpcOpenTelemetry 초기화

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

GrpcOpenTelemetry 인스턴스 만들기

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

OpenTelemetry SDK 종료

gRPC 채널이 종료된 후 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

Prometheus를 사용하여 측정항목을 내보내도록 gRPC OpenTelemetry 플러그인을 설정했으므로 이러한 측정항목은 서버의 경우 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를 사용하여 측정항목을 내보내는 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]))

분위수 계산의 시간 창으로 1분을 사용하여 중간 시도 지연 시간이 있는 그래프를 표시합니다.

쿼리 속도

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (선택사항) 사용자 연습

Prometheus 대시보드에서 QPS가 낮다는 것을 알 수 있습니다. QPS를 제한하는 예시에서 의심스러운 코드를 식별할 수 있는지 확인합니다.

열성적인 사용자를 위해 클라이언트 코드는 특정 시점에 하나의 보류 중인 RPC만 갖도록 제한합니다. 클라이언트가 이전 RPC가 완료될 때까지 기다리지 않고 더 많은 RPC를 전송하도록 수정할 수 있습니다. (이 솔루션은 제공되지 않았습니다.)