grafana_gen/ARCHITECTURE.md
2026-04-16 15:21:43 +03:00

24 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()      (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_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-файл

Путь по умолчанию: /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 файлу (перебираются по порядку)

  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

# Алерты (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