15 KiB
grafana_gen — архитектура и устройство утилиты
Утилита автоматически генерирует Grafana-дашборды и правила алертинга для клиентов WAF на основе данных из PostgreSQL и шаблонов из Git-репозитория.
Обзор потока данных
PostgreSQL
sp_info + manual_info (UNION)
apps_settings
client_info
│
▼
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
Структура файлов
| Файл | Назначение |
|---|---|
main.go |
Точка входа, оркестрация всего потока |
config.go |
Константы, структуры Config, ClientData, DomainInfo, DashboardTemplate |
database.go |
Подключение к PostgreSQL, fetchClientsData() |
dashboard.go |
Генерация JSON дашборда из шаблона и данных клиентов |
panels.go |
Построение отдельных панелей, реестр query-режимов, applyRPSThreshold() |
alerts.go |
Генерация и отправка правил алертинга, параллельный upsert |
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": "bucket_logs", ... },
"status_codes": { ... },
"response_time": { ... },
"traffic_combined": { ... }
},
"layout": {
"client_panels": [...],
"overview_panels": [...],
"overview_row_title": "Обзор"
},
"templating": { ... }
}
Query-режимы панелей (query_mode):
| Режим | Описание |
|---|---|
bucket_logs |
Запрос к OpenSearch по одному SID |
multi_sid |
Запрос к OpenSearch по всем SID клиента |
combined_sids |
Объединённый запрос по SID + алиасам |
static |
Статические данные без запроса |
Обзорные панели (overview_panels) используют Grafana-переменные ($SID, $tenant, $node_name) и отображаются в свёрнутой строке «Обзор» поверх клиентских строк.
alert_rules_template.json
Описывает шаблон для алертов.
Структура:
{
"version": "1.3.0",
"defaults": {
"receiver": "Telegram PTAF Grafana",
"folder": "WAF - PTAF",
"group": "PTAF Grafana"
},
"rps_alert": {
"relative_time_range_from": 1800,
"annotation_summary": "Превышен порог в {rps_limit} RPS в {client_title}"
},
"static_alerts": [ ... ]
}
Генерация дашборда
Создаётся один дашборд со всеми клиентами. Структура дашборда:
┌─────────────────────────────────────────┐
│ 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 (свёрнутая строка) │
│ ... │
└─────────────────────────────────────────┘
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:
| Тип | Условие | Структура запроса |
|---|---|---|
| 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 |
UID каждого алерта генерируется детерминированно: md5(client_title + ":" + 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] ...
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)
Конфигурация
Приоритет разрешения параметров (от высшего к низшему):
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
# Алерты
ALERTS_DATASOURCE_UID=af84zsvlp9blsa
Флаги запуска
| Флаг | Переменная окружения | По умолчанию | Описание |
|---|---|---|---|
-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 |
Telegram PTAF Grafana |
Получатель уведомлений |
-alerts-group |
ALERTS_GROUP |
PTAF Grafana |
Группа алертов |
-alerts-datasource-uid |
ALERTS_DATASOURCE_UID |
af84zsvlp9blsa |
UID datasource OpenSearch |
-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 |
Пересоздать всё даже без изменений |
Примеры запуска
# Обычный запуск
./grafana_gen
# Тестовый прогон без отправки в Grafana
./grafana_gen -dry-run
# Принудительное пересоздание всего
./grafana_gen -force
# Без обновления шаблонов из Git
./grafana_gen -skip-git-pull
# Явное указание всех параметров
./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