24 KiB
grafana_gen — архитектура и устройство утилиты
Утилита автоматически генерирует Grafana-дашборды и правила алертинга для клиентов WAF на основе данных из PostgreSQL и шаблонов из Git-репозитория.
Обзор потока данных
PostgreSQL
sp_info + manual_info (UNION)
apps_settings
client_info
│
▼
fetchClientsData()
map[clientTitle]ClientData
│
├──────────────────────────────────────────────────────┐
│ │
▼ ▼
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.
Структура файлов
| Файл | Назначение |
|---|---|
main.go |
Точка входа, оркестрация всего потока |
config.go |
Константы, структуры Config, ClientData, DomainInfo, DashboardTemplate |
database.go |
Подключение к PostgreSQL, fetchClientsData() |
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, обнаружение изменений |
git.go |
git clone / git pull шаблонов из репозитория |
env.go |
Загрузка .env файла |
dashboard_template.json |
JSON-шаблон дашборда (Git-репозиторий) |
alert_rules_template.json |
JSON-шаблон алертов (Git-репозиторий) |
База данных
Источник данных
Данные всегда берутся из обеих таблиц одновременно через UNION:
SELECT ... FROM (
SELECT sid, domain_name, aliases FROM sp_info WHERE sid IS NOT NULL
UNION
SELECT sid, domain_name, aliases FROM manual_info WHERE sid IS NOT NULL
) s
LEFT JOIN apps_settings a ON s.sid = a.l7resourceid
LEFT JOIN client_info ci ON a.client_title = ci.client_title
Дубликаты по SID схлопываются автоматически (UNION без ALL).
Таблицы
sp_info / manual_info — список ресурсов:
sid— идентификатор ресурсаdomain_name— основной доменaliases— JSON-массив дополнительных доменов
apps_settings — маппинг SID → клиент:
l7resourceid— SID ресурсаclient_title— название клиента (имя тенанта)
client_info — настройки мониторинга клиента:
client_title— название клиентаrps_limit— порог RPS для алерта (NULL = алерт не создавать)rps_commercial_limit— коммерческий лимит RPS для синей линии на графике (NULL = 100)four_hundred— порог 4xx ошибок в % (NULL = алерт не создавать)five_hundred— порог 5xx ошибок в % (NULL = алерт не создавать)
Пример добавления нового клиента
-- 1. Ресурс появится автоматически из sp_info
-- 2. Привязать к клиенту
INSERT INTO apps_settings (l7resourceid, client_title)
VALUES ('SID12345', 'Название клиента');
-- 3. Задать лимиты для алертов (опционально)
INSERT INTO client_info (client_title, rps_limit, rps_commercial_limit, four_hundred, five_hundred)
VALUES ('Название клиента', 1800, 1200, 20, 10);
Шаблоны
Шаблоны хранятся в отдельном Git-репозитории и обновляются при каждом запуске через git pull. Изменение шаблона автоматически приводит к пересозданию дашборда и/или алертов.
dashboard_template.json
Описывает структуру дашборда: метаданные, Grafana-переменные, строки (rows), панели.
Ключевые секции:
{
"version": "3.1.0",
"datasource": { "type": "...", "uid": "..." },
"panels": {
"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": {
"tenant_panels": [...],
"domain_panels": [...],
"overview_panels": [...],
"overview_row_title": "PTAF-Nginx",
"error_panels": [...],
"error_row_title": "Angie"
},
"templating": { "list": [...] }
}
Query-режимы панелей (query_mode):
| Режим | Описание |
|---|---|
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 |
VL-специфичные поля панелей:
| Поле | Описание |
|---|---|
vl_interval |
Минимальный интервал агрегации для VL ("1m", "2m") |
vl_base_query |
LogsQL запрос вместо Lucene base_query для VL |
unknown_label |
Метка для записей с пустым полем группировки (напр. "(unknown)") |
Layout панелей:
Каждый элемент tenant_panels и domain_panels описывает позицию панели:
{
"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
Описывает шаблон для алертов.
Структура:
{
"version": "1.3.0",
"defaults": {
"receiver": "tg+mail+mattermost PTAF Grafana",
"folder": "WAF - PTAF",
"group": "PTAF Grafana"
},
"rps_alert": {
"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": [ ... ]
}
Генерация дашборда
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_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
На панель RPS автоматически накладываются цветные линии из client_info:
| Линия | Значение | Источник |
|---|---|---|
| Синяя | rps_commercial_limit |
client_info.rps_commercial_limit (NULL → 100) |
| Красная | rps_limit |
client_info.rps_limit (0 → линия не рисуется) |
Алерты
Динамические алерты (per client)
Создаются для каждого клиента у которого задан соответствующий лимит в client_info.
OpenSearch алерты (alerts.go):
| Тип | Условие | Структура запроса |
|---|---|---|
| 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 |
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), а не создаётся заново.
Статические алерты (всегда присутствуют)
Описаны в alert_rules_template.json в секции static_alerts, не зависят от БД:
| Алерт | Условие | for |
|---|---|---|
| Use in / | диск / > 75% |
3m |
| Use in /var/log | диск /var/log > 75% |
5m |
| CPU Busy | CPU > 75% | 3m |
| RAM Busy | RAM > 75% | 3m |
| Состояние контейнеров | docker_container_status == 0 |
2m |
| Упала Angie | angie.service не active |
1m |
Параллельная отправка
Все алерты (динамические + статические) отправляются параллельно через горутины с ограничением 5 одновременных запросов к Grafana API:
[RPS client1] [RPS client2] [4xx client1] [5xx client2] [static: CPU] ← 5 горутин
↓ освободился слот
[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_totalspanNulls: trueдля панели status_codesvl_base_queryсTENANT:in(${tenant:csv})дляoverview_tenant_per_nodesvl_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-файл
Путь по умолчанию: /var/lib/grafana_gen/state.json
Хранит хэши для обнаружения изменений между запусками:
{
"last_run": "2026-02-25T10:00:00Z",
"template_commit": "a1b2c3d4...",
"template_version": "3.1.0",
"clients_hash": "sha256...",
"client_count": 42,
"domain_count": 87,
"sids": ["SID001", "SID002", "..."],
"alerts_hash": "sha256...",
"alert_template_hash": "sha256..."
}
Логика запуска:
изменился clients_hash → пересоздать дашборд
изменился template_commit → пересоздать дашборд
изменился template_version → пересоздать дашборд
изменился alerts_hash → переотправить алерты (без пересоздания дашборда)
изменился alert_template_hash → переотправить алерты (без пересоздания дашборда)
ничего не изменилось → выход без действий (если не указан -force)
Хэш алертов сохраняется после успешного выполнения независимо от того какие системы алертов включены (OS/VL).
Конфигурация
Приоритет разрешения параметров (от высшего к низшему):
1. Флаг командной строки (-grafana-url=...)
2. Переменная окружения (GRAFANA_URL=...)
3. .env файл (/etc/grafana_gen/grafana_gen.env)
4. Константа в config.go (DefaultGrafanaURL)
Пути к .env файлу (перебираются по порядку)
$GRAFANA_GEN_ENV_FILE(переменная окружения)/etc/grafana_gen/grafana_gen.env./grafana_gen.env./.env
Пример .env файла
# База данных
DB_USER=grafana_reader
DB_PASSWORD=secret
# Grafana
GRAFANA_URL=https://grafana.example.com
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
Флаги запуска
| Флаг | Переменная окружения | По умолчанию | Описание |
|---|---|---|---|
-db-host |
— | 10.100.10.8 |
PostgreSQL хост |
-db-port |
— | 5432 |
PostgreSQL порт |
-db-user |
DB_USER |
— | PostgreSQL пользователь |
-db-password |
DB_PASSWORD |
— | PostgreSQL пароль |
-db-name |
— | waf_info |
PostgreSQL база данных |
-grafana-url |
GRAFANA_URL |
— | URL Grafana |
-grafana-api-key |
GRAFANA_API_KEY |
— | Grafana API ключ |
-grafana-folder |
GRAFANA_FOLDER |
WAF - Auto Generated |
Папка для дашбордов |
-dashboard-title |
DASHBOARD_TITLE |
PT AF Nodes |
Название дашборда |
-alerts-folder |
ALERTS_FOLDER |
WAF - PTAF |
Папка для алертов |
-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 для алертов |
-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 |
Ветка шаблонов |
-templates-path |
— | /etc/grafana_gen/templates |
Локальный путь шаблонов |
-skip-git-pull |
— | false |
Не обновлять шаблоны из Git |
-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 алерты |
Примеры запуска
# Обычный запуск
./grafana_gen
# Тестовый прогон без отправки в Grafana
./grafana_gen -dry-run
# Принудительное пересоздание всего
./grafana_gen -force
# Без обновления шаблонов из 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 \
-vl-datasource-uid=efewdonokxybkf