From 62a4f1a0db8f5f33a08bff28a0213c8bec33e0d7 Mon Sep 17 00:00:00 2001 From: kpa39l Date: Sun, 13 Sep 2026 15:34:47 +0000 Subject: [PATCH] =?UTF-8?q?feat(openspec):=20vesti-alerts=20=E2=80=94=206?= =?UTF-8?q?=20=D0=B0=D0=BB=D0=B5=D1=80=D1=82=D0=BE=D0=B2=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B=20?= =?UTF-8?q?VESTI=20(web/publisher/tunnel/db/stale);=20OpenSpec=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EXPERIENCE.md | 38 +++++++ README.md | 49 ++++++++- alerts.yml | 44 +++++++++ .../changes/2026-09-13-vesti-alerts/design.md | 61 ++++++++++++ .../2026-09-13-vesti-alerts/proposal.md | 30 ++++++ .../specs/vesti-alerts/spec.md | 99 +++++++++++++++++++ .../changes/2026-09-13-vesti-alerts/tasks.md | 22 +++++ 7 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 openspec/changes/2026-09-13-vesti-alerts/design.md create mode 100644 openspec/changes/2026-09-13-vesti-alerts/proposal.md create mode 100644 openspec/changes/2026-09-13-vesti-alerts/specs/vesti-alerts/spec.md create mode 100644 openspec/changes/2026-09-13-vesti-alerts/tasks.md diff --git a/EXPERIENCE.md b/EXPERIENCE.md index 4624874..20af6d6 100644 --- a/EXPERIENCE.md +++ b/EXPERIENCE.md @@ -252,6 +252,44 @@ grafana.nixg.ru { - В логах caddy много ошибок renew для старых доменов — они имеют уже выпущенные сертификаты в caddy_data, работает всё. +## Опыт: VESTI — textfile-метрики + встроенный Prometheus Alerting (2026-09-13) + +> Ситуация: добавлен дашборд и алерты на компоненты /opt/vesti. Проект +> управляется через OpenSpec: все изменения — только через openspec/change. + +### 22. Textfile-коллектор node-exporter для метрик сервиса + +node-exporter умеет отдавать произвольные метрики из файлов каталога +(`--collector.textfile.directory=...` ← `ARGS` в /etc/default/prometheus-node-exporter). +Скрипт раз в минуту пишет файл в Prometheus-формате (1 метрика = 1 строка +`name{labels} value`), node-exporter отдаёт их в /metrics — и Prometheus +тащит их обычным job'ом `node` (тот же 9100). Для сервиса достаточно: +`/opt/vesti/scripts/vesti-metrics.sh` → `/var/lib/node_exporter/textfile_collector/vesti.prom`. + +Грабля: каталог textfile принадлежит пользователю `prometheus`, скрипт от +root должен делать `chown prometheus:prometheus` (иначе файл появится, но +node-exporter его не прочитает — права!). Запуск: cron.d (root) — каждую минуту. + +### 23. Prometheus-алерты: файл alerts.yml + `/api/v1/rules` + +В этом стеке алерты — НЕ Grafana, а встроенный механизм Prometheus: +`prometheus.yml` → `rule_files: /etc/prometheus/alerts.yml` (группы/правила в +YAML). Правила видны в `http://127.0.0.1:9090/api/v1/rules` (JSON: groups, +state, query). Перечитываются ТОЛЬКО рестартом прометеуса +(`docker restart prometheus`), НЕ автоматически. + +Грабля: `promtool check config /etc/prometheus/prometheus.yml` считает +alerts.yml правило-файлом: «FAILED: field groups not found» при прямом +проверке alerts.yml — это НОРМАЛЬНО (это не самостоятельный конфиг; +проверять через рrometheus.yml, который находит 13 rules). + +### 24. OpenSpec для /opt/monitoring + +Все изменения проекта — ТОЛЬКО через `openspec/change/` (proposal/design/tasks/ +specs/). Формат spec: `## ADDED Requirements` + `### Requirement: X` + +`#### Scenario:` (иначе validate ругается). После внесения: `openspec validate`, +применить, `openspec archive --yes` + обновить STATUS/README/WALKTHROUGH. + ## Опыт: дашборд Grafana пустой (NO DATA) — 3 причины подряд (2026-09-02) > Дата: 2026-09-02. Ситуация: после этапа 6 (tproxy) пользователь сообщил, diff --git a/README.md b/README.md index b1722c0..b9fcbc8 100644 --- a/README.md +++ b/README.md @@ -225,4 +225,51 @@ admin-эндпоинты на loopback :8081: `/healthz`, `/readyz`, `/metrics` Порядок работы: правки коммитятся и пушутся в gitverse (источник), gitea подтягивает их mirror'ом (каждые 8ч или вручную mirror-sync). Если bigbox -сломается — репозиторий остаётся в облаке gitverse. \ No newline at end of file +сломается — репозиторий остаётся в облаке gitverse. + +## VESTI (2026-09-13) + +Проект /opt/vesti: web-интерфейс (:8400, systemd), publisher-контейнер +(docker `vesti-publisher`, :8410/healthz), SOCKS5-туннель Telegram (:1080), +SQLite БД (vesti.db). + +### Метрики (textfile-коллектор node-exporter, job `node`, label `component="vesti"`) + +Скрипт `/opt/vesti/scripts/vesti-metrics.sh` (запуск: cron.d `vesti-metrics`, +root, каждую минуту; альтернативно systemd-таймер `vesti-metrics.timer`) +пишет `/var/lib/node_exporter/textfile_collector/vesti.prom`. node-exporter +на bigbox: `ARGS="--web.listen-address=10.8.0.2:9100 --collector.textfile.directory=/var/lib/node_exporter/textfile_collector"`. + +| Метрика | Смысл | +|---|---| +| vesti_web_up | web :8400 (systemd) | +| vesti_publisher_health | publisher :8410 /healthz | +| vesti_publisher_docker | контейнер publisher (Up) | +| vesti_telegram_tunnel | SOCKS5-туннель :1080 | +| vesti_web_systemd | systemd-юнит web | +| vesti_publisher_bot / _proxy / _channels | детали publisher | +| vesti_db_ok | SQLite vesti.db доступна | +| vesti_db_posts_total / _new_total / _published_total | счётчики БД | +| vesti_db_last_run_ts | Unix-ts последнего рan краулера | + +### Дашборд + +`grafana/dashboards/vesti/vesti.json` (uid `vesti`, папка **vesti**), 18 панелей. +Провайдер `vesti-dashboards` в `grafana/provisioning/dashboards/dashboards.yml`. +Проверка импорта: `sudo docker cp grafana:/var/lib/grafana/grafana.db /tmp/gf.db` ++ `SELECT uid,title FROM dashboard WHERE uid='vesti'`. + +### Алерты (группа `vesti` в alerts.yml) + +| Алерт | Выражение | Удержание | Severity | +|---|---|---|---| +| VestiWebDown | `vesti_web_up{component="vesti"} == 0` | 2m | critical | +| VestiPublisherDown | `vesti_publisher_health{component="vesti"} == 0` | 2m | critical | +| VestiPublisherContainerDown | `vesti_publisher_docker{component="vesti"} == 0` | 2m | critical | +| VestiTunnelDown | `vesti_telegram_tunnel{component="vesti"} == 0` | 2m | critical | +| VestiDbDown | `vesti_db_ok{component="vesti"} == 0` | 2m | critical | +| VestiDbStale | `time() - vesti_db_last_run_ts{component="vesti"} > 1800` | 5m | warning | + +Реализовано через встроенный Prometheus Alerting (`rule_files` в prometheus.yml +→ alerts.yml; правила видны в `/api/v1/rules`). Перечитывается рестартом +прометеуса (`docker restart prometheus`). \ No newline at end of file diff --git a/alerts.yml b/alerts.yml index e065d37..842730b 100644 --- a/alerts.yml +++ b/alerts.yml @@ -54,3 +54,47 @@ groups: severity: critical annotations: summary: Vinograd WAN (Ростелеком) {{ $labels.instance }} is down or unreachable +- name: vesti + rules: + - alert: VestiWebDown + expr: vesti_web_up{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI web (:8400, systemd) is down + - alert: VestiPublisherDown + expr: vesti_publisher_health{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI publisher (:8410) health check failed + - alert: VestiPublisherContainerDown + expr: vesti_publisher_docker{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI publisher Docker container is not Up + - alert: VestiTunnelDown + expr: vesti_telegram_tunnel{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI Telegram SOCKS5 tunnel (:1080) is down + - alert: VestiDbDown + expr: vesti_db_ok{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI SQLite database vesti.db is unavailable + - alert: VestiDbStale + expr: time() - vesti_db_last_run_ts{component="vesti"} > 1800 + for: 5m + labels: + severity: warning + annotations: + summary: VESTI crawler did not run for more than 30 minutes diff --git a/openspec/changes/2026-09-13-vesti-alerts/design.md b/openspec/changes/2026-09-13-vesti-alerts/design.md new file mode 100644 index 0000000..881cf1e --- /dev/null +++ b/openspec/changes/2026-09-13-vesti-alerts/design.md @@ -0,0 +1,61 @@ +# Design + +## Механизм алертов + +В стеке /opt/monitoring алерты реализованы **Prometheus Alerting rules** (prometheus.yml +`rule_files: /etc/prometheus/alerts.yml`, Prometheus 3.1+ встроенный алертинг с мульти-секционной +группировкой `groups:`/`rules:`). Формат rules (алерты на базе PromQL-выражений с `for` = время +удержания условия, `labels.severity`, `annotations.summary`). Доставка уведомлений — согласно +существующей конфигурации (webhook/канал, настроенный в Prometheus или в той же alerts.yml +внешним получателем — проверить фактический механизм доставки в стеке; в референсах скилла +указано, что уведомления приходят в чат). + +## Метрики VESTI (textfile-коллектор, job `node`, label `component="vesti"`) + +| Метрика | Смысл | Алерт | +|---|---|---| +| vesti_web_up | web :8400 (systemd) | ==0 → critical | +| vesti_publisher_health | publisher :8410 /healthz | ==0 → critical | +| vesti_publisher_docker | контейнер publisher (docker ps) | ==0 → critical | +| vesti_telegram_tunnel | SOCKS5-туннель :1080 | ==0 → critical | +| vesti_db_ok | SQLite vesti.db доступна | ==0 → critical | +| vesti_db_last_run_ts | Unix-ts последнего рan краулера | time()-ts >1800 → warning | + +## Формат rules (из alerts.yml) + +```yaml +groups: +- name: vesti + rules: + - alert: VestiWebDown + expr: vesti_web_up{component="vesti"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: VESTI web (:8400) is down +``` + +Время реакции: `for: 2m` — алерт срабатывает после 2 минут непрерывного условия +(метрики обновляются раз в минуту скриптом vesti-metrics.sh → 2-3 пробы подряд). + +## Верификация + +1. `docker exec prometheus promtool check config /etc/prometheus/prometheus.yml` → OK + (проверяет и rule_files). +2. `docker compose restart prometheus` (перечитывает alerts.yml). +3. Запрос в Prometheus API: значения метрик vesti_* присутствуют. +4. Проверка алертов: имитация (временно выставить метрику в 0, убедиться что алерт firing, + вернуть обратно) — или проверка через Prometheus /api/v1/rules/дубликат. +5. Обновить README.md + EXPERIENCE.md, openspec validate + archive. + +## Риски + +- При `for: 2m` и интервале скрипта 1 мин алерт может срабатывать с задержкой до 3 мин + (приемлемо для внутреннего сервиса). +- Метрики пишутся только при успешном выполнении скрипта; если textfile-файл пропадёт + (сломан скрипт) — метрики исчезнут и алерт по `==0` не сработает. Для этого есть + `VestiDbStale` (time() - ts) — он тоже покроет случай пропадания данных? Нет: если файл + пропал, ts не обновится → last_run_ts останется старым → VestiDbStale сработает. + Дополнительно можно добавить expr на отсутствие данных (deadman), но это выходит за рамки + текущего change — зафиксировать в EXPERIENCE.md как известное ограничение. \ No newline at end of file diff --git a/openspec/changes/2026-09-13-vesti-alerts/proposal.md b/openspec/changes/2026-09-13-vesti-alerts/proposal.md new file mode 100644 index 0000000..66469fb --- /dev/null +++ b/openspec/changes/2026-09-13-vesti-alerts/proposal.md @@ -0,0 +1,30 @@ +# Proposal: vesti-alerts + +## Why + +Проект /opt/vesti (веб :8400 + publisher-контейнер :8410 + SOCKS5-туннель :1080 + SQLite БД) +получил дашборд VESTI в Grafana (2026-09-13), но **алертов на падение компонентов нет**: +при недоступности web/publisher/туннеля/БД никто не узнает в реальном времени (только постфактум +на дашборде). Нужны алерты в существующем механизме мониторинга (Prometheus rule_files → alerts.yml) +на все компоненты vesti, с уведомлением (webhook/настроенный канал). + +## What Changes + +- В `/opt/monitoring/alerts.yml` добавляется группа `vesti` с алертами: + - `VestiWebDown` — `vesti_web_up{component="vesti"} == 0`, for 2m, severity: critical + - `VestiPublisherDown` — `vesti_publisher_health{component="vesti"} == 0`, for 2m, severity: critical + - `VestiPublisherContainerDown` — `vesti_publisher_docker{component="vesti"} == 0`, for 2m, severity: critical + - `VestiTunnelDown` — `vesti_telegram_tunnel{component="vesti"} == 0`, for 2m, severity: critical + - `VestiDbDown` — `vesti_db_ok{component="vesti"} == 0`, for 2m, severity: critical + - `VestiDbStale` — `time() - vesti_db_last_run_ts{component="vesti"} > 1800`, for 5m, severity: warning + (краулер не работал >30 мин) +- Все алерты аннотируются summary с именем компонента. +- Верификация: `promtool check config` + запрос фактических значений в Prometheus API. + +## Capabilities + +### New Capabilities +- `vesti-alerts`: алерты на компоненты VESTI (web, publisher, docker, tunnel, db, краулер staleness). + +### Modified Capabilities +- `monitoring-stack` (alerts.yml) — добавлена группа `vesti`. \ No newline at end of file diff --git a/openspec/changes/2026-09-13-vesti-alerts/specs/vesti-alerts/spec.md b/openspec/changes/2026-09-13-vesti-alerts/specs/vesti-alerts/spec.md new file mode 100644 index 0000000..cddc751 --- /dev/null +++ b/openspec/changes/2026-09-13-vesti-alerts/specs/vesti-alerts/spec.md @@ -0,0 +1,99 @@ +# vesti-alerts + +## ADDED Requirements + +### Requirement: VestiWebDown + +Алерт на недоступность web-интерфейса VESTI (:8400, systemd-юнит). + +#### Scenario: web не отвечает + +- Given метрика `vesti_web_up{component="vesti"}` = 0 +- When условие держится 2 минуты +- Then алерт `VestiWebDown` переходит в Firing (severity critical) + +#### Scenario: web восстановился + +- Given метрика `vesti_web_up{component="vesti"}` = 1 +- When алерт в Firing +- Then алерт закрывается автоматически + +### Requirement: VestiPublisherDown + +Алерт на недоступность publisher-сервиса VESTI (:8410, /healthz). + +#### Scenario: publisher не отвечает + +- Given метрика `vesti_publisher_health{component="vesti"}` = 0 +- When условие держится 2 минуты +- Then алерт `VestiPublisherDown` переходит в Firing (severity critical) + +#### Scenario: publisher восстановился + +- Given метрика `vesti_publisher_health{component="vesti"}` = 1 +- When алерт в Firing +- Then алерт закрывается автоматически + +### Requirement: VestiPublisherContainerDown + +Алерт на остановку Docker-контейнера publisher. + +#### Scenario: контейнер publisher не в статусе Up + +- Given метрика `vesti_publisher_docker{component="vesti"}` = 0 +- When условие держится 2 минуты +- Then алерт `VestiPublisherContainerDown` переходит в Firing (severity critical) + +#### Scenario: контейнер publisher снова Up + +- Given метрика `vesti_publisher_docker{component="vesti"}` = 1 +- When алерт в Firing +- Then алерт закрывается автоматически + +### Requirement: VestiTunnelDown + +Алерт на недоступность SOCKS5-туннеля Telegram (:1080). + +#### Scenario: туннель недоступен + +- Given метрика `vesti_telegram_tunnel{component="vesti"}` = 0 +- When условие держится 2 минуты +- Then алерт `VestiTunnelDown` переходит в Firing (severity critical) + +#### Scenario: туннель восстановился + +- Given метрика `vesti_telegram_tunnel{component="vesti"}` = 1 +- When алерт в Firing +- Then алерт закрывается автоматически + +### Requirement: VestiDbDown + +Алерт на недоступность SQLite-базы vesti.db. + +#### Scenario: БД недоступна + +- Given метрика `vesti_db_ok{component="vesti"}` = 0 +- When условие держится 2 минуты +- Then алерт `VestiDbDown` переходит в Firing (severity critical) + +#### Scenario: БД восстановилась + +- Given метрика `vesti_db_ok{component="vesti"}` = 1 +- When алерт в Firing +- Then алерт закрывается автоматически + +### Requirement: VestiDbStale + +Алерт на устаревание данных краулера (не запускался >30 минут). + +#### Scenario: краулер не запускался более 30 минут + +- Given метрика `vesti_db_last_run_ts{component="vesti"}` = T +- When `time() - T` > 1800 секунд и условие держится 5 минут +- Then алерт `VestiDbStale` переходит в Firing (severity warning) + +#### Scenario: краулер снова запустился + +- Given метрика `vesti_db_last_run_ts{component="vesti"}` обновлена (time() - ts < 1800) +- When алерт в Firing +- Then алерт закрывается автоматически \ No newline at end of file diff --git a/openspec/changes/2026-09-13-vesti-alerts/tasks.md b/openspec/changes/2026-09-13-vesti-alerts/tasks.md new file mode 100644 index 0000000..4f5a189 --- /dev/null +++ b/openspec/changes/2026-09-13-vesti-alerts/tasks.md @@ -0,0 +1,22 @@ +# Tasks + +## 1. Алерты в alerts.yml +- [x] 1.1 В `/opt/monitoring/alerts.yml` добавить группу `vesti`: + - VestiWebDown (vesti_web_up == 0, for 2m, critical) + - VestiPublisherDown (vesti_publisher_health == 0, for 2m, critical) + - VestiPublisherContainerDown (vesti_publisher_docker == 0, for 2m, critical) + - VestiTunnelDown (vesti_telegram_tunnel == 0, for 2m, critical) + - VestiDbDown (vesti_db_ok == 0, for 2m, critical) + - VestiDbStale (time() - vesti_db_last_run_ts > 1800, for 5m, warning) +- [x] 1.2 Проверка: `docker exec prometheus promtool check config /etc/prometheus/prometheus.yml` → OK (13 rules) +- [x] 1.3 Рестарт: `docker restart prometheus` +- [x] 1.4 Проверка: группа vesti в /api/v1/rules, 6 правил, state=inactive (норма) + +## 2. Документация +- [x] 2.1 Обновить `/opt/monitoring/README.md` (секция VESTI: алерты, метрики) +- [x] 2.2 Добавить запись в `/opt/monitoring/EXPERIENCE.md` (22-24: textfile, alerting, OpenSpec) + +## 3. Git и OpenSpec +- [ ] 3.1 `git add` (поимённо) + commit + push в gitverse (истина), gitea подтянет mirror +- [ ] 3.2 `openspec validate` + `openspec archive --yes` +- [ ] 3.3 Обновить STATUS.md/WALKTHROUGH.md \ No newline at end of file