Wdrażanie stosu OpenTelemetry dla Go: tracing, metryki i logi
Stos OpenTelemetry dla aplikacji Go zbiera tracing w Tempo, metryki w Prometheus i logi w Loki. Klient app-1 wysyła zapytania do serwera app-2 za pośrednictwem otel-collector. Grafana wizualizuje wszystkie dane. Pełny stos uruchamiany jest za pomocą docker-compose z minimalną konfiguracją.
Konfiguracja Docker Compose
Podstawowe serwisy: dwie aplikacje Go, otel-collector jako odbiornik telemetrii, backendy Tempo/Prometheus/Loki oraz Grafana. Sieć observability izoluje ruch.
Kluczowe porty:
- app-2: 8080
- otel-collector: 4317 (gRPC), 4318 (HTTP), 8889 (metrics)
- Prometheus: 9090
- Tempo: 3200
- Loki: 3100
- Grafana: 3000
services:
app-1:
build: ./app_1
depends_on:
otel-collector:
condition: service_started
networks:
- observability
app-2:
build: ./app_2
ports:
- "8080:8080"
networks:
- observability
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
ports:
- "4318:4318"
- "4317:4317"
- "8889:8889"
volumes:
- ./configs/otel-collector-config.yaml:/etc/otelcol/config.yaml:ro
networks:
- observability
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./configs/prometheus.yml:/etc/prometheus/prometheus.yml:ro
networks:
- observability
tempo:
image: grafana/tempo:2.7.0
ports:
- "3200:3200"
volumes:
- ./configs/tempo-config.yaml:/etc/tempo/tempo.yaml
networks:
- observability
loki:
image: grafana/loki:3.3.2
ports:
- "3100:3100"
volumes:
- ./configs/loki-config.yaml:/etc/loki/local-config.yaml:ro
networks:
- observability
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- ./configs/grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro
networks:
- observability
networks:
observability:
driver: bridge
Konfiguracja Tempo dla śledzenia
Tempo przyjmuje OTLP przez gRPC (4317) i HTTP (4318). Bloki śladów są kompaktowane z retencją 30m. Trace_idle_period 5s zapewnia szybką widoczność w interfejsie użytkownika.
server:
http_listen_port: 3200
grpc_listen_port: 9095
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
ingester:
trace_idle_period: 5s
max_block_duration: 30m
storage:
trace:
backend: local
local:
path: /var/tempo/traces
compactor:
compaction:
block_retention: 30m
Prometheus i reguły alertingu
Zbieranie metryk z otel-collector:8889 co 60s. Reguły alertów monitorują opóźnienie P95 > 2000ms oraz współczynnik błędów > 5%.
global:
scrape_interval: 60s
scrape_configs:
- job_name: "otel-collector"
static_configs:
- targets: ["otel-collector:8889"]
Przykładowe reguły:
groups:
- name: app1-service-alerts
rules:
- alert: App1LatencyHigh
expr: histogram_quantile(0.95, sum by (le) (rate(app_1_requests_latency_ms_bucket[5m]))) > 2000
for: 3m
labels:
severity: warning
- name: app2-service-alerts
rules:
- alert: App2HighErrorRate
expr: rate(app_2_requests_total{status="failed"}[5m]) / rate(app_2_requests_total[5m]) > 0.05
for: 2m
labels:
severity: critical
Konfiguracja Loki dla logów
Logi przechowywane są w filesystem z schematem TSDB v13. Indeksowanie w okresach 24-godzinnych. Structured metadata włączona.
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /tmp/loki
storage:
filesystem:
chunks_directory: /tmp/loki/chunks
schema_config:
configs:
- from: 2020-10-24
store: tsdb
schema: v13
index:
period: 24h
limits_config:
allow_structured_metadata: true
Integracja w Grafana
Źródła danych łączą metryki→śledzenie→logi:
- Prometheus exemplarTraceIdDestinations
- Loki derivedFields według traceID
- Tempo serviceMap i tracesToLogsV2
Inicjalizacja OTel w kliencie Go
Funkcja InitOTel tworzy provider'y dla wszystkich sygnałów z atrybutami zasobu. Sampling 10% dla śladów, batching dla eksportu.
func InitOTel(ctx context.Context, serviceName string) (*log.LoggerProvider, func(context.Context) error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
res := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName(serviceName),
semconv.ServiceVersion("1.0.0"),
)
// Tracing
traceExp, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("otel-collector:4317"),
otlptracegrpc.WithInsecure(),
)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))),
sdktrace.WithBatcher(traceExp),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tracerProvider)
// Metrics
metricExp, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint("otel-collector:4317"),
otlpmetricgrpc.WithInsecure(),
)
meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExp, sdkmetric.WithInterval(10*time.Second))),
sdkmetric.WithResource(res),
)
otel.SetMeterProvider(meterProvider)
// Logs
logExp, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpoint("otel-collector:4317"),
otlploggrpc.WithInsecure(),
)
logProvider := log.NewLoggerProvider(
log.WithProcessor(log.NewBatchProcessor(logExp)),
log.WithResource(res),
)
return logProvider, func(ctx context.Context) error {
// shutdown logic
return nil
}
}
Co ważne
- Pełny stos OTel: śledzenie (Tempo), metryki (Prometheus), logi (Loki) zintegrowane za pośrednictwem otel-collector
- Instrumentacja Go: otelhttp.Transport, otelslog.Handler, metryki counter/histogram
- Natywna integracja Grafana: przejścia metryki→śledzenie→logi według traceID
- Wydajność: trace_idle_period 5s, sampling 10%, periodic reader 10s
- Alerting: P95 latency > 2000ms, error rate > 5%
— Editorial Team
Brak komentarzy.