Best Practice: Observability-Stack aufbauen
Kontext
Observability ist die Fähigkeit, den internen Zustand eines Systems aus seinen Ausgaben zu verstehen. Sie besteht aus drei Säulen: Logs, Metriken und Traces. Ein System ohne alle drei Säulen ist im Incident-Fall blind.
Zielbild
Ein vollständiger Observability-Stack:
-
Alle Services emittieren strukturierte JSON-Logs mit Trace-ID und Request-ID
-
Distributed Tracing ist via OpenTelemetry implementiert und visualisierbar
-
RED-Metriken (Rate, Errors, Duration) für jeden Service exportiert
-
Dashboards zeigen Service-Gesundheit auf einen Blick
-
Log-Retention-Policies steuern Kosten und Compliance
Technische Umsetzung
Schritt 1: Structured Logging einrichten
// Node.js Beispiel mit Winston und OpenTelemetry
const winston = require('winston');
const { trace, context } = require('@opentelemetry/api');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
// Middleware: Trace-ID und Request-ID in jeden Log einbetten
function createLogContext() {
const span = trace.getActiveSpan();
const spanContext = span?.spanContext();
return {
trace_id: spanContext?.traceId || 'no-trace',
span_id: spanContext?.spanId || 'no-span',
};
}
// Verwendung:
logger.info('Payment processed', {
...createLogContext(),
service: 'payment-service',
request_id: req.headers['x-request-id'],
user_id: req.user.id,
amount: payment.amount,
currency: payment.currency,
});
// Erwartetes Log-Ausgabe-Format:
// {
// "timestamp": "2025-03-18T10:30:00.000Z",
// "level": "info",
// "service": "payment-service",
// "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
// "span_id": "00f067aa0ba902b7",
// "request_id": "req-abc123",
// "message": "Payment processed",
// "amount": 99.99,
// "currency": "EUR"
// }
Schritt 2: OpenTelemetry konfigurieren (AWS X-Ray Backend)
// otel-setup.js – vor allem anderen importieren
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { AWSXRayPropagator } = require('@opentelemetry/propagator-aws-xray');
const { AWSXRayIdGenerator } = require('@opentelemetry/id-generator-aws-xray');
const { AWSXRayLambdaPropagator } = require('@opentelemetry/propagator-aws-xray');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
const sdk = new NodeSDK({
textMapPropagator: new AWSXRayPropagator(),
idGenerator: new AWSXRayIdGenerator(),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
}),
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
serviceName: process.env.SERVICE_NAME || 'payment-service',
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
Schritt 3: CloudWatch Log Groups mit Terraform konfigurieren
# CloudWatch Log Group für Applikation
resource "aws_cloudwatch_log_group" "app" {
name = "/aws/ecs/${var.service_name}"
retention_in_days = 90 # 90 Tage für Applikations-Logs
tags = {
service = var.service_name
environment = var.environment
managed-by = "terraform"
}
}
# CloudWatch Log Group für Security/Audit-Logs (längere Retention)
resource "aws_cloudwatch_log_group" "audit" {
name = "/audit/${var.service_name}"
retention_in_days = 365 # 1 Jahr für Audit-Logs
tags = {
service = var.service_name
environment = var.environment
log-type = "audit"
}
}
# CloudWatch Contributor Insights (Anomalie-Erkennung in Logs)
resource "aws_cloudwatch_log_metric_filter" "error_rate" {
name = "${var.service_name}-error-rate"
pattern = "{ $.level = \"ERROR\" }"
log_group_name = aws_cloudwatch_log_group.app.name
metric_transformation {
name = "ErrorCount"
namespace = "PaymentService"
value = "1"
}
}
# X-Ray Sampling-Regel konfigurieren
resource "aws_xray_sampling_rule" "payment_service" {
rule_name = "payment-service-sampling"
priority = 1000
reservoir_size = 5 # Minimum 5 Traces/Sekunde
fixed_rate = 0.05 # 5% Sampling für normale Requests
url_path = "/api/payment/*"
host = "*"
http_method = "*"
service_type = "*"
service_name = "payment-service"
resource_arn = "*"
version = 1
}
Schritt 4: Dashboard erstellen
resource "aws_cloudwatch_dashboard" "service_health" {
dashboard_name = "${var.service_name}-health"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
width = 8
height = 6
properties = {
title = "Request Rate (RPM)"
period = 60
stat = "Sum"
metrics = [["PaymentService", "RequestCount"]]
}
},
{
type = "metric"
width = 8
height = 6
properties = {
title = "Error Rate (%)"
period = 60
stat = "Average"
metrics = [["PaymentService", "ErrorRate"]]
}
},
{
type = "metric"
width = 8
height = 6
properties = {
title = "p99 Latency (ms)"
period = 60
stat = "p99"
metrics = [["PaymentService", "Latency"]]
}
}
]
})
}
Typische Fehlmuster
| Fehlmuster | Problem |
|---|---|
Unstrukturiertes Logging (Text) |
Nicht maschinell verarbeitbar; Log-Suche aufwändig; Trace-ID-Korrelation unmöglich |
Log ohne Trace-ID |
Requests können nicht über Services verfolgt werden; Root-Cause-Analyse dauert Stunden |
Keine Log-Retention |
Kosten wachsen unbegrenzt; Compliance-Verletzung (GDPR: keine unendliche Datenspeicherung) |
Sampling Rate 100% in Production |
Tracing-Kosten explodieren bei hohem Traffic; Alternative: adaptive Sampling |
Sensitive Daten in Logs |
GDPR-Verstoß; PII, Passwörter, Tokens in Logs sind Security-Incident |
Dashboard ohne Alerting |
Dashboard wird nur beobachtet wenn jemand schaut; Symptome unbemerkt bis Nutzer klagen |
Metriken
-
Log-Verfügbarkeit: Sind Logs innerhalb von 60 Sekunden in der Aggregationsplattform? (Ziel: Ja)
-
Trace-Abdeckung: % der Services mit konfiguriertem Distributed Tracing (Ziel: 100%)
-
MTTR-Korrelation: Zeit bis zur Diagnose mit vs. ohne Tracing (messen bei Postmortems)
Reifegrad
| Stufe | Charakteristika |
|---|---|
Level 1 |
Unstrukturierte Logs in Dateien; kein zentrales Aggregation; kein Tracing. |
Level 2 |
Logs zentral aggregiert; Basis-Dashboards; kein Distributed Tracing; kein strukturiertes Format. |
Level 3 |
Structured JSON Logs mit Trace-ID; Distributed Tracing konfiguriert; RED-Metriken; Log-Retention. |
Level 4 |
OpenTelemetry vendor-agnostisch; SLO-basiertes Alerting auf Metriken; Trace-Sampling optimiert. |
Level 5 |
Vollständige Korrelation Logs/Traces/Metriken; Observability-as-a-Product mit internen SLAs. |