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

1. 소개

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

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

학습할 내용

  • 기존 gRPC C++ 애플리케이션을 위한 OpenTelemetry 플러그인 설정 방법
  • 로컬 Prometheus 인스턴스 실행
  • Prometheus로 측정항목 내보내기
  • Prometheus 대시보드에서 측정항목 보기

2. 시작하기 전에

필요한 항목

  • git
  • curl
  • build-essential
  • clang
  • 이 Codelab에서 예시를 빌드하는 bazel

다음 기본 요소를 설치합니다.

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

bazelbazelisk를 통해 설치할 수 있습니다. 최신 버전은 여기에서 확인할 수 있습니다.

간단한 설정 방법은 다음과 같이 PATH에 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

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

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

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

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

bazel을 사용하여 gRPC 라이브러리를 빌드합니다.

bazel build start_here/...

3. OpenTelemetry 플러그인 등록

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

첫 번째 단계는 클라이언트에서 Prometheus 내보내기 도구로 구성된 OpenTelemetry 플러그인을 등록하는 것입니다. 선호하는 편집기로 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;
}

필요한 헤더 파일과 빌드 종속 항목이 이미 편의를 위해 추가되었습니다.

#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 파일에 이미 추가되어 있습니다.

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

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

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

쿼리 비율 -

increase(grpc_client_attempt_duration_seconds_bucket[1m])

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

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

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