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:
// 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.