From 2d7cf436aa4bdb8a5619d9a27f15e237d5223a75 Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Thu, 16 Apr 2026 15:21:43 +0300 Subject: [PATCH] Renew docs --- ARCHITECTURE.md | 264 ++++++++++++++++++++++++++++++++++++------------ README.md | 55 +++++++++- 2 files changed, 251 insertions(+), 68 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e78e590..c8c5c41 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -16,23 +16,27 @@ PostgreSQL fetchClientsData() map[clientTitle]ClientData │ - ├──────────────────────────────────────┐ - ▼ ▼ - generateSingleDashboard() generateAndSendAlerts() - │ │ - ▼ ▼ - dashboard_template.json alert_rules_template.json - (Git-репозиторий) (Git-репозиторий) - │ │ - ▼ ▼ - POST /api/dashboards/db POST/PUT /api/v1/provisioning/alert-rules - (Grafana API) (Grafana Provisioning API, параллельно x5) - │ │ - ▼ ▼ - state.json ──── clients_hash state.json ──── alerts_hash - └─── alert_template_hash + ├──────────────────────────────────────────────────────┐ + │ │ + ▼ ▼ + generateSingleDashboard() generateAndSendAlerts() (OS) + generateVLDashboard() generateAndSendAlertsVL() (VL) + │ │ + ▼ ▼ + dashboard_template.json alert_rules_template.json + (Git-репозиторий) (Git-репозиторий) + │ │ + ▼ ▼ + POST /api/dashboards/db POST/PUT /api/v1/provisioning/alert-rules + (Grafana API) (Grafana Provisioning API, параллельно x5) + │ │ + ▼ ▼ + state.json ──── clients_hash state.json ──── alerts_hash + └─── alert_template_hash ``` +Генерация дашбордов и алертов для OS и VL управляется независимо через переменные окружения `DASHBOARD_OS_ENABLED`, `DASHBOARD_VL_ENABLED`, `ALERTS_OS_ENABLED`, `ALERTS_VL_ENABLED`. + --- ## Структура файлов @@ -42,9 +46,13 @@ PostgreSQL | `main.go` | Точка входа, оркестрация всего потока | | `config.go` | Константы, структуры `Config`, `ClientData`, `DomainInfo`, `DashboardTemplate` | | `database.go` | Подключение к PostgreSQL, `fetchClientsData()` | -| `dashboard.go` | Генерация JSON дашборда из шаблона и данных клиентов | -| `panels.go` | Построение отдельных панелей, реестр query-режимов, `applyRPSThreshold()` | -| `alerts.go` | Генерация и отправка правил алертинга, параллельный upsert | +| `dashboard.go` | Генерация JSON дашборда (OpenSearch) из шаблона и данных клиентов | +| `dashboard_vl.go` | Генерация JSON дашборда для VictoriaLogs | +| `panels.go` | Построение панелей (OpenSearch), реестр query-режимов, `applyRPSThreshold()` | +| `panels_vl.go` | Построение панелей для VictoriaLogs, LogsQL query builders | +| `alerts.go` | Генерация и отправка правил алертинга (OpenSearch), параллельный upsert | +| `alerts_vl.go` | Генерация и отправка правил алертинга для VictoriaLogs | +| `template_validator.go` | Валидация и автоисправление критичных настроек шаблона | | `grafana.go` | HTTP-клиент для Grafana API (дашборды, папки) | | `template.go` | Загрузка `dashboard_template.json` и `alert_rules_template.json` | | `state.go` | Чтение/запись `state.json`, обнаружение изменений | @@ -122,17 +130,30 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); "version": "3.1.0", "datasource": { "type": "...", "uid": "..." }, "panels": { - "rps": { "query_mode": "bucket_logs", ... }, - "status_codes": { ... }, - "response_time": { ... }, - "traffic_combined": { ... } + "rps": { "query_mode": "rps", "vl_interval": "1m", ... }, + "rps_multi": { "query_mode": "rps_multi", "vl_interval": "1m", ... }, + "status_codes": { "query_mode": "multi_target", "vl_interval": "1m", ... }, + "response_time": { "query_mode": "range_percent", "vl_interval": "1m", ... }, + "traffic_combined": { "query_mode": "multi_sum_expression", "vl_interval": "1m", ... }, + "overview_status_codes": { "query_mode": "bucket_logs", ... }, + "overview_tenant_per_nodes": { "query_mode": "bucket_logs", ... }, + "overview_tenant_per_node": { "query_mode": "bucket_logs", ... }, + "overview_uri_top15": { "query_mode": "bucket_logs", ... }, + "overview_errors_by_container": { "query_mode": "bucket_logs", ... }, + "overview_nginx_vs_angie": { "query_mode": "multi_target", ... }, + "error_by_host": { "query_mode": "bucket_logs", ... }, + "error_by_path": { "query_mode": "bucket_logs", ... }, + "error_by_node": { "query_mode": "bucket_logs", ... } }, "layout": { - "client_panels": [...], + "tenant_panels": [...], + "domain_panels": [...], "overview_panels": [...], - "overview_row_title": "Обзор" + "overview_row_title": "PTAF-Nginx", + "error_panels": [...], + "error_row_title": "Angie" }, - "templating": { ... } + "templating": { "list": [...] } } ``` @@ -140,12 +161,43 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); | Режим | Описание | |---|---| -| `bucket_logs` | Запрос к OpenSearch по одному SID | -| `multi_sid` | Запрос к OpenSearch по всем SID клиента | -| `combined_sids` | Объединённый запрос по SID + алиасам | -| `static` | Статические данные без запроса | +| `rps` | RPS для одного домена (OS: count/60, VL: count/$__interval_ms*1000) | +| `rps_multi` | RPS с разбивкой по доменам клиента | +| `rps_var` | RPS с Grafana-переменной SID | +| `multi_target` | Несколько targets на одной панели (status codes, nginx vs angie) | +| `range_percent` | Процентное распределение по диапазонам (response time) | +| `sum_expression` | Сумма поля + math expression (traffic) | +| `multi_sum_expression` | Несколько sum + math expressions | +| `bucket_logs` | Группировка по полю с terms aggregation (обзорные панели) | +| `static_targets` | Статические targets без подстановки SID | -**Обзорные панели** (`overview_panels`) используют Grafana-переменные (`$SID`, `$tenant`, `$node_name`) и отображаются в свёрнутой строке «Обзор» поверх клиентских строк. +**VL-специфичные поля панелей:** + +| Поле | Описание | +|---|---| +| `vl_interval` | Минимальный интервал агрегации для VL (`"1m"`, `"2m"`) | +| `vl_base_query` | LogsQL запрос вместо Lucene `base_query` для VL | +| `unknown_label` | Метка для записей с пустым полем группировки (напр. `"(unknown)"`) | + +**Layout панелей:** + +Каждый элемент `tenant_panels` и `domain_panels` описывает позицию панели: + +```json +{ + "panel_key": "status_codes", + "title_format": "Status Codes {client}", + "width": 8, + "x_offset": 0, + "condition": { "type": "min_domains", "value": 2 } +} +``` + +Текущая расстановка — три столбца по 8: **Status Codes | RPS | Traffic**. + +**Обзорные панели** (`overview_panels`) отображаются в свёрнутой строке «PTAF-Nginx» и используют Grafana-переменные (`${SID}`, `${tenant}`, `${node_name}`, `${response_status_code}`, `${app_name}`, `${error_server}`). + +**Error панели** (`error_panels`) отображаются в свёрнутой строке «Angie» и показывают данные из `log_type:angie-error-PTAF`. ### alert_rules_template.json @@ -157,7 +209,7 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); { "version": "1.3.0", "defaults": { - "receiver": "Telegram PTAF Grafana", + "receiver": "tg+mail+mattermost PTAF Grafana", "folder": "WAF - PTAF", "group": "PTAF Grafana" }, @@ -165,6 +217,10 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); "relative_time_range_from": 1800, "annotation_summary": "Превышен порог в {rps_limit} RPS в {client_title}" }, + "contact_point": { + "receiver_name": "tg+mail+mattermost PTAF Grafana", + "message_template": "..." + }, "static_alerts": [ ... ] } ``` @@ -173,26 +229,52 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); ## Генерация дашборда -Создаётся **один дашборд** со всеми клиентами. Структура дашборда: +### OpenSearch дашборд + +Создаётся один дашборд со всеми клиентами через `generateSingleDashboard()`. + +### VictoriaLogs дашборд + +Создаётся через `generateVLDashboard()` при наличии `VL_DATASOURCE_UID`. Использует те же панели и layout что и OS дашборд, но targets строятся через `queryModeRegistryVL` с LogsQL запросами. + +**Особенности VL дашборда:** + +- Все запросы используют `log_type:nginx-access-PTAF` (не `log_type:access`) +- `queryType: "statsRange"` для агрегирующих запросов +- `interval: "1m"` из поля `vl_interval` шаблона панели +- `timeShift: "1m"` для обрезки неполной последней минуты +- Math expressions для RPS: `$A / $__interval_ms * 1000` +- Math expressions для response time: `$ref / $total * 100` +- Transformation `renameByRegex` для очистки суффиксов в легенде + +**Структура дашборда:** ``` -┌─────────────────────────────────────────┐ -│ Grafana-переменные: tenant, SID, node │ -├─────────────────────────────────────────┤ -│ ▶ Обзор (свёрнутая строка) │ -│ overview_status_codes │ -│ overview_tenant_per_nodes │ -│ overview_tenant_per_node │ -│ overview_uri_top15 │ -├─────────────────────────────────────────┤ -│ ▼ Клиент A (развёрнутая строка) │ -│ RPS | Status codes | Response time │ -│ Traffic combined │ -│ [панели по доменам если > 1 домена] │ -├─────────────────────────────────────────┤ -│ ▶ Клиент B (свёрнутая строка) │ -│ ... │ -└─────────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +│ Grafana-переменные: tenant, SID, node_name, │ +│ Status Code, App Name, Error App │ +├─────────────────────────────────────────────────┤ +│ ▶ PTAF-Nginx (свёрнутая строка) │ +│ Response_status_code SID │ +│ Request Count by TENANT per nodes │ +│ Request Count by TENANT per node │ +│ URI top 15 │ +│ Response status code by Container │ +│ Nginx vs Angie Status Codes │ +├─────────────────────────────────────────────────┤ +│ ▶ Angie (свёрнутая строка) │ +│ Errors by Apps │ +│ Errors by Upstream Path (app: ${error_server})│ +│ Errors by Node │ +├─────────────────────────────────────────────────┤ +│ ▼ Клиент A (развёрнутая строка) │ +│ Status Codes | RPS | Total Traffic │ +│ [domain1] Status Codes | Response Time | Traffic │ +│ [domain2] ... │ +├─────────────────────────────────────────────────┤ +│ ▶ Клиент B (свёрнутая строка) │ +│ ... │ +└─────────────────────────────────────────────────┘ ``` ### Threshold на графике RPS @@ -210,15 +292,27 @@ VALUES ('Название клиента', 1800, 1200, 20, 10); ### Динамические алерты (per client) -Создаются для каждого клиента у которого задан соответствующий лимит в `client_info`: +Создаются для каждого клиента у которого задан соответствующий лимит в `client_info`. + +**OpenSearch алерты** (`alerts.go`): | Тип | Условие | Структура запроса | |---|---|---| -| RPS | `median(count/60) > rps_limit` | OpenSearch count → math /60 → reduce median → threshold | -| 4xx | `(errors/total)*100 > four_hundred` | 2x OpenSearch count → reduce → math % → threshold | -| 5xx | `(errors/total)*100 > five_hundred` | 2x OpenSearch count → reduce → math % → threshold | +| RPS | `median(count/60) > rps_limit` | OS count → math /60 → reduce median → threshold | +| 4xx | `(errors/total)*100 > four_hundred` | 2x OS count → reduce → math % → threshold | +| 5xx | `(errors/total)*100 > five_hundred` | 2x OS count → reduce → math % → threshold | -UID каждого алерта генерируется детерминированно: `md5(client_title + ":" + type)[:8]`. При повторном запуске алерт обновляется (`PUT`), а не создаётся заново. +**VictoriaLogs алерты** (`alerts_vl.go`): + +| Тип | Условие | Структура запроса | +|---|---|---| +| RPS | `median(count/60) > rps_limit` | VL statsRange (1m) → math /60 → reduce median → threshold | +| 4xx | `(errors/total)*100 > four_hundred` | 2x VL statsRange (1m) → reduce sum → math % → threshold | +| 5xx | `(errors/total)*100 > five_hundred` | 2x VL statsRange (1m) → reduce sum → math % → threshold | + +VL алерты имеют `noDataState: "NoData"` — при отсутствии данных алерт не срабатывает. + +UID каждого алерта генерируется детерминированно: `md5(client_title + "_vl:" + type)[:8]`. При повторном запуске алерт обновляется (`PUT`), а не создаётся заново. ### Статические алерты (всегда присутствуют) @@ -243,6 +337,23 @@ UID каждого алерта генерируется детерминиро [static: RAM] ... ``` +### Подавление DatasourceNoData алертов + +VL алерты при отсутствии данных переходят в состояние `NoData` (не `Alerting`). Для подавления уведомлений об этом состоянии в Grafana создан Silence с матчером `alertname = DatasourceNoData`. + +--- + +## Валидатор шаблона + +`template_validator.go` запускается автоматически после загрузки шаблона и проверяет/исправляет: + +- `vl_interval: "1m"` для панелей: rps, rps_multi, status_codes, response_time, traffic_combined, traffic_combined_total +- `spanNulls: true` для панели status_codes +- `vl_base_query` с `TENANT:in(${tenant:csv})` для `overview_tenant_per_nodes` +- `vl_base_query` с `node_name:in(${node_name:csv})` для `overview_tenant_per_node` +- Layout tenant_panels в три столбца по 8 (status_codes + rps/rps_multi + traffic_combined_total) +- Layout domain_panels в три столбца по 8 (status_codes + response_time + traffic_combined) + --- ## State-файл @@ -268,14 +379,16 @@ UID каждого алерта генерируется детерминиро **Логика запуска:** ``` -изменился clients_hash → пересоздать дашборд -изменился template_commit → пересоздать дашборд -изменился template_version → пересоздать дашборд -изменился alerts_hash → переотправить алерты (без пересоздания дашборда) +изменился clients_hash → пересоздать дашборд +изменился template_commit → пересоздать дашборд +изменился template_version → пересоздать дашборд +изменился alerts_hash → переотправить алерты (без пересоздания дашборда) изменился alert_template_hash → переотправить алерты (без пересоздания дашборда) -ничего не изменилось → выход без действий (если не указан -force) +ничего не изменилось → выход без действий (если не указан -force) ``` +Хэш алертов сохраняется после успешного выполнения независимо от того какие системы алертов включены (OS/VL). + --- ## Конфигурация @@ -310,8 +423,19 @@ GRAFANA_API_KEY=glsa-xxxxxxxxxxxxxxxxxxxx # Git (Gitea) GIT_TOKEN=your-gitea-token -# Алерты +# Алерты (OpenSearch) ALERTS_DATASOURCE_UID=af84zsvlp9blsa + +# VictoriaLogs +VL_DATASOURCE_UID=efewdonokxybkf +VL_DASHBOARD_TITLE=PT AF Requests (VictoriaLogs) +VL_GRAFANA_FOLDER=WAF - PTAF + +# Управление генерацией +DASHBOARD_OS_ENABLED=false +DASHBOARD_VL_ENABLED=true +ALERTS_OS_ENABLED=false +ALERTS_VL_ENABLED=true ``` --- @@ -330,9 +454,13 @@ ALERTS_DATASOURCE_UID=af84zsvlp9blsa | `-grafana-folder` | `GRAFANA_FOLDER` | `WAF - Auto Generated` | Папка для дашбордов | | `-dashboard-title` | `DASHBOARD_TITLE` | `PT AF Nodes` | Название дашборда | | `-alerts-folder` | `ALERTS_FOLDER` | `WAF - PTAF` | Папка для алертов | -| `-alerts-receiver` | `ALERTS_RECEIVER` | `Telegram PTAF Grafana` | Получатель уведомлений | +| `-alerts-receiver` | `ALERTS_RECEIVER` | `tg+mail+mattermost PTAF Grafana` | Получатель уведомлений | | `-alerts-group` | `ALERTS_GROUP` | `PTAF Grafana` | Группа алертов | -| `-alerts-datasource-uid` | `ALERTS_DATASOURCE_UID` | `af84zsvlp9blsa` | UID datasource OpenSearch | +| `-alerts-datasource-uid` | `ALERTS_DATASOURCE_UID` | `af84zsvlp9blsa` | UID datasource OpenSearch для алертов | +| `-vl-datasource-uid` | `VL_DATASOURCE_UID` | — | UID VictoriaLogs datasource (пустая = не генерировать VL) | +| `-vl-dashboard-title` | `VL_DASHBOARD_TITLE` | `PT AF Requests (VictoriaLogs)` | Название VL дашборда | +| `-vl-grafana-folder` | `VL_GRAFANA_FOLDER` | — | Папка для VL дашборда (пустая = как у OS) | +| `-vl-dashboard-uid` | `VL_DASHBOARD_UID` | `pt-af-requests-auto-vl` | UID VL дашборда в Grafana | | `-git-token` | `GIT_TOKEN` | — | Gitea токен | | `-templates-repo` | — | `svc-git.cirex.ru/...` | URL Git-репозитория шаблонов | | `-templates-branch` | — | `master` | Ветка шаблонов | @@ -341,6 +469,10 @@ ALERTS_DATASOURCE_UID=af84zsvlp9blsa | `-state-file` | — | `/var/lib/grafana_gen/state.json` | Путь к state-файлу | | `-dry-run` | — | `false` | Не отправлять в Grafana, только логировать | | `-force` | — | `false` | Пересоздать всё даже без изменений | +| — | `DASHBOARD_OS_ENABLED` | `true` | Генерировать OS дашборд | +| — | `DASHBOARD_VL_ENABLED` | `true` | Генерировать VL дашборд | +| — | `ALERTS_OS_ENABLED` | `true` | Генерировать OS алерты | +| — | `ALERTS_VL_ENABLED` | `true` | Генерировать VL алерты | ### Примеры запуска @@ -357,11 +489,15 @@ ALERTS_DATASOURCE_UID=af84zsvlp9blsa # Без обновления шаблонов из Git ./grafana_gen -skip-git-pull +# Только VL дашборд и VL алерты +DASHBOARD_OS_ENABLED=false ALERTS_OS_ENABLED=false ./grafana_gen + # Явное указание всех параметров ./grafana_gen \ -db-host=10.100.10.8 \ -db-user=reader \ -db-password=secret \ -grafana-url=https://grafana.example.com \ - -grafana-api-key=glsa-xxx -``` \ No newline at end of file + -grafana-api-key=glsa-xxx \ + -vl-datasource-uid=efewdonokxybkf +``` diff --git a/README.md b/README.md index eca3697..9850128 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # grafana_gen -Утилита автоматической генерации Grafana-дашбордов и правил алертинга для клиентов WAF. +Утилита автоматической генерации Grafana-дашбордов и правил алертинга для клиентов WAF/PTAF. ## Что делает - Читает список клиентов и их SID из PostgreSQL (`sp_info` + `manual_info`) -- Генерирует единый дашборд с панелями на каждого клиента -- Создаёт правила алертинга: RPS, 4xx/5xx ошибки (пороги из БД), CPU, RAM, диск, контейнеры, Angie +- Генерирует дашборд для **OpenSearch** и/или **VictoriaLogs** datasource (независимо) +- Создаёт правила алертинга для OS и/или VL: RPS, 4xx/5xx ошибки (пороги из БД), CPU, RAM, диск, контейнеры, Angie - Обновляет шаблоны дашборда и алертов из Git-репозитория при каждом запуске - Пропускает запуск если ничего не изменилось (отслеживает хэши) +- Валидирует и автоисправляет критичные настройки шаблона при каждом запуске ## Быстрый старт @@ -47,6 +48,16 @@ DB_PASSWORD=secret GRAFANA_URL=https://grafana.example.com GRAFANA_API_KEY=glsa-xxxxxxxxxxxxxxxxxxxx GIT_TOKEN=your-gitea-token + +# VictoriaLogs (опционально) +VL_DATASOURCE_UID=efewdonokxybkf +VL_DASHBOARD_TITLE=PT AF Requests (VictoriaLogs) + +# Управление генерацией +DASHBOARD_OS_ENABLED=true +DASHBOARD_VL_ENABLED=true +ALERTS_OS_ENABLED=true +ALERTS_VL_ENABLED=true ``` ## Полезные флаги @@ -57,6 +68,31 @@ GIT_TOKEN=your-gitea-token -skip-git-pull # Не обновлять шаблоны из Git ``` +## Управление генерацией через переменные окружения + +Можно независимо включать и отключать генерацию дашбордов и алертов: + +| Переменная | По умолчанию | Описание | +|---|---|---| +| `DASHBOARD_OS_ENABLED` | `true` | Генерировать дашборд для OpenSearch | +| `DASHBOARD_VL_ENABLED` | `true` | Генерировать дашборд для VictoriaLogs | +| `ALERTS_OS_ENABLED` | `true` | Генерировать алерты для OpenSearch | +| `ALERTS_VL_ENABLED` | `true` | Генерировать алерты для VictoriaLogs | + +Пример — только VL дашборд и VL алерты: + +```env +DASHBOARD_OS_ENABLED=false +ALERTS_OS_ENABLED=false +``` + +## VictoriaLogs дашборд + +При наличии `VL_DATASOURCE_UID` генерируется второй дашборд с теми же панелями но на базе VictoriaLogs datasource и LogsQL запросов. Дашборд содержит дополнительные секции: + +- **PTAF-Nginx** (обзорные панели) — статус коды, запросы по нодам/тенантам, топ URI, сравнение nginx vs angie +- **Angie** — панели ошибок из `log_type:angie-error-PTAF` + ## Шаблоны Дашборд и алерты строятся по JSON-шаблонам из Git-репозитория: @@ -66,6 +102,17 @@ GIT_TOKEN=your-gitea-token Изменение любого шаблона автоматически триггерит пересоздание при следующем запуске. +### Валидатор шаблона + +При каждом запуске `template_validator.go` автоматически проверяет и исправляет критичные настройки шаблона: + +- `vl_interval: "1m"` для панелей RPS, status_codes, response_time, traffic +- `spanNulls: true` для панели status_codes +- `vl_base_query` с переменными тенанта/ноды для обзорных панелей +- Layout в три столбца (8+8+8) для tenant и domain панелей + +Это защищает от потери настроек при обновлении шаблона из Git. + ## Подробнее -См. [ARCHITECTURE.md](ARCHITECTURE.md). \ No newline at end of file +См. [ARCHITECTURE.md](ARCHITECTURE.md).