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

15 KiB
Raw Blame History

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 файлу (перебираются по порядку)

  1. $GRAFANA_GEN_ENV_FILE (переменная окружения)
  2. /etc/grafana_gen/grafana_gen.env
  3. ./grafana_gen.env
  4. ./.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