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/