En este post, vamos a ver cómo instrumentar una arquitectura de microservicios desarrollada en Go utilizando OpenTelemetry para obtener trazas, métricas y logs. También veremos cómo enviar estos datos a sistemas de backend como Jaeger, Prometheus/Grafana y Seq Server, usando OpenTelemetry Collector como punto central de recolección y exportación.
1. Observabilidad en Arquitecturas Distribuidas
Cuando se trabaja con microservicios, la observabilidad es esencial para monitorear y analizar el rendimiento del sistema. Queremos tener acceso a:
- Trazas: Para conocer el flujo de solicitudes a través de diferentes servicios.
- Métricas: Para medir el rendimiento y el estado de los servicios.
- Logs: Para rastrear errores y otros eventos importantes.
OpenTelemetry es una herramienta que permite recolectar estos tres tipos de datos y enviarlos a sistemas de backend como Jaeger (trazas), Prometheus (métricas) y Seq Server (logs), a través del Collector.
2. ¿Qué es OpenTelemetry?
OpenTelemetry es una plataforma estándar para recolectar y exportar trazas, métricas y logs en aplicaciones distribuidas. En nuestro entorno de microservicios, vamos a instrumentar las aplicaciones en Go para enviar estos datos al OpenTelemetry Collector, que se encargará de exportarlos a los backends de observabilidad.
3. Arquitectura de Observabilidad
En nuestro caso, cada microservicio desarrollado en Go enviará trazas, métricas y logs al OpenTelemetry Collector, que luego reenviará estos datos a los sistemas de backend. Veamos cómo configurar cada uno de estos elementos.
4. Configuración del OpenTelemetry Collector
Primero, necesitamos configurar el OpenTelemetry Collector para que reciba y exporte trazas, métricas y logs. Esta es la configuración básica que usaremos:
Configuración del Collector:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317" # Para recibir trazas, métricas y logs vía gRPC
http:
endpoint: "0.0.0.0:4318" # Para recibir trazas, métricas y logs vía HTTP
prometheus:
config:
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 5s
static_configs:
- targets: ['0.0.0.0:8888'] # Para recibir métricas Prometheus
logs:
protocols:
grpc:
endpoint: "0.0.0.0:4317" # Para recibir logs vía gRPC
http:
endpoint: "0.0.0.0:4318" # Para recibir logs vía HTTP
exporters:
jaeger:
endpoint: "jaeger-host:14250"
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:8888" # Para exportar métricas a Prometheus
otlp:
endpoint: "seq-server-host:4317" # Para exportar logs a Seq Server
service:
pipelines:
traces:
receivers: [otlp] # Recibe trazas
exporters: [jaeger] # Exporta trazas a Jaeger
metrics:
receivers: [otlp, prometheus] # Recibe métricas
exporters: [prometheus] # Exporta métricas a Prometheus
logs:
receivers: [otlp, logs] # Recibe logs
exporters: [otlp] # Exporta logs a Seq Server
5. Instrumentación en Microservicios con Go
Para que nuestros microservicios en Go envíen trazas, logs y métricas al OpenTelemetry Collector, necesitamos agregar código de instrumentación en cada microservicio.
Trazas y Métricas:
Instalamos las dependencias de OpenTelemetry en Go:
go get go.opentelemetry.io/otel go get go.opentelemetry.io/otel/sdk go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
Luego, añadimos el código de configuración para el envío de trazas y métricas:
package main
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"google.golang.org/grpc"
)
func initTracer() *trace.TracerProvider {
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "collector-host:4317", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to create gRPC connection: %v", err)
}
exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
if err != nil {
log.Fatalf("Failed to create trace exporter: %v", err)
}
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("MyGoService"),
)),
)
otel.SetTracerProvider(tp)
return tp
}
func initMeter() *metric.MeterProvider {
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "collector-host:4317", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to create gRPC connection: %v", err)
}
exporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithGRPCConn(conn))
if err != nil {
log.Fatalf("Failed to create metric exporter: %v", err)
}
mp := metric.NewMeterProvider(
metric.WithReader(exporter),
metric.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("MyGoService"),
)),
)
return mp
}
Logs:
Para los logs, necesitamos usar un paquete específico que envíe los registros al OTLP:
go get go.opentelemetry.io/otel/exporters/otlp/otlpgrpc go get go.opentelemetry.io/otel/sdk/logs
Luego, añadimos la configuración para los logs:
package main
import (
"context"
"log"
"go.opentelemetry.io/otel/sdk/logs"
"go.opentelemetry.io/otel/exporters/otlp/otlpgrpc"
"google.golang.org/grpc"
)
func initLogger() *logs.LoggerProvider {
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "collector-host:4317", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to create gRPC connection: %v", err)
}
exporter, err := otlpgrpc.New(ctx, otlpgrpc.WithGRPCConn(conn))
if err != nil {
log.Fatalf("Failed to create log exporter: %v", err)
}
lp := logs.NewLoggerProvider(
logs.WithBatcher(exporter),
)
return lp
}
6. Mejores Prácticas
Aquí algunas recomendaciones a medida que escales tu arquitectura de microservicios:
- Variables de Entorno: Utiliza variables de entorno para configurar dinámicamente los endpoints del Collector, el muestreo de trazas, etc. Esto facilita la gestión y configuración de la observabilidad sin tener que cambiar el código.
- Instrumentación Automática: Siempre que sea posible, usa la instrumentación automática proporcionada por OpenTelemetry para evitar el trabajo manual de instrumentar cada servicio.
- Collector Centralizado o Sidecar: Implementar el Collector como un servicio centralizado o como sidecar puede ayudarte a reducir la complejidad en la gestión de los datos de observabilidad.
En este post hemos visto cómo configurar la observabilidad para microservicios desarrollados en Go, con la instrumentación adecuada y el uso del Collector, podemos capturar y analizar trazas, logs y métricas de manera eficiente en un entorno distribuido.
Referencia: https://usuarioperu.com/2022/05/14/opentelemetry/