Alarme
Ongrids Alarm-Subsystem ist eine einzelne Tick-Loop, die jede aktivierte Regelzeile durchläuft, das passende Backend fragt (Prom für Metriken + Trace-Spanmetrics, Loki für Logs), ob das Prädikat matcht, und Feuerungen in die incidents-Tabelle schreibt.
Es gibt keinen separaten Alertmanager, keine separate Rules-Datei. Regeln liegen in MySQL, der Evaluator pollt sie bei einem 30s-Cache-Refresh, und Benachrichtigungen fächern durch die Kanal-Registry aus.
Die 14 Regelarten
Regeln werden mit einer kind-Spalte gespeichert. Der Compiler dispatcht darauf.
Der Compiler liegt in rules.go und die Evaluatoren in evaluators_phaseA.go + evaluators_phaseB.go.
Die 8+6-Aufteilung ist HLD-004s Phase-A (Metriken) / Phase-B (Logs + Traces), gelandet 2026-05-08.
Metrik-Arten (Phase A)
| Art | Was sie tut | Spec-Felder |
|---|---|---|
metric_raw | PromQL-Ausdruck IST das Prädikat. Feuert pro zurückgegebenem Vektor-Eintrag. | expr |
metric_anomaly | Z-Score oder MAD über ein rollendes Baseline-Fenster. | metric, method, baseline_window, baseline_step, deviation, for_seconds |
metric_forecast | predict_linear(metric[fit_window], predict_seconds) <op> threshold. | metric, fit_window, predict_seconds, operator, threshold |
metric_burn_rate | Googles SRE Multi-Window-Multi-Burn über ein SLO. ALLE Fenster müssen triggern. | sli, slo, burns[].window, burns[].multiplier |
Die Legacy-prom_query-Art wurde in metric_raw umbenannt. Die Legacy-metric_threshold-Form ist jetzt ein nur-UI-Eintrag, der zur Speicherzeit zu metric_raw kompiliert wird — es gibt keinen separaten Evaluator dafür.
// internal/manager/biz/alert/rules.go:36
type MetricRawRule struct {
ID uint64
RuleKey string
Name string
Severity string
ScopeType string // host / global / monitoring_pipeline
RunbookURL string
Labels map[string]string
Expr string // canonical predicate, e.g. `up == 0`
}Log- + Trace-Arten (Phase B)
| Art | Was sie tut | Backend |
|---|---|---|
log_match | count_over_time(<stream> |~ <filter> [window]) <op> threshold gegen Loki. Pro Labelset-Feuern. | Loki |
log_volume | Gleiche Form wie log_match, Current-Window-Count vs. absoluter Schwellwert. | Loki |
trace_latency | histogram_quantile(q, sum by(le)(rate(traces_spanmetrics_latency_bucket[w]))) > threshold_ms. | Prom (Spanmetrics) |
trace_error_rate | 100 * (sum rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}) / sum rate(...)) > pct. | Prom (Spanmetrics) |
Trace-Arten fragen Prometheus ab, nicht Tempo. Der Spanmetrics-Generator scrapt Tempo und schreibt traces_spanmetrics_*-Serien zurück in Prom — Prom abzufragen hält den Alarm-Evaluator auf einer Query-Engine und nutzt die gesamte Operator-Filter-/Schwellwert-Logik wieder.
Scope-Typen
Jede Regel hat scope_type ∈ {host, global, monitoring_pipeline}. Default pro Art definiert in defaultScopeForKind in rules.go.
host— Incident muss einedevice_idtragen. Der Evaluator parst dasdevice_id-Label aus den Prom-Ergebnislabels;validateFiringlehnt host-scoped Feuerungen ohne eine ab.global— service-level Alarme (trace_, log_), die nicht an einen einzelnen Host pinnen.monitoring_pipeline— Meta-Alarme über Ongrid selbst (scrape_down,prom_ingest_fail, ...).
Der Evaluator-Tick
PipelineEvaluator.evaluate läuft alle Interval (default 5 min, konfigurierbar via PipelineEvaluatorOpts.Interval).
func (e *PipelineEvaluator) evaluate(ctx context.Context) {
now := e.now()
if e.edges != nil {
e.refreshDeviceStalenessGauge(ctx, now)
}
if e.prom != nil {
e.evaluatePromQuery(ctx, now)
e.evaluateMetricAnomaly(ctx, now)
e.evaluateMetricForecast(ctx, now)
e.evaluateMetricBurnRate(ctx, now)
e.evaluateTraceLatency(ctx, now)
e.evaluateTraceErrorRate(ctx, now)
}
if e.logq != nil {
e.evaluateLogMatch(ctx, now)
e.evaluateLogVolume(ctx, now)
}
}Ein nil-Backend überspringt die entsprechenden Arten stillschweigend — Loki down bricht keine Metrik-Alarme.
Dedup + Recovery
Der Evaluator trackt firingSnapshot[ruleKey] = set<dedupeKey> über Ticks hinweg. Ein im letzten Tick präsenter, in diesem Tick abwesender Schlüssel → PromQLs Vergleichsfilter ließ die Serie fallen → Prädikat geräumt → SystemResolveIncident feuert mit "prom condition cleared". So erholen sich Alarme ohne separaten „Resolve"-Evaluator.
Dedupe-Schlüssel-Form: pipeline:<rule_key>:<sorted-label-set> — Provenienz-Labels (__name__, ongrid_source) werden entfernt, sodass derselbe Alarm, der sowohl vom eingebetteten als auch vom Cloud-Collector gemeldet wird, zu einem Incident dedupliziert, nicht zu zwei (labelSetKey).
Kanal-Fan-out
Wenn ein Incident feuert, konsultiert der Notifier.MaybeNotify-Pfad den ChannelResolver:
- Per-Regel-Pinning — wenn
rule.notify_channel_ids_jsonnicht leer ist, matchen nur diese Channel-IDs (und nur die aktivierten). - Andernfalls wird jede aktivierte
notification_channels-Zeile nachmatch_severity_minundmatch_scope_typesgefiltert. - Wenn nichts matcht, fällt der Resolver auf eine synthetische Kanalliste zurück, die von
DefaultChannelsgeseedet wird, sodass Benachrichtigungen nie verschwinden.
Siehe router.go.
Inhibition
Zwei eingebaute Inhibition-Regeln (inhibit.go), die die lauten Default-Fälle abdecken:
edge_offline:edge_Xinhibitiert jedeshost:X:*— wenn eine Edge nicht erreichbar ist, wird jeder host-scoped Alarm auf ihr unterdrückt.pipeline:prom_ingest_failinhibitiertpipeline:scrape_down:*— wenn Prometheus selbst nicht ingesten kann, ist jeder „Target Down"-Alarm Rauschen.
Eine zukünftige inhibition_rules-Tabelle erweitert dies auf admin-definierte Gruppen.
Cooldown + Dämpfung
NotifyOpts.Cooldown (default 10 Minuten) begrenzt Re-Notifizierung auf demselben dedupe_key. Der Dämpfungsfilter sitzt innerhalb von Usecase.MaybeNotify, sodass der Channel-Resolver und Inhibitor bei jeder Feuerung weiterhin laufen — nur der tatsächliche Notifier.Send wird übersprungen.
Siehe auch
- RCA — was passiert, wenn ein Incident feuert.
- Logs — Loki + die
log_match/log_volume-Evaluatoren. - Traces — Tempo + die
trace_latency/trace_error_rate-Evaluatoren. - Kanäle-Übersicht — wie Slack- / Telegram- / Lark- / DingTalk- / WeCom- + Webhook-Kanäle konfiguriert werden.
- Alarmregel-Schema — das Wire-Format der Regel-Zeile.