Saltar a contenido

OpenTelemetry

OpenTelemetry (OTel) es el estándar abierto para instrumentación de observabilidad: trazas distribuidas, métricas y logs. En la plataforma de identidad, OTel correlaciona peticiones a través de Traefik, Keycloak y los Resource Servers Spring WebFlux, permitiendo rastrear el ciclo completo de autenticación.

Los tres pilares

flowchart LR
    subgraph Apps["Aplicaciones instrumentadas"]
        T[Traefik v3]
        K[Keycloak 26]
        S[Spring WebFlux]
    end

    subgraph Collector["OTel Collector"]
        R[Receivers\nOTLP / Prometheus]
        P[Processors\nBatch / Filter]
        E[Exporters]
    end

    subgraph Backends["Backends de observabilidad"]
        Prometheus[(Prometheus\nMétricas)]
        Loki[(Loki\nLogs)]
        Tempo[(Tempo\nTrazas)]
        Grafana[Grafana\nDashboards]
    end

    T -- OTLP gRPC --> R
    K -- Metrics endpoint --> R
    S -- OTLP gRPC --> R

    R --> P --> E

    E --> Prometheus
    E --> Loki
    E --> Tempo

    Prometheus --> Grafana
    Loki --> Grafana
    Tempo --> Grafana

Spring Boot 4 + Micrometer + OTel

Dependencias

<dependencies>
    <!-- Actuator expone métricas -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- Exportar métricas en formato Prometheus -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    <!-- Trazas con OpenTelemetry -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
</dependencies>

Configuración

# application.yml
management:
  tracing:
    sampling:
      probability: 1.0   # 100% en dev; reducir a 0.1 en producción
  otlp:
    tracing:
      endpoint: http://otel-collector:4318/v1/traces
  metrics:
    export:
      otlp:
        url: http://otel-collector:4318/v1/metrics
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics

logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

OTel Collector — configuración

# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  prometheus:
    config:
      scrape_configs:
        - job_name: keycloak
          scrape_interval: 15s
          static_configs:
            - targets: ['keycloak:9000']
          metrics_path: /metrics

processors:
  batch:
    timeout: 5s
    send_batch_size: 512
  resourcedetection:
    detectors: [docker, system]

exporters:
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
  loki:
    endpoint: http://loki:3100/loki/api/v1/push
  otlp/tempo:
    endpoint: http://tempo:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, resourcedetection]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp, prometheus]
      processors: [batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [loki]

docker-compose para el stack de observabilidad

# compose.observability.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    command: ["--config=/etc/otel-collector-config.yml"]
    ports:
      - "4317:4317"   # OTLP gRPC (interno)
      - "4318:4318"   # OTLP HTTP (interno)
    networks:
      - frontend

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    networks:
      - frontend

  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    networks:
      - frontend

  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yml"]
    volumes:
      - ./tempo.yml:/etc/tempo.yml
    networks:
      - frontend

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - frontend

volumes:
  prometheus_data:
  grafana_data:

Correlation IDs — trazabilidad end-to-end

Cada petición genera un traceId que se propaga automáticamente a través de todos los servicios:

Browser → Traefik (genera traceId) → Spring WebFlux (propaga traceId) → Log con traceId
// Acceder al traceId en el código
import io.micrometer.tracing.Tracer;

@Service
@RequiredArgsConstructor
public class AuditService {

    private final Tracer tracer;

    public void log(String event) {
        String traceId = tracer.currentSpan() != null
                ? tracer.currentSpan().context().traceId()
                : "no-trace";
        log.info("event={} traceId={}", event, traceId);
    }
}

Los logs con traceId se pueden correlacionar directamente con las trazas en Grafana Tempo usando la función Trace to Logs.

Reducir el sampling en producción

Con probability: 1.0 se registra el 100% de las trazas. En producción con carga alta, reduce a 0.1 (10%) para limitar el volumen de datos. Las métricas siempre se exportan al 100% independientemente del sampling.