Zum Inhalt springen

Monitoring- und Alerting-Strategien, die wirklich funktionieren

Alarmmüdigkeit sabotiert die Incident-Reaktion — baue deine Monitoring-Strategie auf SLIs, sinnvollen Schwellenwerten und umsetzbaren Alarmen auf.

3 Min. Lesezeit
Monitoring-Dashboard mit SLI-Metriken und Alarmschwellenwerten

Die meisten Monitoring-Setups scheitern nicht an fehlenden Daten, sondern daran, dass sie zu viel Rauschen erzeugen. Teams versinken in Alarmen, die keine Reaktion erfordern, und ignorieren irgendwann ihre Pager komplett. Effektives Monitoring beginnt damit, festzulegen, was wirklich zählt, Schwellenwerte zu setzen, die den tatsächlichen Einfluss auf die Nutzer widerspiegeln, und sicherzustellen, dass jeder Alarm einen klaren Reaktionsweg hat.

Die vier goldenen Signale

Googles SRE-Buch beschreibt vier Signale, die die meisten Monitoring-Anforderungen abdecken. Jeder Dienst sollte diese vier erfassen, bevor irgendetwas anderes hinzukommt.

tstypescript
// Express middleware that captures the four golden signals
import { Counter, Histogram, Gauge } from "prom-client";
 
// 1. Latency — how long requests take
const httpDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Duration of HTTP requests in seconds",
  labelNames: ["method", "route", "status_code"],
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});
 
// 2. Traffic — how many requests per second
const httpRequests = new Counter({
  name: "http_requests_total",
  help: "Total number of HTTP requests",
  labelNames: ["method", "route", "status_code"],
});
 
// 3. Errors — rate of failed requests
const httpErrors = new Counter({
  name: "http_errors_total",
  help: "Total number of HTTP errors",
  labelNames: ["method", "route", "error_type"],
});
 
// 4. Saturation — how full your resources are
const activeConnections = new Gauge({
  name: "http_active_connections",
  help: "Number of active HTTP connections",
});
 
app.use((req, res, next) => {
  activeConnections.inc();
  const end = httpDuration.startTimer({
    method: req.method,
    route: req.route?.path ?? req.path,
  });
 
  res.on("finish", () => {
    const labels = {
      method: req.method,
      route: req.route?.path ?? req.path,
      status_code: res.statusCode.toString(),
    };
    end(labels);
    httpRequests.inc(labels);
    activeConnections.dec();
 
    if (res.statusCode >= 500) {
      httpErrors.inc({ ...labels, error_type: "server_error" });
    }
  });
 
  next();
});

Service-Level-Indikatoren (SLIs)

SLIs übersetzen Rohdaten aus Metriken in Qualitätswerte, die sich auf die Nutzererfahrung beziehen. Sie beantworten die Frage: Ist die Nutzererfahrung akzeptabel?

ymlyaml
# Prometheus alerting rules based on SLIs
groups:
  - name: sli-alerts
    rules:
      # Availability SLI: proportion of successful requests
      - alert: HighErrorRate
        expr: |
          (
            sum(rate(http_errors_total[5m]))
            /
            sum(rate(http_requests_total[5m]))
          ) > 0.01
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Error rate exceeds 1% for 5 minutes"
          runbook: "https://wiki.internal/runbooks/high-error-rate"
 
      # Latency SLI: proportion of requests served within threshold
      - alert: HighLatency
        expr: |
          (
            sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
            /
            sum(rate(http_request_duration_seconds_count[5m]))
          ) < 0.95
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Less than 95% of requests served within 500ms"

Prinzipien für gute Alarme

Ein Alarm sollte jemanden nur dann wecken, wenn sofortiges menschliches Eingreifen nötig ist. Alles andere gehört auf ein Dashboard.

ymlyaml
# ❌ Noisy alert — fires on any single error
- alert: AnyServerError
  expr: http_errors_total > 0
  labels:
    severity: critical
 
# ✅ Meaningful alert — fires on sustained elevated error rate
- alert: ElevatedErrorRate
  expr: |
    (
      sum(rate(http_errors_total[10m]))
      /
      sum(rate(http_requests_total[10m]))
    ) > 0.005
  for: 10m  # Must sustain for 10 minutes
  labels:
    severity: warning
  annotations:
    summary: "Error rate above 0.5% for 10 minutes"
    impact: "Approximately {{ $value | humanizePercentage }} of users affected"
    runbook: "https://wiki.internal/runbooks/elevated-errors"
    dashboard: "https://grafana.internal/d/api-overview"

Jede Alarm-Annotation sollte enthalten, was gerade passiert, wer betroffen ist, einen Link zum Runbook und einen Link zum passenden Dashboard. Wenn die Bereitschaftsperson erst nach Kontext suchen muss, ist der Alarm unvollständig.

Strukturiertes Logging zur Korrelation

Metriken zeigen dir, dass etwas nicht stimmt. Logs zeigen dir, warum. Strukturierte Logs mit Korrelations-IDs ermöglichen es, eine bestimmte fehlgeschlagene Anfrage über mehrere Dienste hinweg zu verfolgen.

tstypescript
// ❌ Unstructured logs — impossible to search or correlate
console.log("Error processing order for user " + userId);
 
// ✅ Structured JSON logs with correlation ID
import { randomUUID } from "crypto";
 
app.use((req, res, next) => {
  req.correlationId = req.headers["x-correlation-id"] as string ?? randomUUID();
  res.setHeader("x-correlation-id", req.correlationId);
  next();
});
 
function log(level: string, message: string, context: Record<string, unknown>) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level,
    message,
    correlationId: context.correlationId,
    service: "order-service",
    ...context,
  }));
}
 
// Usage in route handler
app.post("/orders", async (req, res) => {
  log("info", "Processing order", {
    correlationId: req.correlationId,
    userId: req.body.userId,
    itemCount: req.body.items.length,
  });
 
  try {
    const order = await createOrder(req.body);
    log("info", "Order created", {
      correlationId: req.correlationId,
      orderId: order.id,
    });
    res.json(order);
  } catch (error) {
    log("error", "Order creation failed", {
      correlationId: req.correlationId,
      error: error instanceof Error ? error.message : String(error),
    });
    res.status(500).json({ error: "Order processing failed" });
  }
});

Dashboard-Hierarchie

Strukturiere Dashboards in Schichten: eine übergeordnete Übersicht für die schnelle Erstbewertung, Dashboards auf Service-Ebene für die Untersuchung und Dashboards auf Komponentenebene für die detaillierte Fehlersuche.

Dashboard Hierarchy:
├── System Overview (RED metrics for all services)
│   ├── API Gateway (request rate, error rate, latency p50/p95/p99)
│   ├── Services (per-service health summary)
│   └── Infrastructure (CPU, memory, disk across clusters)
├── Service: Order API (detailed metrics)
│   ├── Endpoint breakdown (latency per route)
│   ├── Database queries (slow queries, pool utilization)
│   └── Downstream dependencies (timeout rates)
└── Component: PostgreSQL (deep dive)
    ├── Connection pool utilization
    ├── Query performance (p95, p99)
    ├── Replication lag
    └── Disk I/O and buffer cache hit ratio

Die wichtigsten Erkenntnisse

  1. Beginne mit den vier goldenen Signalen — Latenz, Traffic, Fehler und Auslastung decken die meisten Anforderungen ab
  2. Definiere SLIs, die die Nutzererfahrung widerspiegeln — alarmiere bei anhaltender Verschlechterung, nicht bei einzelnen Fehlern
  3. Jeder Alarm braucht ein Runbook — wenn die Bereitschaftsperson erst recherchieren muss, was der Alarm bedeutet, ist er unvollständig
  4. Strukturierte Logs mit Korrelations-IDs — ermöglichen die Verfolgung einer einzelnen Anfrage über mehrere Dienste hinweg
  5. Staffele deine Dashboards — Übersicht für die Erstbewertung, Service-Ebene für die Untersuchung, Komponentenebene für die Fehlersuche
  6. Alarmiere bei Symptomen, nicht bei Ursachen — Nutzer interessieren sich für Fehlerraten und Latenz, nicht für CPU-Auslastung
Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX