grafana_gen/ARCHITECTURE.md
2026-02-25 15:29:05 +03:00

367 lines
No EOL
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`:
```sql
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 = алерт не создавать)
### Пример добавления нового клиента
```sql
-- 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), панели.
**Ключевые секции:**
```json
{
"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
Описывает шаблон для алертов.
**Структура:**
```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`
Хранит хэши для обнаружения изменений между запусками:
```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 файлу (перебираются по порядку)
1. `$GRAFANA_GEN_ENV_FILE` (переменная окружения)
2. `/etc/grafana_gen/grafana_gen.env`
3. `./grafana_gen.env`
4. `./.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` | Пересоздать всё даже без изменений |
### Примеры запуска
```bash
# Обычный запуск
./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
```