Configura el complemento básico de OpenTelemetry en gRPC Java

1. Introducción

En este codelab, usarás gRPC para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Java.

Al final del instructivo, tendrás una aplicación simple de gRPC HelloWorld instrumentada con el complemento de OpenTelemetry de gRPC y podrás ver las métricas de observabilidad exportadas en Prometheus.

Qué aprenderás

  • Cómo configurar el complemento de OpenTelemetry para una aplicación existente de gRPC Java
  • Ejecuta una instancia local de Prometheus
  • Exporta métricas a Prometheus
  • Visualiza las métricas del panel de Prometheus

2. Antes de comenzar

Requisitos

  • git
  • curl
  • JDK v8 o versiones posteriores

Instala los requisitos previos:

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

Obtén el código

Para optimizar tu aprendizaje, este codelab ofrece un código fuente precompilado que te ayudará a comenzar. Los siguientes pasos te guiarán para instrumentar el complemento de OpenTelemetry de gRPC en una aplicación.

grpc-codelabs

El código fuente del andamio para este codelab está disponible en este directorio de GitHub. Si prefieres no implementar el código por tu cuenta, el código fuente completo está disponible en el directorio completed.

Primero, clona el repo del codelab de gRPC y cambia el directorio a la carpeta grpc-java-opentelemetry:

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

También puedes descargar el archivo .zip que contiene solo el directorio del codelab y descomprimirlo de forma manual.

3. Cómo registrar el complemento de OpenTelemetry

Necesitamos una aplicación de gRPC para agregar el complemento de OpenTelemetry de gRPC. En este codelab, usaremos un cliente y un servidor simples de gRPC HelloWorld que instrumentaremos con el complemento de gRPC OpenTelemetry.

El primer paso es registrar el complemento de OpenTelemetry configurado con un exportador de Prometheus en el cliente. Abre codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryClient.java con tu editor favorito y, luego, modifica main para agregar código para configurar la API de OpenTelemetry de gRPC Java.

Configura la instrumentación en el cliente

Crea el exportador de Prometheus

Crea un PrometheusHttpServer para convertir las métricas de OpenTelemetry al formato de Prometheus y exponerlas a través de un HttpServer. El siguiente fragmento de código crea un nuevo Prometheus Exporter.

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

Crea una instancia del SDK de OpenTelemetry

Registra el objeto prometheusExporter creado anteriormente como MetricReader para leer métricas de un SdkMeterProvider. SdkMeterProvider se usa para configurar los parámetros de configuración de las métricas.

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

Crea una instancia de OpenTelemetrySdk con el sdkMeterProvider creado anteriormente para la implementación del SDK de OpenTelemetry.

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

Crea una instancia de GrpcOpenTelemetry

Con la API de GrpcOpenTelemetry, establece el SDK de OpenTelemetry que usa el exportador de métricas de Prometheus.

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

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

Una vez que se registra una instancia de GrpcOpenTelemetry de forma global con registerGlobal, todos los clientes y servidores de gRPC creados posteriormente se instrumentarán con OpenTelemetry.

Apaga el SDK de OpenTelemetry

El cierre debe ocurrir dentro de ShutDownHook. openTelemetrySdk.close() cierra el SDK y también llama a shutdown en SdkMeterProvider.

Configura la instrumentación en el servidor

Del mismo modo, agreguemos GrpcOpenTelemetry al servidor también. Abre codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryServer.java y agrega código para inicializar GrpcOpenTelemetry.

Crea el exportador de Prometheus

Dado que este codelab podría ejecutarse desde la misma máquina, usamos un puerto diferente para alojar las métricas del servidor de gRPC y evitar conflictos de puertos mientras creamos PrometheusHttpServer.

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

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

Crea una instancia del SDK de OpenTelemetry

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

Inicializa GrpcOpenTelemetry con el SDK de OpenTelemetry

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

Crea una instancia de GrpcOpenTelemetry

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

Apaga el SDK de OpenTelemetry

Se llama después de que se cierra el canal de gRPC. Llamar a openTelemetrySdk.close() cierra el SDK y también llama a shutdown en SdkMeterProvider.

4. Cómo ejecutar el ejemplo y ver las métricas

Para ejecutar el servidor, ejecuta el siguiente comando:

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

Si la configuración se realiza correctamente, verás el siguiente resultado para el servidor:

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

Mientras el servidor se ejecuta, en otra terminal, ejecuta el cliente:

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

Una ejecución correcta se verá de la siguiente manera:

[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

Dado que configuramos el complemento de OpenTelemetry de gRPC para exportar métricas con Prometheus. Esas métricas estarán disponibles en localhost:9464 para el servidor y en localhost:9465 para el cliente.

Para ver las métricas del cliente, haz lo siguiente:

curl localhost:9465/metrics

El resultado tendría la siguiente forma:

# 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

Del mismo modo, para las métricas del servidor, haz lo siguiente:

curl localhost:9464/metrics

5. Visualiza métricas en Prometheus

Aquí, configuraremos una instancia de Prometheus que extraerá nuestro cliente y servidor de ejemplo de gRPC que exportan métricas con Prometheus.

Descarga la versión más reciente de Prometheus para tu plataforma, y luego extráela y ejecútala:

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

Crea un archivo de configuración de Prometheus con lo siguiente:

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

Inicia Prometheus con la nueva configuración:

./prometheus --config.file=grpc_otel_java_prometheus.yml

Esto configurará las métricas de los procesos del codelab del cliente y del servidor para que se extraigan cada 5 segundos.

Ve a http://localhost:9090/graph para ver las métricas. Por ejemplo, la siguiente consulta:

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

mostrará un gráfico con la latencia mediana de intentos usando 1 minuto como período para el cálculo del cuantil.

Tasa de consultas:

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. Ejercicio opcional para el usuario

En los paneles de Prometheus, observarás que los QPS son bajos. Intenta identificar código sospechoso en el ejemplo que limita las QPS.

Para los entusiastas, el código del cliente se limita a tener solo un RPC pendiente en un momento determinado. Esto se puede modificar para que el cliente envíe más RPC sin esperar a que se completen las anteriores. (No se proporcionó la solución para este problema).