Пишем хорошие PrometheusRules: метки, ссылки на Grafana и humanizeTimestamp
Published: 2026-02-16
PrometheusRule, которое срабатывает вовремя и даёт дежурному достаточно контекста для действий, стоит десяти дашбордов. Плохие алерты либо шумят (срабатывают на каждый кратковременный сбой), либо молчат (неверные селекторы меток — алерт не доходит до Alertmanager). Вот паттерн, к которому мы пришли после нескольких десятков продакшн-алертов.
Анатомия правила алерта
yamlapiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-service-alerts
namespace: observability
labels:
release: kube-prom-stack # должно совпадать с селектором оператора Prometheus
spec:
groups:
- name: my-service
interval: 30s
rules:
- alert: MyServiceDown
expr: up{job="my-service"} < 1
for: 5m
labels:
service: my-service
severity: disaster
groupp: admin
url: 'https://grafana.example.com/d/my-service/overview'
event: 'Service Down |{{$labels.job}}|{{$labels.instance}}'
annotations:
description: >-
Service {{$labels.instance}} has been down for more than 5 min,
timestamp: {{ with query "time()+10800" }}
{{ . | first | value | humanizeTimestamp }}{{ end }}
Метка release: kube-prom-stack — именно так оператор kube-prometheus-stack обнаруживает PrometheusRule. Без неё правило молча игнорируется — никакой ошибки, никакого предупреждения. Проверить загрузку:
bash# Убедиться, что метка есть
kubectl get prometheusrule -n observability my-service-alerts -o yaml | grep -A5 "labels:"
# Проверить загрузку в Prometheus
kubectl port-forward -n observability svc/kube-prom-stack-prometheus 9090:9090
# открыть localhost:9090/rules в браузере
Почему interval: 30s на группе
Интервал вычисления правил в Prometheus по умолчанию — 1m. Для продакшн-сервисов, где нужны быстрые алерты, задайте группе интервал 30s — он переопределит глобальный только для этой группы, не затрагивая остальные.
Если уменьшить глобальный интервал до 30s, это затронет все правила, включая потенциально дорогие recording rules. Интервал на уровне группы — точечный подход.
Для некритичных алертов (использование диска, тренды глубины очереди) достаточно 2m — снижает нагрузку.
Метки для маршрутизации
Наш Alertmanager маршрутизирует по трём измерениям:
| Метка | Назначение |
|---|---|
severity |
disaster / high / warning / average |
groupp |
какая команда получит алерт (admin, dev, infra) |
service |
для группировки и дедупликации |
Двойная p в groupp — намеренно, чтобы избежать коллизии с зарезервированной меткой Prometheus group.
Конфигурация маршрутизации Alertmanager
yamlroute:
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: disaster
receiver: pagerduty-critical
continue: false
- match:
groupp: admin
receiver: telegram-admin
- match:
groupp: dev
receiver: telegram-dev
group_by: ['alertname', 'service'] гарантирует: если три экземпляра my-service упадут одновременно, придёт одно уведомление, а не три.
Метка url: ссылка на дашборд Grafana
Добавьте URL дашборда Grafana в метку — Alertmanager включит его в шаблон уведомления:
yamllabels:
url: 'https://grafana.example.com/d/my-dashboard/my-service'
В шаблоне receiver Alertmanager:
{{ range .Alerts }}
*{{ .Labels.event }}*
{{ .Annotations.description }}
🔗 {{ .Labels.url }}
{{ end }}
Дежурный получает в уведомлении прямую ссылку на нужный дашборд — не придётся искать его в интерфейсе Grafana в три часа ночи.
Для ссылок с предустановленным временным диапазоном «вокруг момента срабатывания»:
yamlurl: 'https://grafana.example.com/d/my-dashboard/my-service?from=now-1h&to=now'
Метка event: понятный заголовок
yamlevent: 'Kafka Down |{{$labels.job}}|{{$labels.instance}}'
Эта метка становится заголовком уведомления. Контекст через разделители | позволяет с первого взгляда понять, какой экземпляр затронут.
Ещё примеры:
yamlevent: 'High Error Rate |{{$labels.service}}| {{$value | humanize}}%'
event: 'Disk Full |{{$labels.node}}| {{$value | humanize}}% used'
event: 'PVC Unbound |{{$labels.persistentvolumeclaim}}|{{$labels.namespace}}'
humanizeTimestamp для временных меток
Аннотации алерта вычисляются в момент срабатывания и могут включать PromQL-выражения. Всегда добавляйте читаемую временную метку:
yamlannotations:
description: >-
{{ .Labels.instance }} has been down for 5 min,
timestamp: {{ with query "time()+10800" }}
{{ . | first | value | humanizeTimestamp }}{{ end }}
time() возвращает Unix epoch. +10800 — поправка на UTC+3 (UTC+0 → +0, UTC+2 → +7200). humanizeTimestamp рендерит 2026-07-30 14:23:00 +0000 UTC.
Другие полезные функции humanize:
humanize— преобразует1048576в1.05Mhumanize1024— преобразует1048576в1.00MihumanizeDuration— преобразует3665в1h 1m 5s
for: — не стрелять на флапах
Всегда задавайте for:. Без него один-единственный сбой сбора метрик мгновенно вызовет алерт.
| Сценарий | for: |
|---|---|
| Сервис полностью недоступен | 1m или 5m |
| Всплеск ошибок | 5m |
| Нехватка памяти/CPU | 10m |
| Предупреждение по диску | 15m |
| Истечение сертификата | 1h |
Group interval vs for
Это разные параметры:
interval: 30s(на группе) — как часто Prometheus вычисляет выражение правилаfor: 5m— как долго выражение должно оставаться истинным, прежде чем алерт перейдёт изpendingвfiring
При for: 5m и интервале 30s Prometheus проверяет условие каждые 30 секунд и срабатывает после 10 положительных вычислений подряд. Если на седьмом условие перестало выполняться — алерт возвращается в pending, счётчик обнуляется.
Посмотреть pending-алерты:
bashcurl -s http://localhost:9090/api/v1/alerts | jq '.data.alerts[] | select(.state=="pending")'
Recording rules для дорогих выражений
yamlgroups:
- name: my-service-recordings
interval: 30s
rules:
- record: job:request_error_rate:5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)
- name: my-service-alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: job:request_error_rate:5m > 0.05
for: 5m
Придерживайтесь соглашения об именовании job:metric_name:duration. Выражение алерта обращается к заранее вычисленной метрике, а не повторяет дорогой range query.
Тестирование правил локально
bashpromtool check rules my-prometheusrule.yaml
cat > test.yaml << 'EOF'
rule_files:
- my-prometheusrule.yaml
tests:
- interval: 1m
input_series:
- series: 'up{job="my-service",instance="host:9090"}'
values: '1 1 1 0 0 0 0 0 0'
alert_rule_test:
- eval_time: 8m
alertname: MyServiceDown
exp_alerts:
- exp_labels:
severity: disaster
instance: host:9090
EOF
promtool test rules test.yaml
Типичные ошибки
- Нет метки release — PrometheusRule молча не загружается
- Слишком маленький
for:— поток алертов при каждом rolling update - Нет метки
service— Alertmanager группирует несвязанные алерты вместе - Одинаковые имена алертов — второе правило молча перезаписывает первое
- Ошибки PromQL в аннотациях — алерт срабатывает, но в description выводится
<error>; проверяйте черезpromtool check rules