Jede Cloud verkauft Ihnen Alarme. CloudWatch Alarms, Azure-Monitor-Alarmregeln, Cloud-Monitoring-Alerting-Policies: jeder ist vorkonfiguriert, jeder ist ein paar Klicks entfernt, und jeder ist in einem Format definiert, das nur seine eigene Cloud versteht. Laufen Sie in mehr als einer, landen Sie bei drei Alarmsystemen, drei On-Call-Routings, drei Definitionen von „die API ist ungesund“ und früher oder später bei drei verschiedenen Schwellwerten für dieselbe Sache, weil jemand einen nachjustiert und die anderen vergessen hat.
Wir haben dargelegt, dass Logs und Traces von der Anwendung emittiert und dort gespeichert werden sollten, wo die Cloud sie nicht besitzt. Alarme sind das dritte Stück, und es ist das, was um 02:40 zählt, denn ein Alarm ist das, was entscheidet, ob Sie wach sind. So definieren wir sie einmal, aus Metriken, die die Anwendung emittiert, sodass „Fehlerrate über 1 %“ auf EKS, GKE und AKS dasselbe bedeutet und ein Dienst, der zwischen ihnen umzieht, nichts daran ändert, was Sie weckt.
Auf das alarmieren, was der Nutzer sieht, nicht auf das, was die Cloud sieht
Die nativen Alarme der Cloud handeln von den Ressourcen der Cloud: CPU auf einer Instanz, 5xx-Zähler auf einem Load Balancer, Throttles auf einer Tabelle. Sie sind nützlich, und sie sind das Falsche zum Wecken, denn ein Nutzer erlebt keine CPU. Ein Nutzer erlebt einen langsamen oder fehlgeschlagenen Request. Also liegen die Alarme, die wecken, auf drei Zahlen pro Dienst, gemessen aus Sicht der Anwendung selbst:
- Rate: Requests pro Sekunde, damit ein Absturz auf null sichtbar ist.
- Errors: Anteil der fehlgeschlagenen Requests.
- Duration: Latenz im 95. oder 99. Perzentil.
Diese kommen aus derselben OpenTelemetry-Pipeline, die die Traces trägt. Der spanmetrics-Connector des Collectors macht aus jedem Server-Span ein Histogramm der Dauern und einen Zähler der Aufrufe, gelabelt mit service.name, der HTTP-Route, dem Statuscode und, weil resourcedetection zuerst lief, cloud.provider. Keine neue Instrumentierung in der App. Die Spans, die Sie bereits emittieren, werden zu den Metriken, auf die Sie alarmieren.
connectors:
spanmetrics:
histogram:
explicit: { buckets: [50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s] }
dimensions:
- name: http.route
- name: http.response.status_code
exemplars: { enabled: true }
resource_metrics_key_attributes: [service.name, cloud.provider, cloud.region]
service:
pipelines:
traces: { receivers: [otlp], processors: [resourcedetection, batch], exporters: [otlphttp/tempo, spanmetrics] }
metrics: { receivers: [spanmetrics, otlp], processors: [batch], exporters: [prometheusremotewrite/mimir] }
Die Metriken gehen nach Mimir (oder schlichtes Prometheus oder VictoriaMetrics; der Architektur ist es egal). Exemplare sind an, sodass ein Punkt im Latenzgraphen auf einen echten Trace verweist, der ihn erzeugt hat.
Die Regeln, einmal geschrieben
Grafana-Alarmregeln leben in einer YAML-Datei im Repository und werden bei jedem Deploy provisioniert, genau wie die Dashboards. Eine Regel pro SLO, nicht eine pro Dienst pro Cloud. Die Abfrage erledigt die Auffächerung:
# alerting/rules.yaml
groups:
- name: api-slo
interval: 1m
rules:
- alert: ApiErrorBudgetBurn
for: 2m
labels: { severity: page, team: platform }
annotations:
summary: 'api error budget burning {{ $labels.cloud_provider }} · {{ $value | humanizePercentage }}'
runbook: https://runbooks.internal/api-errors
expr: |
(
sum by (cloud_provider) (rate(calls_total{service_name="api", http_response_status_code=~"5.."}[5m]))
/
sum by (cloud_provider) (rate(calls_total{service_name="api"}[5m]))
) > (14.4 * 0.001)
and
(
sum by (cloud_provider) (rate(calls_total{service_name="api", http_response_status_code=~"5.."}[1h]))
/
sum by (cloud_provider) (rate(calls_total{service_name="api"}[1h]))
) > (14.4 * 0.001)
Das ist ein Multi-Window-Burn-Rate-Alarm für ein Verfügbarkeits-SLO von 99,9 %: wecken, wenn die Fehlerrate der letzten fünf Minuten und der letzten Stunde beide das 14,4-Fache des Fehlerbudgets überschreiten, was dem Verbrauch des gesamten 30-Tage-Budgets in etwa zwei Tagen entspricht. Das sum by (cloud_provider) ist die gesamte Multi-Cloud-Geschichte. Eine Regel, ein Ausdruck, und sie feuert getrennt für aws, gcp und azure, mit der Cloud im Alarmtitel, wenn nur eine davon brennt. Zieht die API von EKS nach GKE um, ändert sich, welcher Label-Wert feuert. Die Regel ändert sich nicht.
Eine zweite Regel mit längerem Fenster (6 Stunden und 3 Tage, Schwelle 1×) fängt langsames Abbrennen als Ticket statt als Page. Latenz bekommt dieselbe Behandlung auf histogram_quantile(0.99, sum by (le, cloud_provider) (rate(duration_bucket{...}[5m]))).
Was nativ bleibt, und wie es dazukommt
Die eigenen Alarme der Cloud verschwinden nicht; sie wechseln den Job. RDS-CPU, Aurora-ACU-Auslastung, Port-Allokationsfehler des NAT Gateways, DynamoDB-Throttles, Service-Quota-Limits: das sind Eigenschaften von Infrastruktur, die die Cloud besitzt, und das Monitoring der Cloud sieht sie zuerst und am besten. Diese Alarme halten wir nativ, definiert im selben CDK, das die Ressource definiert, und routen sie mit niedriger Priorität an denselben Pager. Sie informieren; sie wecken niemanden. Beginnt ein NAT, Pakete zu verwerfen, weckt der API-Fehlerraten-Alarm ohnehin innerhalb von zwei Minuten, und der NAT-Alarm sitzt im Incident-Kanal und erklärt, warum.
Diese Umkehrung ist der ganze Punkt. Symptome wecken. Ursachen annotieren. Die Symptom-Alarme sind Cloud-neutral, weil die Anwendung die Daten emittiert hat; die Ursachen-Alarme sind Cloud-spezifisch, weil die Cloud die Daten emittiert hat. Keiner gibt vor, der andere zu sein.
Drei Dinge, die uns länger gekostet haben, als sie sollten
Kardinalität. spanmetrics mit http.route als Dimension ist in Ordnung. Mit http.target (dem rohen Pfad, IDs eingeschlossen) erzeugt es eine Zeitreihe pro Bestellung und Mimir kippt um. Das Routen-Template verwenden, nie den Pfad.
Ruhe während Deploys. Ein Rolling Deploy erzeugt für ein paar Sekunden einen Schwall Connection Resets. Das for: 2m in der Page-Regel fängt ihn ab. Ohne es hat in der ersten Woche jeder Deploy jemanden geweckt.
Der Pager muss eine Sache sein. Wir hatten CloudWatch an ein On-Call-Tool und Grafana an ein anderes angeschlossen, weil sie zu verschiedenen Zeiten eingerichtet worden waren. Zwei Rotationen, zwei Apps auf dem Telefon und eine Nacht, in der die Person, die für das eine Bereitschaft hatte, für das andere keine hatte. Alles routet jetzt über Grafanas Contact Points in eine Rotation. Die nativen Alarme kommen über ein SNS-Topic → Webhook dorthin, was eine Stunde Einrichtung gekostet hat und Tag eins hätte sein sollen.
Die Kurzfassung
Auf die drei Zahlen alarmieren, die der Nutzer spürt, abgeleitet aus Spans, die Sie ohnehin emittieren, gespeichert in einem Metrik-Backend, das Ihnen gehört, ausgewertet von Regeln, die in Git liegen und ein sum by (cloud_provider) enthalten. Die Alarme der Cloud für die Dinge der Cloud behalten, mit niedriger Priorität, in denselben Pager. Wechselt ein Dienst die Cloud, ändert sich ein Label-Wert und niemand bearbeitet einen Alarm.
Möchten Sie die Regeldatei für Ihre eigenen Dienste, abgestimmt auf echte SLOs statt auf runde Zahlen? Sprechen Sie mit uns.