Refine EvoBGP architecture plan with updated database schema details, improved descriptions for components, and added structured content sections. Adjusted todos for clarity on tasks related to PostgreSQL and SQLite integration.

This commit is contained in:
Denozordec
2026-04-03 23:20:29 +07:00
parent 31ff5bbd2b
commit 431ef3e770
@@ -1,202 +1,209 @@
--- ---
name: EvoBGP архитектура name: EvoBGP архитектура
overview: Control-plane на мастере; BIRD на мастере и опционально на EvoBGP-нодах, стягивающих подписанный бандл (префиксы+пиры+фильтры) для одинаковых правил. Клиенты — внешние BGP-пиры. MySQL/SQLite, Go, REST, ревизии. overview: 'Оба профиля по умолчанию на PostgreSQL (один движок, одни миграции). reference — микросервисы + PG + брокер; microVPS — evobgp-all + контейнер PG (жёсткий тюнинг). SQLite — опция только для одного контейнера без PG.'
todos: todos:
- id: schema-mysql - id: schema-db
content: "Схема БД (MySQL prod + SQLite edge): модули, CDN, DoH, community, пиры, ревизии, jobs" content: "Схема БД: PostgreSQL (основной); опционально SQLite (microVPS single-container); модули, ревизии, jobs"
status: pending status: pending
- id: bird-generator - id: bird-generator
content: Определить формат bird.conf фрагментов, фильтры и точки reload/configure content: Формат bird.conf, include-фрагменты, фильтры, birdc configure
status: pending status: pending
- id: rest-jobs - id: rest-jobs
content: Спецификация REST (refresh модуля, apply, preview, rollback) и async jobs content: REST (refresh, apply, preview, rollback, bundle API) и async jobs
status: pending status: pending
- id: node-agent - id: node-agent
content: evobgp-agent на мастере; опционально evobgp-node (pull бандла + BIRD на реплике) content: evobgp-agent на мастере; evobgp-node на реплике (отдельная ВМ)
status: pending status: pending
- id: observability - id: observability
content: Метрики, алерты на дрейф префиксов, статус пиров content: Метрики, алерты префиксов и BGP-сессий
status: pending status: pending
- id: docker-ms - id: docker-ms
content: Dockerfile, compose profiles full vs edge_1g, сети/volumes, healthchecks content: Compose profiles reference + microVPS, лимиты, логи, prune
status: pending status: pending
- id: go-modules - id: go-modules
content: Структура Go-модулей, общие пакеты (db, models, bird templating) content: Monorepo internal/*, cmd/evobgp-all и cmd/* для reference
status: pending status: pending
- id: replica-bundle - id: replica-bundle
content: Формат бандла ревизии, подпись, API выдачи, evobgp-node pull и локальный BIRD content: Подписанный бандл ревизии, API, evobgp-node
status: pending status: pending
isProject: false isProject: false
--- ---
# EvoBGP: быстрый анонс префиксов через BIRD + MySQL + REST # EvoBGP — архитектурный план
Репозиторий сейчас без кода — план описывает целевую архитектуру «с нуля». **Backend — Go.** Развёртывание control-plane — **контейнеры Docker**; оркестрация: `docker compose` для разработки, в production — Kubernetes / Nomad / Swarm по выбору. Control-plane на **Go**, анонс префиксов через **BIRD**, политика и история в **SQL-БД**, управление по **REST**. Репозиторий кода пока пустой — документ задаёт целевую архитектуру.
## Целевая картина (логическая) ---
- **BIRD** на стороне **вашего сервера** — процесс, который **анонсирует** префиксы (и ведёт сессии с соседями). **Клиенты** — это **удалённые BGP-пиры** (до ~20 и более), которые **подключаются к этому серверу** и **получают** объявления; у них **свой** стек (не evobgp, не ваш Docker). ## Содержание
- **MySQL/SQLite** — политика: какие префиксы, какие community, **какие пиры** в `protocol bgp` и кому что экспортировать.
- **Быстрота:** очередь задач, идempotent-воркеры, `birdc configure` после подмены include на **хосте BIRD**.
**Терминология:** **клиенты** — внешние BGP-пиры (их роутеры), не контейнеры EvoBGP. **Мастер** — control-plane + первичный BIRD (или только control-plane, если BIRD только на границе). **Опционально** — одна или несколько **EvoBGP-нод**: только стягивание готового бандла с мастера и локальный BIRD с **теми же** префиксами/фильтрами/правилами пиров (см. раздел «Реплика-нода»). 1. [Два эталонных профиля](#1-два-эталонных-профиля-развёртывания)
2. [Логическая модель и термины](#2-логическая-модель-общая-для-обоих-профилей)
3. [Сервисы и контейнеры](#3-сервисы-сравнение-профилей)
4. [База данных, ETL, ER-схема](#4-база-данных-etl-er-схема)
5. [Модули префиксов и FQDN](#5-модули-префиксов-as-cdn-домены)
6. [REST API](#6-rest-api)
7. [Генерация BIRD и ревизии](#7-генерация-bird-и-ревизии)
8. [Эксплуатация и масштаб пиров](#8-эксплуатация-и-масштаб)
9. [Реплика evobgp-node](#9-реплика-evobgp-node)
10. [Выбор СУБД](#10-выбор-субд)
11. [Профиль microVPS — детализация](#11-профиль-microvps-детализация)
12. [Риски и этапы](#12-риски-и-этапы-внедрения)
### Диаграмма: микросервисы и Docker (control-plane + BGP-сервер) ---
## 1. Два эталонных профиля развёртывания
Один и тот же **код** в дереве `internal/` (все подпакеты), две **упаковки** в Docker Compose.
| Критерий | **reference** (эталон) | **microVPS** |
| ---------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Назначение** | Production control-plane без жёсткого лимита RAM; горизонтальное масштабирование воркеров | Один хост: маленькая VPS / вложенная ВМ |
| **CPU** | 2+ vCPU (рекомендуется) | **1 vCPU** |
| **RAM** | 4+ ГиБ (ориентир) | **~1 ГиБ** |
| **Диск под Docker + данные** | по объёму проекта | **710 ГиБ** (бюджет; не весь диск ОС) |
| **ОС** | Linux (в т.ч. Ubuntu 22.04/24.04) | **Ubuntu 24.04 LTS** (референс) |
| **Контейнеры Go** | 5 образов: `api`, `scheduler`, `ingest`, `render`, `deploy` | **1** образ: `evobgp-all` |
| **БД** | **PostgreSQL** (отдельный сервис или managed) | **PostgreSQL** в контейнере (**жёсткий** тюнинг под 1 ГиБ) |
| **Очередь** | **NATS JetStream** или **Redis Streams** (на выбор) | **Нет брокера**`job_audit` в **PostgreSQL** + `SKIP LOCKED` / малый пул коннектов |
| **Reverse proxy** | Traefik / Nginx (опционально) | **Нет** — прямой порт API |
| **Object storage** | MinIO / S3 опционально для артефактов | Только **локальный volume** или небольшие BLOB в БД |
| **BIRD** | Хост или privileged-контейнер + `evobgp-agent` | **Предпочтительно на хосте**; иначе +1 контейнер BIRD |
| **Контейнеры (итого)** | 5 Go + PG + брокер (+ опц. proxy) | **2:** `evobgp-all` + **postgres** (+ опц. BIRD-контейнер); без 5×Go и без брокера |
```mermaid
flowchart LR
subgraph ref [reference]
R1[5x Go]
R2[(PostgreSQL)]
R3[(Broker)]
R1 --> R2
R1 --> R3
end
subgraph micro [microVPS]
M1[evobgp-all]
M2[(PostgreSQL)]
M1 --> M2
end
```
> **Правило:** логика домена **одинакова**; **один движок БД — PostgreSQL** (одинаковые миграции и SQL). Отличаются число процессов Go, наличие брокера и **настройки** PG.
> **Опция `microVPS_sqlite`:** один контейнер `evobgp-all` без PG — только если критичен абсолютный минимум контейнеров; иначе **не рекомендуется** как основной путь.
---
## 2. Логическая модель (общая для обоих профилей)
- **BGP-сервер (ваш)** — процесс **BIRD**, который **анонсирует** префиксы и держит сессии.
- **Клиенты** — **внешние** роутеры (BGP-пиры), подключающиеся **к вам** и **получающие** маршруты. Это не контейнеры EvoBGP.
- **Мастер** — control-plane + первичный BIRD (и `evobgp-agent` рядом с ним).
- **EvoBGP-нода (опционально)** — отдельная площадка: **только** pull подписанного **бандла** с мастера + локальный BIRD; **своей** полной БД и ingest **нет**.
**Быстрый путь данных:** БД → ingest (CDN / DoH / AS) → render (префиксы + community + ревизия) → deploy → файлы BIRD → `birdc configure` → клиентские сессии.
---
## 3. Сервисы (сравнение профилей)
| Сервис | Назначение | reference | microVPS |
| -------------------- | ---------------------------------------------- | ----------------------- | ------------------------ |
| **evobgp-api** | REST, CRUD, задачи, `/jobs` | отдельный контейнер | goroutine в `evobgp-all` |
| **evobgp-scheduler** | Интервалы модулей/CDN → события | отдельный контейнер | goroutine в `evobgp-all` |
| **evobgp-ingest** | CDN, DoH, материализация в БД | отдельный контейнер | goroutine в `evobgp-all` |
| **evobgp-render** | Итоговые префиксы, ревизия, текст BIRD | отдельный контейнер | goroutine в `evobgp-all` |
| **evobgp-deploy** | Доставка на мастерский BIRD, публикация бандла | отдельный контейнер | goroutine в `evobgp-all` |
| **evobgp-agent** | На хосте мастерского BIRD: файлы, `birdc` | бинарь/контейнер у BIRD | то же |
| **evobgp-node** | Реплика: pull бандла, локальный BIRD | отдельный хост | **не на microVPS** |
**Инфраструктура reference:** PostgreSQL, NATS или Redis, опционально Traefik, MinIO.
### Зависимости (профиль reference)
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph edge [Периметр] subgraph cp [Control plane]
LB[Traefik или Nginx] API[evobgp-api]
SCH[scheduler]
ING[ingest]
REN[render]
DEP[deploy]
end end
subgraph docker [Docker host control-plane] DB[(PostgreSQL)]
API[evobgp-api Go] MQ[(Broker)]
SCH[evobgp-scheduler Go]
ING[evobgp-ingest Go]
REN[evobgp-render Go]
DEP[evobgp-deploy Go]
MQ[(NATS или Redis Streams)]
DB[(MySQL)]
end
subgraph speaker [Хост BGP сервера]
AG1[evobgp-agent опционально]
BR1[BIRD анонсирует префиксы]
AG1 --> BR1
end
subgraph clients [Клиенты BGP пиры]
C1[BGP клиент 1]
C2[BGP клиент N]
end
subgraph replicaOpt [Опционально реплика]
NODE[evobgp-node]
BR2[BIRD реплика]
NODE --> BR2
CR[Клиенты к реплике]
BR2 <-->|BGP| CR
end
API -->|bundle mTLS| NODE
LB --> API
API --> DB API --> DB
API --> MQ API --> MQ
SCH --> DB SCH --> DB
SCH --> MQ SCH --> MQ
ING --> MQ
ING --> DB ING --> DB
REN --> MQ ING --> MQ
REN --> DB REN --> DB
DEP --> MQ REN --> MQ
DEP --> DB DEP --> DB
DEP -->|конфиг на хост BIRD| AG1 DEP --> MQ
BR1 <-->|BGP сессии| C1 DEP --> AG[evobgp-agent]
BR1 <-->|BGP сессии| C2 AG --> BIRD[BIRD мастер]
``` ```
Назначение сервисов (можно объединять на раннем MVP, границы — контракты между пакетами):
| Сервис | Роль |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **evobgp-api** | REST, аутентификация, CRUD сущностей, постановка задач (`refresh`, `apply`, `rollback`), `GET /jobs`. |
| **evobgp-scheduler** | Читает интервалы модулей и CDN-строк из MySQL, публикует события «пора обновить модуль/источник» в очередь. |
| **evobgp-ingest** | Fetch CDN, DoH-резолв доменов, загрузка AS/префиксов; пишет материализованные строки и сырые метаданные в MySQL. |
| **evobgp-render** | Собирает итоговый набор префиксов + community, создаёт ревизию, генерирует артефакты BIRD (текст конфигов). |
| **evobgp-deploy** | Доставка конфига **на мастерский** BIRD через `evobgp-agent`; **публикация** подписанного **бандла** для `evobgp-node` (HTTP или объектное хранилище). |
| **evobgp-agent** | На **мастерском** хосте BIRD: применить файлы от `evobgp-deploy`, `birdc`, отчитаться о ревизии. |
| **evobgp-node** | **Опционально** на удалённой площадке: периодически **скачивает** с мастера **подписанный бандл** ревизии (все include префиксов, фильтры, `peers.conf`), проверяет подпись/хэши, раскладывает на диск, вызывает локальный `birdc`. **Без** своей MySQL и без ingest — только **consumer** артефакта. |
**Инфраструктурные контейнеры:** **MySQL**, **брокер очередей** (NATS JetStream или Redis), опционально **Valkey/Redis** для кэша и rate-limit по модулям.
--- ---
## 1. MySQL: сущности и версионирование ## 4. База данных, ETL, ER-схема
Рекомендуемые группы таблиц: ### 4.1. Группы сущностей
| Область | Назначение | | Область | Назначение |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------------------------------------------------ | ----------------------------------------------------------------------- |
| **Модули** | Тип: `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`; включён/выключен; приоритет; ссылки на расписание и (для доменов) DoH-профиль. | | **module** | Тип `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`, расписание, DoH, приоритет |
| **Расписания обновления** | Базовый интервал на **модуль** (`refresh_interval_sec`, cron или interval); см. ниже про переопределение на CDN. | | **module_cdn_source** | URL/inline CDN; свой `refresh_interval` опционально |
| **Источники CDN внутри модуля** | Для типа `CDN_CIDRS`: несколько записей «URL/статический список» на модуль; у **каждой** записи свой опциональный `refresh_interval_sec` (если NULL — брать интервал модуля). | | **doh_profile** | URL DoH, таймауты, секреты по ссылке |
| **Профили DoH** | URL HTTPS DoH (`https://…/dns-query`), опционально имя для SNI, таймауты, доверие к сертификату (политика); привязка к модулям `DOMAINS` или глобальный default. | | **module_domain_entry** / **module_as_entry** | FQDN или ASN/префикс + `community_id` |
| **Содержимое модуля** | AS и префиксы; CDN-строки; FQDN; у каждой сущности — **привязка к community** (FK). | | **bgp_community** | Справочник community для BIRD |
| **Справочник BGP community** | Канонические записи: `standard` (65535:123), `large` (x:y:z) при необходимости, человекочитаемое имя, описание, `tenant_id`. | | **bgp_peer** | Клиентский пир; `speaker_id` NULL = все спикеры в бандле |
| **Привязки community** | Связь «сущность → community»: для **домена**, **ASN**, **префикса/CIDR** (в т.ч. из CDN-листа) — `community_id`; при генерации BIRD маршруты/фильтры получают соответствующий `bgp_community.add()`. | | **bgp_speaker** | master / replica, `last_applied_revision_id` |
| **Пиры (клиенты)** | Удалённый BGP-сосед, который **получает** анонсы с вашего BIRD: neighbor IP, remote ASN, секреты, `export`/`import` политика, теги (группы клиентов). Одна строка ≈ одна сессия к одному клиентскому роутеру. | | **config_revision** / **revision_materialized_prefix** | История, diff, откат |
| **Экземпляр BIRD (`bgp_speaker`)** | Мастерский и/или репликовый BIRD: `role` (`master` / `replica`), hostname, `last_applied_revision_id`, для реплики — URL мастера / токен ноды (или вне секретов). Реплика **не** ведёт ingest; только pull бандла. | | **job_audit** | Асинхронные задачи; в reference дополняет брокер |
| **История (append-only)** | Снимок состояния или дифф после каждого успешного применения; `revision_id`, автор (API key/user), timestamp. |
| **Журнал заданий** | Очередь «пересобрать модуль X», «откатить на revision Y», статус, ошибки. |
### Расписание: модуль и отдельно каждый CDN-источник ### 4.2. ETL
- **Уровень модуля:** поля вроде `refresh_interval_seconds` и/или `cron_expr` (если нужны окна по времени); воркер ставит следующий запуск по минимальному интервалу среди дочерних сущностей.
- **Уровень CDN-строки** (таблица `module_cdn_sources` или аналог): для каждого URL/файла — **опционально** свой `refresh_interval_seconds`. Если задан — имеет приоритет над модулем для **fetch** этого списка; если NULL — используется интервал модуля.
- **Домены и AS:** по умолчанию следуют расписанию модуля; при необходимости позже можно добавить переопределение на уровне отдельной записи FQDN (аналогично CDN).
### DoH для резолва доменов
- В БД хранится **профиль** (`doh_profiles`): базовый URL DoH, при необходимости заголовки/API-ключ (секрет — вне БД или зашифровано), таймаут, лимит параллельных запросов.
- Модуль типа `DOMAINS` ссылается на `doh_profile_id` (или наследует default из глобальных настроек).
- Воркер выполняет DNS-запросы **только через выбранный DoH** (RFC 8484 JSON или wire-format POST — зафиксировать один поддерживаемый режим в реализации), а не системный stub resolver, чтобы поведение было предсказуемым и привязанным к политике.
### Справочник community и привязки
- Таблица `bgp_community` (справочник): имя, тип (`standard` / `large` / `extended` — по потребности), значения полей, уникальность по `(tenant_id, representation)`.
- Для **каждой** привязываемой сущности в контенте модулей — поле `community_id` (nullable: «наследовать от модуля» или «без community» по политике):
- домен → community;
- ASN (в AS-модуле) → community;
- CIDR/префикс (в т.ч. строка из CDN-листа после парсинга) → community.
- **Разрешение конфликтов:** если один префикс попал из двух источников с разными community — в плане заложить явное правило (приоритет модуля, приоритет специфичности CIDR, или запрет дубликата с алертом). Зафиксировать в конфиге по умолчанию: «более специфичный источник wins» + лог предупреждения.
**Генерация BIRD:** для каждого итогового префикса (или группы) в `filter` экспорта добавляется соответствующий `bgp_community.add(...)` из справочника; именованные community можно сгенерировать как `define` в отдельном include.
**Откат:** не переписывать текущее состояние «вручную», а хранить **ревизии** (например JSON-снимок или нормализованные строки в history-таблицах). Операция rollback = `INSERT` новой ревизии с содержимым выбранной старой + триггер перегенерации. Так история остаётся линейной и аудируемой.
**Дополнительно:** мягкие блокировки (`SELECT ... FOR UPDATE` на уровне модуля или экземпляра `bgp_speaker` при применении), чтобы два REST-вызова не портили друг друга.
### Диаграмма ETL (от источников до BIRD и ревизий)
```mermaid ```mermaid
flowchart LR flowchart LR
subgraph extract [Extract] subgraph ex [Extract]
CDN[CDN URL и HTTP fetch] CDN[CDN fetch]
DOH[DoH A или AAAA] DOH[DoH]
AS[AS и префиксы из БД] AS[AS из БД]
end end
subgraph transform [Transform] subgraph tr [Transform]
NORM[Нормализация CIDR] NORM[Нормализация CIDR]
DEDUP[Дедуп и конфликты] DEDUP[Дедуп]
COMM[Подстановка community] COMM[Community]
end end
subgraph load [Load] subgraph ld [Load]
MYSQL[(MySQL материализация)] DB[(SQL БД)]
REV[revision и snapshot] REV[revision]
ART[Артефакты BIRD] ART[артефакты BIRD]
end
subgraph out [Выход]
BIRD[BIRD сервер анонсов]
end end
CDN --> NORM CDN --> NORM
DOH --> NORM DOH --> NORM
AS --> NORM AS --> NORM
NORM --> DEDUP NORM --> DEDUP --> COMM
DEDUP --> COMM COMM --> DB
COMM --> MYSQL
COMM --> REV COMM --> REV
REV --> ART REV --> ART
ART --> BIRD
``` ```
Пояснение: **Extract** разнесён по сервису `evobgp-ingest`; **Transform** частично в ingest, частично в `evobgp-render` (финальное объединение модулей); **Load** — транзакции в MySQL + запись файлов/объектов для деплоя. ### 4.3. ER (упрощённо)
### Диаграмма схемы MySQL (сущности и зависимости FK)
Схема упрощена; имена таблиц — ориентир для миграций (`golang-migrate` / `goose`).
```mermaid ```mermaid
erDiagram erDiagram
@@ -211,293 +218,196 @@ erDiagram
bgp_community ||--o{ module_domain_entry : tags bgp_community ||--o{ module_domain_entry : tags
bgp_community ||--o{ module_as_entry : tags bgp_community ||--o{ module_as_entry : tags
bgp_community ||--o{ module_cdn_source : tags bgp_community ||--o{ module_cdn_source : tags
bgp_speaker ||--o{ bgp_peer : optional_scope bgp_speaker ||--o{ bgp_peer : scope
module ||--o{ config_revision : produces module ||--o{ config_revision : produces
config_revision ||--o{ revision_materialized_prefix : snapshot config_revision ||--o{ revision_materialized_prefix : snapshot
module ||--o{ job_audit : async_tasks module ||--o{ job_audit : tasks
``` ```
### Пояснения к таблицам MySQL ### 4.4. Пояснения к таблицам
Ниже — **назначение**, **основные поля (логически)** и **кто пишет/читает**. Точные типы и индексы задаются в миграциях.
| Таблица | Назначение | Ключевые поля и смысл | Кто использует | | Таблица | Назначение | Ключевые поля | Кто использует |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------ |
| **tenant** | Изоляция клиентов/организаций (multi-tenant). | `id`, `name`, `slug`, статус; все сущности ниже с `tenant_id` при необходимости. | API, все сервисы при фильтрации. | | **tenant** | Multi-tenant | `id`, `name`, `slug` | API, все сервисы |
| **module** | Логический блок политики: тип `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`, включён ли, приоритет при конфликтах. | `tenant_id`, `type`, `enabled`, `priority`, `doh_profile_id` (для DOMAINS), `refresh_interval_sec` (и/или `cron_expr`), `default_community_id` (опционально для наследования). | API (CRUD), scheduler (интервалы), ingest/render (содержимое). | | **module** | Блок политики | `type`, `enabled`, `priority`, `doh_profile_id`, `refresh_interval_sec`, `cron_expr`, `default_community_id` | API, scheduler, ingest, render |
| **doh_profile** | Параметры DNS over HTTPS для резолва доменов. | `url` (HTTPS DoH), таймауты, ссылка на секрет (ID в Vault/K8s, не сам пароль в открытом виде), политика TLS. | Модули DOMAINS, ingest. | | **doh_profile** | DoH | `url`, таймауты, ссылка на секрет | DOMAINS, ingest |
| **module_cdn_source** | Один источник CDN внутри модуля типа `CDN_CIDRS`: URL или встроенный список. | `module_id`, `source_kind` (url / inline), `url`, `etag`, `last_modified`, `refresh_interval_sec` (если NULL — брать из `module`), `community_id` (тег для префиксов из этого источника). | Ingest (fetch), render. | | **module_cdn_source** | Строка CDN | `source_kind`, `url`, `etag`, `refresh_interval_sec`, `community_id` | ingest, render |
| **module_domain_entry** | Одна строка FQDN в модуле `DOMAINS`. | `module_id`, `fqdn`, `community_id`, опционально переопределение интервала; материализованные поля после резолва можно хранить в отдельной таблице или здесь (`last_resolved_at`, хэш ответа). | Ingest (DoH), render. | | **module_domain_entry** | FQDN | `fqdn`, `community_id`, метаданные резолва | ingest, render |
| **module_as_entry** | Одна строка в модуле `AS_PREFIXES`: ASN и/или явный префикс. | `module_id`, `asn`, `prefix` (nullable если задаётся только ASN), `community_id`. | API, render (если данные не из внешнего IRR — тогда расширить провайдером). | | **module_as_entry** | AS/префикс | `asn`, `prefix`, `community_id` | API, render |
| **bgp_community** | Справочник BGP community для экспорта в BIRD. | `tenant_id`, `name`, `kind` (standard/large/extended), числовые поля значения, уникальность в рамках tenant. | API, render (генерация `filter` / `define`). | | **bgp_community** | Справочник | `kind`, значения, уникальность в tenant | API, render |
| **bgp_peer** | Клиентский BGP-пир. | Как выше; `bgp_speaker_id`: **NULL** — включить в **бандлы всех** спикеров (зеркало); иначе только выбранный мастер/реплика. | API, render, упаковка бандла. | | **bgp_peer** | Клиентский пир | neighbor, ASN, политики, `bgp_speaker_id` (NULL = все спикеры) | API, render, бандл |
| **bgp_speaker** | Экземпляр BIRD: **master** или **replica**. | `role`, endpoint для agent/deploy, `last_applied_revision_id`; для replica — учётные данные к API бандла (вне БД). | deploy, бандлы, нода. | | **bgp_speaker** | Экземпляр BIRD | `role` master/replica, endpoint, `last_applied_revision_id` | deploy, нода |
| **config_revision** | Неизменяемая точка истории после успешного применения политики. | `id`, `module_id` или `NULL` (глобальная ревизия), `created_at`, `author`, `hash` префикс-сета, ссылка на артефакт (путь/URL в object storage), `parent_revision_id` (для отката как «новая ревизия со старым содержимым»). | render, deploy, API (rollback, audit). | | **config_revision** | История | `hash`, артефакт, `parent_revision_id` | render, deploy, rollback |
| **revision_materialized_prefix** | Снимок итоговых префиксов для ревизии (для быстрого diff и отката без пересчёта из сырья). | `revision_id`, `prefix`, `cidr_len`, `community_id`, `source` (модуль/тип). | render (запись), API (preview, diff), откат. | | **revision_materialized_prefix** | Снимок префиксов | `revision_id`, `prefix`, `community_id`, `source` | preview, diff, откат |
| **job_audit** | Журнал асинхронных операций (дополняет брокер, не заменяет его). | `id`, `kind` (refresh / apply / rollback), `module_id`, `status`, `error`, `idempotency_key`, `created_at`. | API, операторы, ретраи. | | **job_audit** | Задачи | `kind`, `status`, `idempotency_key` | API, воркеры |
**Дополнительно (по необходимости):** Дополнительно: `**global_settings`** (KV); `**module_cdn_fetch_log`** (опционально, TTL).
- `**global_settings`** — ключ–значение для дефолтного DoH, лимитов, feature flags; одна строка на tenant или плоская таблица. В **reference** очередь: брокер + `job_audit`; в **microVPS** — только БД и идемпотентность в `job_audit`.
- `**module_cdn_fetch_log`** (опционально) — сырые ответы HTTP, размер, время, для отладки CDN; с TTL очистки.
Очередь в **NATS/Redis** в MySQL не дублируется обязательно; `job_audit` нужен для **идемпотентности**, **аудита** и отображения статуса в UI.
### Диаграмма зависимостей сервисов и компонентов
```mermaid
flowchart TB
subgraph clients [Клиенты]
CLI[CLI или CI]
UI[Опционально UI]
end
clients --> LB
LB[Reverse proxy]
LB --> API
API --> MYSQL[(MySQL)]
API --> MQ[Message broker]
SCH[scheduler] --> MYSQL
SCH --> MQ
ING[ingest] --> MQ
ING --> MYSQL
ING --> EXT[Интернет CDN и DoH]
REN[render] --> MQ
REN --> MYSQL
DEP[deploy] --> MQ
DEP --> MYSQL
DEP --> AG[evobgp-agent]
AG --> BIRD[BIRD]
PROM[Prometheus] --> API
PROM --> ING
PROM --> AG
```
**Зависимости по данным:** все мутирующие сервисы согласуются через **MySQL** и **очередь**; **evobgp-agent** на хосте BIRD не ходит в MySQL напрямую — получает артефакты от `evobgp-deploy` (pull/mTLS/SSH). **Клиентские роутеры** в БД не фигурируют как хосты EvoBGP — только как записи `bgp_peer`.
--- ---
## 2. Три типа include-модулей ## 5. Модули префиксов (AS / CDN / домены)
| Тип | Ввод | Поведение | | Тип | Ввод | Поведение |
| ----------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- | | ---------- | -------------- | -------------------------------------------------------- |
| **AS / префиксы** | Список AS или явные префиксы | Импорт из ваших таблиц или опционально **pdb/API RIR** (вне scope MVP — заложить интерфейс `PrefixProvider`). | | **AS** | ASN / префиксы | Таблица + опционально внешний `PrefixProvider` |
| **CDN CIDR** | URL или статический список | Периодический fetch + парсинг (plain text, JSON); хранить `etag`/`last_modified` для условных запросов. | | **CDN** | URL или список | Fetch, ETag, интервал модуля или строки |
| **Домены** | FQDN | См. ниже: в BIRD **не** попадают строки доменов — только результат резолва. | | **Домены** | FQDN | В BIRD попадают **только** IP-префиксы после DoH-резолва |
### FQDN → что именно кладётся в `include` BIRD **FQDN:** воркер резолвит через **DoH-профиль** модуля → `/32` / `/128` (или политика) → материализация в БД → генерация static include для BIRD.
**В конфигурацию BIRD доменные имена не записываются.** В языке BIRD нет встроенного «подставь IP по DNS при reload» для списков анонсируемых префиксов: политика и `filter` оперируют **префиксами** (и community и т.д.), а не FQDN.
Пайплайн для модуля `DOMAINS`:
1. **Воркер (control plane)** по расписанию модуля, по TTL или по `POST /modules/{id}/refresh` выполняет DNS lookup (A/AAAA) для каждого FQDN **через DoH-профиль** модуля (см. раздел про DoH в MySQL).
2. Результат нормализуется в префиксы, обычно **хост-префиксы** `/32` (IPv4) и `/128` (IPv6) для каждого полученного адреса; при необходимости политика может задавать агрегацию (редко для «точечных» CDN/host записей).
3. В MySQL хранятся и **исходные FQDN** (для аудита и повторного резолва), и **материализованный набор префиксов** с `resolved_at` / сроком жизни.
4. **Генератор** собирает обычный статический фрагмент, например `include "/etc/bird.d/prefixes_from_domains.conf"`, внутри — объявления вида `route` / набор в `define` / список в `filter` (конкретный синтаксис зависит от выбранной схемы BIRD 2.x), но **только из IP-префиксов**, уже полученных на шаге 2.
Итог: **include в BIRD — это всегда уже готовые префиксы**; смена IP у DNS обновляет анонс только после следующего успешного резолва и перегенерации конфига + `birdc configure` (или эквивалент). Это совпадает с ограничением из раздела «Риски»: без частого refresh доменный модуль может отставать от реальности.
Общий пайплайн: модуль → нормализованный список **префиксов + community из справочника по привязкам** → объединение с дедупликацией и политикой конфликтов (более специфичный wins или явный приоритет модулей).
--- ---
## 3. REST API: обновление «отдельного модуля» ## 6. REST API
Минимальный набор эндпоинтов: - `POST /modules/{id}/refresh`
- `POST /apply`, `POST /speakers/{id}/apply` или `/bird/apply`
- `POST /modules/{id}/refresh` — пересобрать только этот модуль (CDN fetch / DNS refresh / перечитать AS-данные). - `GET /revisions`, `POST /revisions/{id}/rollback`
- `POST /apply` или `POST /speakers/{id}/apply` (или `/bird/apply` при одном сервере) — сгенерировать конфиг и применить на **хосте BIRD-сервера**; клиентские пиры подтянут изменения после перезагрузки сессии/политики export в BIRD. - `POST|PATCH /peers`, CRUD DoH / community / модулей
- `GET /revisions`, `POST /revisions/{id}/rollback`. - Реплики: `GET /v1/speakers/{id}/bundle/{revision}`, `GET .../revisions/latest`, enrollment нод
- `POST /peers` / `PATCH /peers/{id}` — добавление/изменение пира; опционально `POST /peers/{id}/apply`. - Асинхронно: **202** + `job_id`, `GET /jobs/{id}`
- CRUD для **профилей DoH**, **справочника community**, **расписаний** (если вынесены из PATCH модуля) — по необходимости UI/автоматизации. - Auth: API keys / mTLS
- Для реплик: `**GET /v1/speakers/{id}/bundle/{revision}`**, `**GET .../revisions/latest`**, опционально enrollment нод.
Ответы — **202 Accepted** + `job_id`, если работа асинхронная; **GET /jobs/{id}** для статуса.
Аутентификация: API keys или mTLS для production.
--- ---
## 4. Генерация BIRD и «на лету» ## 7. Генерация BIRD и ревизии
- Генерировать **фрагменты** (`/etc/bird.d/*.conf`) и один `bird.conf` с `include`. - Фрагменты `bird.d/*.conf`, `include`, фильтры, `peers.conf`, community из справочника.
- Статические фильтры: префиксы из БД; для экспорта — **community** из справочника по привязкам (отдельный include с `define` или динамика в `filter` по классам префиксов). - Ревизия: хэш набора префиксов, артефакты, откат = новая ревизия со старым содержимым.
- **RPKI** (опционально позже) — отдельный блок.
- **Пиры:** отдельный include `peers.conf`; при изменении — перезапись файла и `birdc configure` (или полный reload по политике безопасности).
Где возможно, использовать **runtime** команды BIRD для соседей; если версия/политика требует только файл — документировать один поддерживаемый путь (проще сопровождать).
--- ---
## 5. История и быстрый откат ## 8. Эксплуатация и масштаб
- Каждое успешное применение создаёт **revision** с хэшем содержимого анонсируемого набора и ссылкой на сгенерированные артефакты (опционально хранить сам `bird` snippet в BLOB для форензики). - До **~20+** клиентских пиров — строки `bgp_peer`, не отдельные хосты EvoBGP.
- Откат = создание новой ревизии с данными из выбранной + тот же pipeline генерации. - Rate-limit CDN, per-module cooldown.
- Индексы по `(module_id, created_at)` и `(revision_id)` для быстрых запросов. - Canary: несколько `bgp_speaker` или подмножество пиров по фильтру.
- Метрики: размер префикс-сета, ошибки DoH/CDN, `birdc show protocols`.
- Секреты BGP — Vault / K8s secrets, не plaintext в БД.
- Last-known-good конфиг на хосте BIRD.
--- ---
## 6. Масштаб: до ~20 клиентских BGP-пиров и один (или несколько) BIRD-сервер ## 9. Реплика evobgp-node
1. **Control-plane на мастере:** Docker с API/воркерами; **мастерский BIRD** + `evobgp-agent`. **Опционально** дополнительные хосты только с `**evobgp-node` + BIRD** (реплики), без своей БД. Клиенты — внешние пиры к мастеру и/или к репликам. 1. Render на мастере упаковывает **бандл** (как для мастерского BIRD) + **manifest** (SHA-256) + **подпись** (например Ed25519).
2. **Версия конфига:** `bgp_speaker.last_applied_revision_id` (или файл-маркер на хосте BIRD); агент репортит применённую ревизию. 2. **evobgp-node**: fetch → проверка → распаковка → `birdc configure`.
3. **Canary:** при **нескольких** `bgp_speaker` — сначала обновить один POP; при одном сервере — canary через **группу пиров** / отдельный `export` filter для подмножества `bgp_peer`, затем полный rollout. 3. Общие фильтры/префиксы **идентичны**; `local.conf` на ноде (router id, source) — вне бандла.
4. **Очередь и rate-limit:** массовый refresh CDN не должен DDOSить внешние списки; **per-module cooldown** в воркере. 4. Риски: дубли анонсов, отставание ревизий, компрометация без подписи.
5. **Наблюдаемость:** метрики: размер префикс-сета, ошибки DNS/CDN, **состояние BGP-сессий с клиентами** (`birdc show protocols`). Алерты на скачок/пропадание префиксов и на **Down** сессий к критичным клиентам.
6. **Консистентность БД:** транзакции при ревизиях; миграции `goose`/`golang-migrate`.
7. **Секреты:** MD5/password пиров не в открытом виде — Vault/K8s secrets.
8. **Multi-tenant:** `tenant_id` на модулях и пирах при необходимости.
9. **Dry-run:** `POST .../preview` перед apply на BIRD-сервер — обязателен при рискованных изменениях.
10. **Резервный путь:** last-known-good конфиг **на хосте BIRD**, если control-plane недоступен.
--- ---
## 7. Опциональная EvoBGP-нода: стянуть конфиг с мастера и поднять BIRD ## 10. Выбор СУБД
**Цель:** на отдельной площадке запустить **второй (или N-й) BIRD**, чтобы клиенты могли строить сессии **и к мастеру, и к ноде**, получая **одинаковые** наборы префиксов, community и **те же** правила `export`/`import` (как в сгенерированном конфиге мастера). Control-plane и MySQL на ноде **не нужны**. > **Решение по умолчанию:** **PostgreSQL** в **обоих** профилях — один тип миграций, один SQL-диалект в коде (`pgx` / `database/sql`), проще сопровождение и перенос с microVPS на reference без смены БД.
### Поток данных
1. На **мастере** после `evobgp-render` формируется **бандл ревизии**: каталог файлов, идентичный тому, что уходит на мастерский BIRD (`bird.conf` + includes: префиксы, фильтры, `peers.conf`, при необходимости отдельный фрагмент только для «общей» политики). | Движок | Роль в плане |
2. Добавляется **manifest** (JSON): список путей, SHA-256 каждого файла, `revision_id`, `speaker_id` или маркер «полное зеркало политики». | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
3. Бандл **подписывается** ключом мастера (например Ed25519); нода хранит **доверенный публичный** ключ / цепочку. | **PostgreSQL** | **Основной** для `reference` и `microVPS` (различаются только ресурсы и `postgresql.conf`). |
4. `**evobgp-node`** по расписанию или webhook: `GET` (или pull из S3/MinIO с тем же manifest) → проверка подписи и хэшей → атомарная распаковка в каталог BIRD → `birdc configure`. | **SQLite** | **Опция** `microVPS_sqlite`: один контейнер без PG — только при жёстком лимите «ровно один контейнер приложения+БД в одном процессе». |
5. Нода репортит на мастер опционально: `POST /nodes/{id}/applied` с `revision_id` (для дашборда «реплика отстаёт»). | **MySQL / MariaDB** | **Опционально** по требованию заказчика/хостинга; тот же слой DAO через второй драйвер — вне дефолтного пути. |
| **rqlite** | HA без отдельного DBA Postgres; **+контейнеры**; на 1 ГиБ тесно. |
### Одинаковые правила для клиентов на мастере и на ноде
- **Общая часть бандла** (префикс-листы, `filter`, `define` community) — **байт-в-байт** одинакова на мастере и реплике. ### Почему PG «не много» и всё же два контейнера на microVPS
- **Пиры:** если в БД у `bgp_peer` задано `speaker_id IS NULL` — пир попадает в бандлы **всех** спикеров с режимом зеркала; если указан конкретный `bgp_speaker_id` — только в бандл этого спикера (мастер vs реплика с разным набором соседей).
- **Локальные отличия только на ноде:** через небольшой `**local.conf`** (не из бандла): `router id`, при необходимости `source address` для BGP multihop, локальный `listen` — подставляется `evobgp-node` из env/volume **до** или **после** include общих файлов, без изменения семантики фильтров.
### API мастера (минимум) При **узкой** конфигурации (`shared_buffers` **64128 МиБ**, `max_connections` **1530**, **один** пул коннектов в `evobgp-all`, без сотен долгоживущих backend’ов) суммарный вклад PG **сопоставим** с практичным использованием SQLite в том же объёме RAM, зато:
- `GET /v1/speakers/{id}/revisions/latest` — метаданные и URL/тело бандла. - нет отличий DDL/типов между профилями;
- `GET /v1/speakers/{id}/bundle/{revision_id}` — архив (например `tar.zst`) + заголовок или sidecar с подписью. - нормальные **advisory locks** / `SKIP LOCKED` для очереди `job_audit`;
- Аутентификация ноды: **mTLS** или **Bearer** (токен выдачи при enrollment ноды). - проще подключить **внешний** managed Postgres при росте.
### Когда полный клон пиров возможен На **microVPS** отдельный контейнер `postgres`**осознанная плата** за единообразие (2 контейнера: `evobgp-all` + `postgres`).
Клиенты «одинаково» подключаются, если с их стороны допустимы **две независимые сессии** (к мастеру и к реплике) с **теми же** параметрами политики; **neighbor** в BIRD — IP клиента на стороне реплики/мастера. Если у клиента **разные** source IP к разным серверам — в БД это либо **две** записи `bgp_peer`, либо одна запись с учётом того, как BIRD видит remote (уточняется при внедрении). ### PostgreSQL: память и CPU (ориентиры)
| Сценарий соединения | RAM на backend |
| ---------------------- | -------------- |
| Чистый idle | ~1.5 МиБ |
| После простых запросов | ~10–11 МиБ |
| После тяжёлых / temp | ~14.5 МиБ |
Плюс `shared_buffers`, relation cache при огромной схеме, autovacuum. **reference:** при многих процессах Go — **PgBouncer** (transaction pooling). **microVPS:** пулер часто **не нужен**, если суммарно **≤10** реальных коннектов к PG. Таймауты: `idle_in_transaction_session_timeout`, `statement_timeout`, параметры `tcp_keepalives`_*; мониторинг `pg_stat_activity`.
### rqlite
Raft, HTTP/`gorqlite`; для HA control-plane; не замена дефолтного PG в плане без отдельного решения.
---
## 11. Профиль microVPS — детализация
> **Железо:** 1 vCPU · ~1024 МиБ RAM · **710 ГиБ** SSD под Docker + данные + логи · Ubuntu 24.04. Рекомендуется **swap 512 МиБ–1 ГиБ**, если его нет.
### Контейнеры
| Вариант | Состав |
| --------------- | ----------------------------------------------------------------------------- |
| **A (целевой)** | **evobgp-all** + **postgres** (официальный образ Postgres, volume для данных) |
| **B** | A + контейнер **BIRD** (privileged), если BIRD не на хосте Ubuntu |
| **C (опция)** | Только **evobgp-all** + SQLite на volume — профиль **microVPS_sqlite** |
### Бюджет диска 7–10 ГиБ
| Статья | Ориентир |
| ---------------- | ------------------------------------- |
| Образ evobgp-all | ~50150 МиБ |
| Образ PostgreSQL | ~80200 МиБ (слои образа) |
| Данные PG | ~200 МиБ – 2 ГиБ (политика ревизий) |
| Конфиги / бандлы | ~10–200 МиБ |
| Логи Docker | **max-size** / **max-file** в compose |
| Prune | `docker system prune` по расписанию |
| Резерв ОС/пики | ≥1–2 ГиБ |
### CPU и RAM
- Worker pool ingest/render: **12**; без агрессивного параллелизма на одном ядре.
- `deploy.resources`: лимиты на контейнеры **evobgp-all** и **postgres** (например PG `mem_limit` **256m384m**, приложение **384m512m** — подобрать по `free -m` на хосте).
- Ориентир суммарно: **~350–650 МиБ** простой, **~450800 МиБ** пик (**PG + Go + система + BIRD**) — на **1 ГиБ** нужны swap или ещё более жёсткий `shared_buffers`.
### Tuning PostgreSQL (microVPS)
Пример направлений (не копировать слепо — проверить по мониторингу):
- `shared_buffers` = **64128 МиБ**; `max_connections` = **2040**; `work_mem` умеренно низкий.
- Отключить или минимизировать `parallel_workers` на 1 vCPU.
- `effective_cache_size` подсказка планировщику без выделения RAM.
### Tuning PostgreSQL (reference)
Обычные практики под размер ВМ; **PgBouncer** при многих сервисах Go; резерв под autovacuum и пики ingest.
---
## 12. Риски и этапы внедрения
### Риски ### Риски
- **Дублирование анонсов** в одну и ту же сеть от двух BIRD с разным `router id` — согласовать с дизайном AS/апстримами (могут быть допустимы как anycast/резерв, могут требовать политики). - Домены и устаревшие IP; DoH down; IRR/RIR vs локальные фильтры; community-ошибки; дубли при master+node; **microVPS:** переполнение диска логами/ревизиями, OOM без swap.
- **Расхождение ревизий:** мастер обновился, нода отстала — мониторинг `applied_revision` на ноде.
- **Компрометация бандла** без подписи недопустима — только проверенные артефакты. ### Этапы
1. Monorepo Go, миграции **PostgreSQL** (основной путь), опционально SQLite для `microVPS_sqlite`, Compose `reference` и `microVPS`.
2. Один тип модуля end-to-end; render + BIRD.
3. Расписания CDN/модуля; deploy + agent.
4. Ревизии, rollback, bundle API.
5. **evobgp-node** на отдельной ВМ; observability.
6. Hardening, документация операторская.
--- ---
## Технологический стек **Соответствие старым именам:** `full``reference`, `target_vps``microVPS`.
- **Backend:** **Go** (1.22+): REST на `chi` / `echo` / `fiber`; драйвер MySQL — `database/sql` + `sqlc` или GORM по согласованию команды; DNS DoH — HTTP-клиент с проверкой TLS.
- **Миграции:** `golang-migrate` или `goose`, SQL в репозитории.
- **Контейнеры:** отдельный **multi-stage Dockerfile** на сервис (минимальный образ `distroless` или `alpine`); `docker compose.yaml` для локальной среды: `mysql`, `nats` или `redis`, сервисы `api`, `scheduler`, `ingest`, `render`, `deploy`, опционально `minio` для артефактов.
- **BIRD и агент:** BIRD на **мастере** и опционально на **нодах**; `evobgp-agent` — у мастерского BIRD; `**evobgp-node`** — лёгкий образ (pull бандла + локальный BIRD) **без** MySQL. Клиентские роутеры в стек EvoBGP **не входят**.
- **Наблюдаемость:** OpenTelemetry / Prometheus metrics в каждом Go-сервисе; единый `health` endpoint для оркестратора.
### Хранилище: MySQL и более лёгкие альтернативы
**MySQL** остаётся разумным выбором для **полного** стека: привычные репликации, бэкапы, несколько инстансов API/воркеров, нормальная конкуренция писателей.
Для **минимального потребления RAM** и профиля **edge** рассмотреть:
| Вариант | RAM / эксплуатация | Плюсы | Минусы |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **SQLite** (файл на volume, драйвер `modernc.org/sqlite` или CGO) | Почти **нет отдельного сервера** — память в процессе приложения; типично **десятки МиБ** на БД при умеренном объёме данных | Один контейнер `evobgp-all` без `mysql`; проще бэкап (копия файла + опционально **Litestream**); SQL и миграции те же с поправкой на диалект | Один писатель в классической модели — **нормально** для одного `evobgp-all`; несколько реплик приложения — нужна осторожность (WAL, или только чтение с реплик + один writer). Не замена кластерному MySQL без доп. продуктов. |
| **PostgreSQL** | Обычно **не легче** MySQL на малых инсталляциях | Богатый SQL | Для 1 ГиБ edge — не упрощение. |
| **Встраиваемые KV (badger, bbolt)** | Очень мало | Полный контроль | Нет SQL, сложнее отчёты/миграции/операторский доступ — обычно **не окупается**, если уже есть реляционная модель. |
**Рекомендация по коду:** слой repository/DAO с интерфейсами; реализация **MySQL** для `full`, **SQLite** для профиля `edge_sqlite` — миграции через тот же `goose` с **двумя диалектами** (или отдельные папки SQL с условной сборкой). Типы запросов в плане (ревизии, job-очередь, модули) **хорошо укладываются в SQLite** при одном процессе записи.
---
## Профиль развёртывания ~1 ГиБ RAM (edge / маленький VPS)
Цель — **весь docker-compose на одной ВМ с ≈1 ГиБ RAM** без OOM. Полноценный микросервисный разнобой для этого профиля **отключается**: та же логика, другой **способ упаковки**.
### Архитектурные решения
| Было (полный стек) | Для 1 ГиБ RAM |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 5 отдельных контейнеров Go | **Один** бинарь / **один** образ `evobgp-all`: внутри goroutines — HTTP API, планировщик, ingest, render, deploy-клиент (как подпроцессы логики, не отдельные ОС-процессы). |
| NATS / Redis | **Нет брокера:** очередь задач только в **MySQL** (`job_audit` + `SELECT … FOR UPDATE SKIP LOCKED` или аналог). |
| Traefik / Nginx | **Прямой** проброс порта на `evobgp-all` (или один `nginx` только если критично — тогда ещё −50–80 МиБ). |
| MinIO | Артефакты BIRD на **локальный volume** или в БД (BLOB/текст ограниченно), без object storage. |
| Prometheus на том же хосте | **Не крутить** на этой же машине; метрики — опционально pull снаружи или отключить в профиле `edge`. |
| MySQL | Опционально заменить на **SQLite** в том же процессе — см. раздел «Хранилище»; тогда **нет контейнера** с СУБД. |
### Контейнеры в compose (минимум)
**Вариант A (как раньше):** `mysql` + `evobgp-all`**2 контейнера**.
**Вариант B (максимально лёгкий):** только `**evobgp-all`**, SQLite-файл на **volume****1 контейнер**, минимальный RAM (см. ниже про SQLite).
### Tuning MySQL под малую память
Задать через `command` или `my.cnf` (ориентиры, подобрать по замерам):
- `innodb_buffer_pool_size`**96128 МиБ** (не дефолтные сотни МиБ).
- `max_connections`**2050** (достаточно для одного приложения).
- `table_open_cache` / `performance_schema` — снизить или отключить `performance_schema`, если допустимо.
- **Не** включать тяжёлые плагины; по возможности **одна** БД без реплики на этом же хосте.
Ожидаемый RSS MySQL после тюнинга: **~250400 МиБ** вместо 600+ МиБ «из коробки».
### Бюджет памяти (порядок)
**Почему Go не «съедает много» сам по себе:** у статически слинкованного Go-процесса в простое типичный **RSS десятки МиБ** (рантайм, GC, стеки goroutine). Он **существенно легче** типичного JVM/.NET для аналогичной роли. В прошлой оценке диапазон по Go получился завышенно пессимистичным, если читать его как «всегда 120–250 МиБ».
**Где растёт память у Go в этом приложении:** не сам рантайм, а **работа** — буферы HTTP при скачивании больших CDN-листов, тысячи параллельных DoH-ответов, большие слайсы префиксов при рендере, подключения к MySQL. Пик кратковременно поднимает heap до сотен МиБ, пока GC не соберёт; это можно сдерживать **лимитом concurrency** ingest и `GOGC` (и не держать гигантские строки в памяти целиком).
| Компонент | Простой (baseline) | Пик (тяжёлый ingest / большой diff) |
| ------------------------------ | ------------------ | ------------------------------------- |
| MySQL (после тюнинга) | **250400 МиБ** | чуть выше при большом InnoDB workload |
| `evobgp-all` (один процесс Go) | **~3080 МиБ** | **~80–180 МиБ** при нагрузке |
| Docker / ядро / page cache | **120220 МиБ** | **150250 МиБ** |
**Сумма ориентировочно:** **~400–700 МиБ** в типичном простое, **~500–850 МиБ** в пике — реалистично для **1 ГиБ** с MySQL и ограничением параллелизма.
**С SQLite (вариант B):** вычитается **~250–400 МиБ** сервера MySQL; остаётся **~150–450 МиБ** в простое и **~250–550 МиБ** в пике — **заметный запас** под 512 МиБ-VM невозможен без ещё более жёстких лимитов, но **1 ГиБ** становится комфортнее. Для **512 МиБ RAM** хоста целиться **только в SQLite** + один бинарь + жёсткий `mem_limit` на контейнер.
**Риски:** при большом числе модулей/CDN-параллелизма или огромных таблицах ревизий возможен **OOM** — в профиле `edge` задать **лимиты** `mem_limit` в compose и **очередь** тяжёлых задач (строго последовательно или 1–2 воркера).
### Связь с полным стеком
Код организовать так, чтобы **те же пакеты** `internal/scheduler`, `internal/ingest` и т.д. собирались в `cmd/evobgp-all` и отдельно в `cmd/evobgp-api`, … для production; **compose profile** `full` vs `edge` выбирает количество контейнеров.
---
## Риски и границы
- **Домены в BGP:** IP меняются; без короткого TTL и мониторинга возможны утечки/дыры. Заложить политику «максимальный срок жизни записи».
- **DoH:** недоступность выбранного резолвера блокирует обновление доменного модуля; иметь **fallback** (второй профиль или кратковременный отказ в смене префиксов с алертом) — по политике эксплуатации.
- **Согласование «что анонсировать»** с регистрацией в RIR/IRR — отдельная дисциплина; система может лишь **не выходить за заданные в БД границы** (prefix filters).
- **Community:** ошибка в справочнике или привязке ведёт к неверной маркировке трафика у апстримов; обязательны preview/diff перед apply и аудит изменений справочника.
- **Несколько BIRD с одной политикой (мастер + ноды):** возможны лишние/дублирующие анонсы в зависимости от топологии; проектировать совместно с маршрутизацией в AS.
---
## Предлагаемые этапы внедрения
1. Репозиторий Go (monorepo `cmd/evobgp-all` + `cmd/<service>` + `internal/`), миграции под **MySQL и SQLite**, `docker compose` с профилями `full`, `edge_1g` (MySQL + при необходимости брокер в `full`), опционально `edge_sqlite` (один контейнер).
2. Сервис `evobgp-api` + `evobgp-ingest` (один тип модуля) + очередь; затем `evobgp-render` и генерация BIRD.
3. `evobgp-scheduler` и политики интервалов (модуль + CDN-строка).
4. `evobgp-deploy` + `evobgp-agent` на **мастерском** BIRD; публикация бандла; ревизии и rollback в БД.
5. Опционально `**evobgp-node`**: pull бандла, подпись, второй BIRD, пилот с клиентами к мастеру и к ноде.
6. Наблюдаемость, hardening.