DEV Community

Cover image for Observabilidade com OpenTelemetry e .Net Core
Adriano
Adriano

Posted on • Edited on

Observabilidade com OpenTelemetry e .Net Core

Com o rápido avanço dos sistemas de tecnologia, e o número diverso de micro serviços necessários para atender o funcionamento de um negócio, vem se tornando cada vez mais complexo a gestão e busca por qualidade no desenvolvimento e principalmente em produção. O OpenTelemetry surgiu como uma proposta de solução capaz de facilitar e acelerar a implementação de monitoramento de aplicações modernas.

O que é o OpenTelemetry?

O OpenTelemetry é um framework de observabilidade de código aberto que permite às equipes de desenvolvimento gerar, processar e transmitir dados de telemetria em um formato unificado, provendo um conjunto de APIs, SDKs e ferramentas padronizados e independentes de fornecedor para coletar e exportar esses dados de telemetria.

Desenvolvido pela Cloud Native Computing Foundation (CNCF) com o objetivo de padronizar a coleta e roteamento de métricas, logs e traces para plataformas de monitoramento diversas.

Ao utilizar o OpenTelemetry podemos desacoplar nossos sistemas dos backends de observabilidade, pois o mesmo provê capacidade de instrumentar o código para gerar dados padronizados de telemetria e enviá-los utilizando o protocolo OTLP.

Como funciona?

Junto com as ferramentas, APIs e SDKs o OpenTelemetry disponibiliza um agente (Collector) responsável por receber os dados das suas aplicações e enviar para os backends de observabilidade.

Arquitetura do Collector

Image description

Uma das boas práticas de uso do OpenTelemetry é fazer o deploy do Collector em um servidor separado da aplicação, e configurar a aplicação para enviar dados para o endpoint do mesmo.

Componentes do Collector:

  • Receptores (Receivers): Responsáveis por receber dados de telemetria de várias fontes. Eles podem lidar com diferentes formatos, como OTLP (OpenTelemetry Protocol), Jaeger, Prometheus e até ferramentas comerciais ou proprietárias. Um receptor pode receber traces de solicitações HTTP, métricas de um serviço específico ou logs de aplicativos.

  • Exportadores (Exporters): Responsáveis por enviar os dados de telemetria processados para os backends de observabilidade, como Jaeger, Zipkin, Prometheus ou qualquer outro destino. Entre os exportadores, os baseados no OpenTelemetry Protocol (OTLP) são especialmente relevantes, pois mantêm a integridade dos dados sem perda de informações.

  • Processadores (Processors): Atuam na transformação e filtragem dos dados de telemetria antes de serem exportados. Permitindo aplicar regras de negócios, agregar informações, adicionar campos personalizados ou até mesmo reduzir o volume de dados. Por exemplo, um processador pode enriquecer traces com informações contextuais ou remover spans irrelevantes

Também podemos instrumentar a aplicação e enviar os dados de telemetria diretamente para os backends de observabilidade sem utilizar o collector, desta forma tornando o uso mais simples sem a necessidade de mais uma peça para gerenciar, mas em contra partida gerando forte acoplamento entre o código do sistema e o backend de observabilidade.

Projeto de exemplo

Para exemplificar melhor o funcionamento do OpenTelemetry, vamos desenvolver alguns projetos .Net Core do tipo WebApi, simulando um sistema de pedidos. Também iremos usar o Docker e Docker Compose para subir as aplicações e alguns backends de observabilidade (Prometheus, Grafana, Jaeger).

Estrutura do projeto

Image description

Criando os projetos e fazendo a instrumentação.

Os 3 projetos é do tipo WebApi versão .net core 8, após criar o projetos vamos instalar os seguintes pacotes:

Image description

Na classe Program.cs vamos fazer a instrumentação da aplicação:

  • Definindo url do collector e adicionamento SDK do OpenTelemetry.
var tracingOtlpGrpcEndpoint = "http://otel-collector:4317";
var otel = builder.Services.AddOpenTelemetry();
Enter fullscreen mode Exit fullscreen mode
  • Configurando OpenTelemetry Resource e o nome da aplicação que será registrado nos dados de telemetria.
otel.ConfigureResource(resource => resource
    .AddService(serviceName: builder.Environment.ApplicationName));
Enter fullscreen mode Exit fullscreen mode
  • Instrumentando a aplicação para enviar métricas.
otel.WithMetrics(metrics => metrics
    // Metrics provider from OpenTelemetry    
    .AddHttpClientInstrumentation()
    .AddAspNetCoreInstrumentation()
    .AddMeter("PedidoApiMetrics")

    .AddOtlpExporter(otlpOptions => 
    {
        otlpOptions.Endpoint = new Uri(tracingOtlpGrpcEndpoint);
    })
    );
Enter fullscreen mode Exit fullscreen mode
  • Instrumentando a aplicação para enviar tracing.
otel.WithTracing(tracing =>
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSource("PedidoApi");
    if (tracingOtlpGrpcEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =>
         {
             otlpOptions.Endpoint = new Uri(tracingOtlpGrpcEndpoint);             
         });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
Enter fullscreen mode Exit fullscreen mode
  • Instrumentando a aplicação para enviar logs.
otel.WithLogging(logging => 
{                    
    logging.AddOtlpExporter((otlpOptions, processorOptions) =>
    {        
        otlpOptions.Endpoint = new Uri(tracingOtlpGrpcEndpoint);                
        processorOptions.BatchExportProcessorOptions.ScheduledDelayMilliseconds = 2000;
        processorOptions.BatchExportProcessorOptions.MaxExportBatchSize = 512;
    });
});
Enter fullscreen mode Exit fullscreen mode

Configurando arquivo docker-compose.yml

name: webapiotlp-example

networks:
  proxy:
    driver: bridge 

services:
  grafana:
    image: grafana/grafana-enterprise
    container_name: grafana
    restart: unless-stopped
    ports:
     - '3000:3000'
    networks:
      - proxy
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_DISABLE_LOGIN_FORM=true
      - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor

  grafana-loki:
    image: grafana/loki:3.1.0
    container_name: grafana-loki    
    ports:
     - '3100:3100'
    volumes:
      - ./grafana-loki-config.yaml:/etc/loki/local-config.yaml
    command: 
     - -config.file=/etc/loki/local-config.yaml
     - -print-config-stderr=true
    networks:
      - proxy

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      # - ./logs:/etc/output:rw      
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - 1888:1888 # pprof extension
      - 8888:8888 # Prometheus metrics exposed by the Collector
      - 8889:8889 # Prometheus exporter metrics
      - 13133:13133 # health_check extension
      # - 4317:4317 # OTLP gRPC receiver
      # - 4318:4318 # OTLP http receiver
      # - 55679:55679 # zpages extension
    networks:
      - proxy
    depends_on:
      - grafana-loki

  jaeger:
    image: jaegertracing/all-in-one:1.48.0
    restart: always
    ports:
      - "6831:6831/udp" # UDP port for Jaeger agent
      - "16686:16686" # Web UI
      # - "14268:14268" # HTTP port for spans
      # - "4317:4317" # OTLP gRPC receiver for jaeger
      # - "4318:4318" # OTLP HTTP receiver for jaeger
    environment:
      - LOG_LEVEL=debug
    networks:
      - proxy 

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - proxy

  pedido-api:
    image:  pedido-api:dev
    build: 
      context: .
      dockerfile: ./Src/00-PedidoApi/Dockerfile
    ports:
      - "8080:8080" 
    environment:
      - DELAY=2
      - BASE_ADDRESS=http://estoque-api:8080/
      - URL_POST=Estoque
    networks:
      - proxy
    depends_on:
      - otel-collector
      - jaeger

  estoque-api:
    image:  estoque-api:dev
    build: 
      context: .
      dockerfile: ./Src/01-EstoqueApi/Dockerfile
    ports:
      - "8081:8080"
    environment:
      - DELAY=1
      - BASE_ADDRESS=http://notafiscal-api:8080/
      - URL_POST=NotaFiscal
    networks:
      - proxy
    depends_on:
      - otel-collector
      - jaeger

  notafiscal-api:
    image:  notafiscal-api:dev
    build: 
      context: .
      dockerfile: ./Src/02-NotaFiscalApi/Dockerfile
    ports:
      - "8082:8080" 
    environment:
      - DELAY=0
    networks:
      - proxy 
    depends_on:
      - otel-collector
      - jaeger
Enter fullscreen mode Exit fullscreen mode

Configurando receptores e exportadores no arquivo otel-collector-config.yaml

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: otel-collector:4317
      http:
        endpoint: otel-collector:4318

exporters:  
  debug:
    verbosity: detailed
  prometheus:
    endpoint: "0.0.0.0:8889"
    send_timestamps: true
    metric_expiration: 180m
    enable_open_metrics: true
  logging:

  otlp:
    endpoint: jaeger:4317
    tls:
      insecure: true
  otlphttp:
    endpoint: http://grafana-loki:3100/otlp

  # file:
  #   path: /etc/output/logs.json

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug, logging, otlp]
    metrics:
      receivers: [otlp]
      exporters: [debug, logging, prometheus]
    logs:
      receivers: [otlp]
      exporters: [debug, logging, otlphttp]      
Enter fullscreen mode Exit fullscreen mode

Configurando prometheus para coletar métricas do collector no arquivo prometheus.yml

global:
  scrape_interval: 5s

scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['otel-collector:8888', 'otel-collector:8889']
Enter fullscreen mode Exit fullscreen mode

Vamos capturar os logs obtidos no collector pelo grafana-loki, para funcionar local é necessário configurar no arquivo grafana-loki-config.yaml

auth_enabled: false

limits_config:
  allow_structured_metadata: true

server:
  http_listen_port: 3100

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093
Enter fullscreen mode Exit fullscreen mode

Executando o projeto e validando os dados coletados

  • Vamos criar um novo pedido fazendo uma requisição na api de pedidos usando o Postman:

Image description

  • Verificando no Jaeger os traces gerados:

Image description

  • Verificando no Prometheus as métricas geradas:

Image description

Image description

  • Verificando logs enviados para o grafana loki:

Image description

Quais são os benefícios ao utilizar o OpenTelemetry?

Como podemos ver, ganhamos muito ao utilizar o OpenTelemetry, com ele podemos implementar de uma forma simples e padronizada rastreabilidade de ponta a ponta em nossas aplicações, ajudando na identificação de gargalos e melhoria de desempenho. Também nos auxilia na detecção de erros, mostrando todo o fluxo de solicitações e logs. Não tenho dúvidas que é uma ferramenta essencial que irá ajudar o desenvolvedor a obter melhor qualidade em seus projetos.

Agradecimento

Obrigado por ler até aqui. Se você tiver alguma dúvida ou sugestão, não deixe de comentar.

Top comments (0)