Configurar o plug-in básico do OpenTelemetry no gRPC Java

1. Introdução

Neste codelab, você vai usar o gRPC para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em Java.

Ao final do tutorial, você terá um aplicativo gRPC HelloWorld simples instrumentado com o plug-in gRPC OpenTelemetry e poderá conferir as métricas de observabilidade exportadas no Prometheus.

O que você vai aprender

  • Como configurar o plug-in do OpenTelemetry para um aplicativo Java gRPC atual
  • Executar uma instância local do Prometheus
  • Exportar métricas para o Prometheus
  • Conferir métricas no painel do Prometheus

2. Antes de começar

O que é necessário

  • git
  • curl
  • JDK v8 ou mais recente

Instale os pré-requisitos:

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

Acessar o código

Para facilitar o aprendizado, este codelab oferece um scaffold de código-fonte pré-criado para ajudar você a começar. As etapas a seguir explicam como instrumentar o plug-in gRPC do OpenTelemetry em um aplicativo.

grpc-codelabs

O código-fonte do scaffold para este codelab está disponível neste diretório do GitHub. Se preferir não implementar o código por conta própria, o código-fonte concluído está disponível no diretório completed.

Primeiro, clone o repositório do codelab do gRPC e acesse a pasta grpc-java-opentelemetry:

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

Como alternativa, baixe o arquivo .zip que contém apenas o diretório do codelab e descompacte-o manualmente.

3. Registrar o plug-in do OpenTelemetry

Precisamos de um aplicativo gRPC para adicionar o plug-in gRPC OpenTelemetry. Neste codelab, vamos usar um cliente e um servidor gRPC HelloWorld simples que vamos instrumentar com o plug-in gRPC OpenTelemetry.

A primeira etapa é registrar o plug-in do OpenTelemetry configurado com um exportador do Prometheus no cliente. Abra codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryClient.java com seu editor favorito e modifique o principal para adicionar código e configurar a API gRPC Java OpenTelemetry.

Configurar a instrumentação no cliente

Criar exportador do Prometheus

Crie um PrometheusHttpServer para converter métricas do OpenTelemetry no formato do Prometheus e exponha-as por um HttpServer. O snippet de código a seguir cria um novo exportador do Prometheus.

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

Criar instância do SDK do OpenTelemetry

Registre acima de create prometheusExporter como MetricReader para ler métricas de um SdkMeterProvider. O SdkMeterProvider é usado para configurar as opções de métricas.

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

Crie uma instância de OpenTelemetrySdk com o sdkMeterProvider criado acima para a implementação do SDK do OpenTelemetry.

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

Criar instância do GrpcOpenTelemetry

Usando o conjunto de APIs GrpcOpenTelemetry, defina o SDK do OpenTelemetry que usa o exportador de métricas do Prometheus.

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

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

Depois que uma instância GrpcOpenTelemetry é registrada globalmente usando registerGlobal, todos os clientes e servidores gRPC criados posteriormente são instrumentados com o OpenTelemetry.

Encerrar o SDK do OpenTelemetry

O desligamento precisa acontecer dentro do ShutDownHook. O openTelemetrySdk.close() encerra o SDK e também chama o encerramento no SdkMeterProvider.

Configurar a instrumentação no servidor

Da mesma forma, vamos adicionar o GrpcOpenTelemetry ao servidor também. Abra codelabs/grpc-java-opentelemetry/start_here/src/main/java/io/grpc/codelabs/opentelemetry/OpenTelemetryServer.java e adicione o código para inicializar o GrpcOpenTelemetry.

Criar exportador do Prometheus

Como este codelab pode ser executado na mesma máquina, estamos usando uma porta diferente para hospedar métricas do lado do servidor gRPC e evitar conflitos de porta ao criar o PrometheusHttpServer.

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

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

Criar instância do SDK do OpenTelemetry

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

Inicializar o GrpcOpenTelemetry com o SDK do OpenTelemetry

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

Criar instância do GrpcOpenTelemetry

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

Encerrar o SDK do OpenTelemetry

Depois que o canal gRPC for desligado. Chamar openTelemetrySdk.close() encerra o SDK e também chama o encerramento no SdkMeterProvider.

4. Executar o exemplo e conferir as métricas

Para executar o servidor, execute:

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

Com uma configuração bem-sucedida, você verá a seguinte saída para o servidor:

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

Enquanto o servidor estiver em execução, execute o cliente em outro terminal:

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

Uma execução bem-sucedida será semelhante a esta:

[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

Como configuramos o plug-in gRPC OpenTelemetry para exportar métricas usando o Prometheus. Essas métricas estarão disponíveis em localhost:9464 para o servidor e localhost:9465 para o cliente.

Para ver as métricas do cliente:

curl localhost:9465/metrics

O resultado seria assim:

# 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

Da mesma forma, para as métricas do lado do servidor:

curl localhost:9464/metrics

5. Como visualizar métricas no Prometheus

Aqui, vamos configurar uma instância do Prometheus que vai extrair nosso exemplo de cliente e servidor gRPC que estão exportando métricas usando o Prometheus.

Faça o download da versão mais recente do Prometheus para sua plataforma, extraia e execute:

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

Crie um arquivo de configuração do Prometheus com o seguinte:

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

Inicie o Prometheus com a nova configuração:

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

Isso vai configurar as métricas dos processos do codelab do cliente e do servidor para serem coletadas a cada 5 segundos.

Acesse http://localhost:9090/graph para conferir as métricas. Por exemplo, a consulta:

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

vai mostrar um gráfico com a latência mediana da tentativa usando 1 minuto como janela de tempo para o cálculo do quantil.

Taxa de consultas:

increase(grpc_client_attempt_duration_seconds_bucket[1m])

6. (Opcional) Exercício para o usuário

Nos painéis do Prometheus, você vai notar que o QPS está baixo. Veja se você consegue identificar um código suspeito no exemplo que está limitando o QPS.

Para os entusiastas, o código do cliente se limita a ter apenas uma RPC pendente em um determinado momento. Isso pode ser modificado para que o cliente envie mais RPCs sem esperar que os anteriores sejam concluídos. (A solução para isso não foi fornecida.)