Алерты
Подсистема алертов Ongrid — это единственный tick-цикл, который обходит каждую включённую строку правила, спрашивает соответствующий бэкенд (Prom для метрик + trace spanmetrics, Loki для логов), сопоставляется ли предикат, и записывает срабатывания в таблицу incidents.
Отдельного Alertmanager нет, нет отдельного rules-файла. Правила живут в MySQL, evaluator опрашивает их с обновлением кэша 30с, а уведомления расходятся через реестр каналов.
14 типов правил
Правила хранятся со столбцом kind. Компилятор диспетчирует на нём.
Компилятор находится в rules.go, а evaluator — в evaluators_phaseA.go + evaluators_phaseB.go.
Разделение 8+6 — это Phase-A (метрики) / Phase-B (логи + трейсы) HLD-004, приземлилось 2026-05-08.
Метрики (Phase A)
| Kind | Что делает | Поля spec |
|---|---|---|
metric_raw | PromQL-выражение И ЕСТЬ предикат. Срабатывает на каждую возвращённую запись вектора. | expr |
metric_anomaly | Z-score или MAD поверх скользящего окна baseline. | 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 | Google SRE multi-window multi-burn над SLO. ВСЕ окна должны триггериться. | sli, slo, burns[].window, burns[].multiplier |
Унаследованный kind prom_query переименован в metric_raw. Унаследованная форма metric_threshold теперь — UI-only запись, которая компилируется в metric_raw во время сохранения — отдельного evaluator для неё нет.
// 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`
}Логи + трейсы (Phase B)
| Kind | Что делает | Бэкенд |
|---|---|---|
log_match | count_over_time(<stream> |~ <filter> [window]) <op> threshold против Loki. Срабатывание на label-set. | Loki |
log_volume | Такая же форма, как log_match, счёт текущего окна vs абсолютный порог. | 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) |
Трейсовые типы запрашивают Prometheus, не Tempo. Генератор spanmetrics скрейпит Tempo и записывает серии traces_spanmetrics_* обратно в Prom — запрос к Prom держит alert evaluator на одном query-движке и переиспользует всю логику фильтрации операторов / порогов.
Типы scope
У каждого правила есть scope_type ∈ {host, global, monitoring_pipeline}. По умолчанию per-kind определяется в defaultScopeForKind в rules.go.
host— инцидент должен нестиdevice_id. Evaluator парсит labeldevice_idиз меток результата Prom;validateFiringотвергает host-scoped срабатывания без него.global— service-level алерты (trace_, log_), которые не закрепляются за единственным хостом.monitoring_pipeline— мета-алерты о самом Ongrid (scrape_down,prom_ingest_fail, ...).
Evaluator tick
PipelineEvaluator.evaluate запускается каждый Interval (по умолчанию 5 мин, настраивается через 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)
}
}nil-бэкенд молча пропускает соответствующие kind — упавший Loki не ломает алерты на метриках.
Dedup + recovery
Evaluator отслеживает firingSnapshot[ruleKey] = set<dedupeKey> между тиками. Ключ, присутствующий в прошлом тике, но отсутствующий в этом → фильтр сравнения PromQL отбросил серию → предикат снят → SystemResolveIncident срабатывает с "prom condition cleared". Так алармы восстанавливаются без отдельного evaluator «resolve».
Форма ключа dedupe: pipeline:<rule_key>:<sorted-label-set> — provenance-метки (__name__, ongrid_source) срезаются, чтобы один и тот же аларм, сообщённый и встроенным, и облачным коллектором, дедуплицировался в один инцидент, а не два (labelSetKey).
Fan-out по каналам
Когда инцидент срабатывает, путь Notifier.MaybeNotify обращается к ChannelResolver:
- Per-rule прикрепление — если
rule.notify_channel_ids_jsonнепустой, совпадают только эти channel id (и только включённые). - Иначе каждая включённая строка
notification_channelsфильтруется поmatch_severity_minиmatch_scope_types. - Если ничего не совпало, resolver откатывается на синтетический список каналов, посеянный из
DefaultChannels, так что уведомления никогда не исчезают.
См. router.go.
Inhibition
Два встроенных правила inhibition (inhibit.go), покрывающие шумные дефолты:
edge_offline:edge_Xподавляет любыеhost:X:*— когда edge недостижим, каждый host-scoped аларм на нём подавлен.pipeline:prom_ingest_failподавляетpipeline:scrape_down:*— когда сам Prometheus не может принять данные, каждый аларм «target down» — шум.
Будущая таблица inhibition_rules расширит это на admin-определённые группы.
Cooldown + дампенинг
NotifyOpts.Cooldown (по умолчанию 10 минут) ограничивает повторное уведомление на тот же dedupe_key. Фильтр дампенинга сидит внутри Usecase.MaybeNotify, так что resolver канала и inhibitor всё ещё запускаются на каждое срабатывание — пропускается только фактический Notifier.Send.
См. также
- RCA — что происходит, когда инцидент срабатывает.
- Логи — Loki + evaluator
log_match/log_volume. - Трейсы — Tempo + evaluator
trace_latency/trace_error_rate. - Обзор каналов — как настраиваются каналы Slack / Telegram / Lark / DingTalk / WeCom + webhook.
- Схема правила алерта — wire-формат строки правила.