Update EvoBGP architecture plan with new overview and todos. Refined descriptions for control-plane and data-plane components, clarified roles of services, and added details on client interactions and replica nodes.

This commit is contained in:
Denozordec
2026-04-03 22:29:16 +07:00
parent cbf3aaa485
commit 31ff5bbd2b
@@ -1,9 +1,9 @@
--- ---
name: EvoBGP архитектура name: EvoBGP архитектура
overview: "Control-plane на Go в Docker: микросервисы (API, планировщик, ingest, генерация BIRD, доставка на узлы), MySQL, брокер очередей; префиксы из AS/CDN/доменов, расписания, DoH, community-справочник, REST, ревизии и откат; data-plane — BIRD и агент на узлах." overview: Control-plane на мастере; BIRD на мастере и опционально на EvoBGP-нодах, стягивающих подписанный бандл (префиксы+пиры+фильтры) для одинаковых правил. Клиенты — внешние BGP-пиры. MySQL/SQLite, Go, REST, ревизии.
todos: todos:
- id: schema-mysql - id: schema-mysql
content: "Схема MySQL: модули, расписания, CDN-источники, DoH-профили, справочник community, привязки, пиры, узлы, ревизии, jobs" content: "Схема БД (MySQL prod + SQLite edge): модули, CDN, DoH, community, пиры, ревизии, jobs"
status: pending status: pending
- id: bird-generator - id: bird-generator
content: Определить формат bird.conf фрагментов, фильтры и точки reload/configure content: Определить формат bird.conf фрагментов, фильтры и точки reload/configure
@@ -12,17 +12,20 @@ todos:
content: Спецификация REST (refresh модуля, apply, preview, rollback) и async jobs content: Спецификация REST (refresh модуля, apply, preview, rollback) и async jobs
status: pending status: pending
- id: node-agent - id: node-agent
content: Протокол доставки конфига на до 20 узлов (агент + версии + canary) content: evobgp-agent на мастере; опционально evobgp-node (pull бандла + BIRD на реплике)
status: pending status: pending
- id: observability - id: observability
content: Метрики, алерты на дрейф префиксов, статус пиров content: Метрики, алерты на дрейф префиксов, статус пиров
status: pending status: pending
- id: docker-ms - id: docker-ms
content: Dockerfile сервисов, compose (dev), сети/volumes, healthchecks content: Dockerfile, compose profiles full vs edge_1g, сети/volumes, healthchecks
status: pending status: pending
- id: go-modules - id: go-modules
content: Структура Go-модулей, общие пакеты (db, models, bird templating) content: Структура Go-модулей, общие пакеты (db, models, bird templating)
status: pending status: pending
- id: replica-bundle
content: Формат бандла ревизии, подпись, API выдачи, evobgp-node pull и локальный BIRD
status: pending
isProject: false isProject: false
--- ---
@@ -32,17 +35,20 @@ isProject: false
## Целевая картина (логическая) ## Целевая картина (логическая)
- **BIRD** — источник истины на уровне маршрутизации; **MySQL** — для политики, ревизий и материализованных префиксов. - **BIRD** на стороне **вашего сервера** — процесс, который **анонсирует** префиксы (и ведёт сессии с соседями). **Клиенты** — это **удалённые BGP-пиры** (до ~20 и более), которые **подключаются к этому серверу** и **получают** объявления; у них **свой** стек (не evobgp, не ваш Docker).
- **Быстрота:** очередь задач, идempotent-воркеры, при необходимости `birdc configure` после атомарной подмены include-файлов. - **MySQL/SQLite** — политика: какие префиксы, какие community, **какие пиры** в `protocol bgp` и кому что экспортировать.
- **Быстрота:** очередь задач, идempotent-воркеры, `birdc configure` после подмены include на **хосте BIRD**.
### Диаграмма: микросервисы и Docker (control-plane + data-plane) **Терминология:** **клиенты** — внешние BGP-пиры (их роутеры), не контейнеры EvoBGP. **Мастер** — control-plane + первичный BIRD (или только control-plane, если BIRD только на границе). **Опционально** — одна или несколько **EvoBGP-нод**: только стягивание готового бандла с мастера и локальный BIRD с **теми же** префиксами/фильтрами/правилами пиров (см. раздел «Реплика-нода»).
### Диаграмма: микросервисы и Docker (control-plane + BGP-сервер)
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph edge [Периметр] subgraph edge [Периметр]
LB[Traefik или Nginx] LB[Traefik или Nginx]
end end
subgraph docker [Docker host или кластер] subgraph docker [Docker host control-plane]
API[evobgp-api Go] API[evobgp-api Go]
SCH[evobgp-scheduler Go] SCH[evobgp-scheduler Go]
ING[evobgp-ingest Go] ING[evobgp-ingest Go]
@@ -51,11 +57,23 @@ flowchart TB
MQ[(NATS или Redis Streams)] MQ[(NATS или Redis Streams)]
DB[(MySQL)] DB[(MySQL)]
end end
subgraph nodes [До 20 узлов] subgraph speaker [Хост BGP сервера]
AG1[evobgp-agent Go] AG1[evobgp-agent опционально]
BR1[BIRD] BR1[BIRD анонсирует префиксы]
AG1 --> BR1 AG1 --> BR1
end 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 LB --> API
API --> DB API --> DB
API --> MQ API --> MQ
@@ -67,7 +85,9 @@ flowchart TB
REN --> DB REN --> DB
DEP --> MQ DEP --> MQ
DEP --> DB DEP --> DB
DEP -->|mTLS pull или push| AG1 DEP -->|конфиг на хост BIRD| AG1
BR1 <-->|BGP сессии| C1
BR1 <-->|BGP сессии| C2
``` ```
@@ -75,17 +95,18 @@ flowchart TB
Назначение сервисов (можно объединять на раннем MVP, границы — контракты между пакетами): Назначение сервисов (можно объединять на раннем MVP, границы — контракты между пакетами):
| Сервис | Роль | | Сервис | Роль |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **evobgp-api** | REST, аутентификация, CRUD сущностей, постановка задач (`refresh`, `apply`, `rollback`), `GET /jobs`. | | **evobgp-api** | REST, аутентификация, CRUD сущностей, постановка задач (`refresh`, `apply`, `rollback`), `GET /jobs`. |
| **evobgp-scheduler** | Читает интервалы модулей и CDN-строк из MySQL, публикует события «пора обновить модуль/источник» в очередь. | | **evobgp-scheduler** | Читает интервалы модулей и CDN-строк из MySQL, публикует события «пора обновить модуль/источник» в очередь. |
| **evobgp-ingest** | Fetch CDN, DoH-резолв доменов, загрузка AS/префиксов; пишет материализованные строки и сырые метаданные в MySQL. | | **evobgp-ingest** | Fetch CDN, DoH-резолв доменов, загрузка AS/префиксов; пишет материализованные строки и сырые метаданные в MySQL. |
| **evobgp-render** | Собирает итоговый набор префиксов + community, создаёт ревизию, генерирует артефакты BIRD (текст конфигов). | | **evobgp-render** | Собирает итоговый набор префиксов + community, создаёт ревизию, генерирует артефакты BIRD (текст конфигов). |
| **evobgp-deploy** | Доставка артефактов на узлы, учёт `node_config_version`, canary. | | **evobgp-deploy** | Доставка конфига **на мастерский** BIRD через `evobgp-agent`; **публикация** подписанного **бандла** для `evobgp-node` (HTTP или объектное хранилище). |
| **evobgp-agent** | Отдельный образ для узла: получение конфига, запись в volume, вызов `birdc`, отчёт о версии. | | **evobgp-agent** | На **мастерском** хосте BIRD: применить файлы от `evobgp-deploy`, `birdc`, отчитаться о ревизии. |
| **evobgp-node** | **Опционально** на удалённой площадке: периодически **скачивает** с мастера **подписанный бандл** ревизии (все include префиксов, фильтры, `peers.conf`), проверяет подпись/хэши, раскладывает на диск, вызывает локальный `birdc`. **Без** своей MySQL и без ingest — только **consumer** артефакта. |
Инфраструктурные контейнеры: **MySQL**, **брокер очередей** (NATS JetStream или Redis), опционально **Valkey/Redis** для кэша и rate-limit по модулям. **Инфраструктурные контейнеры:** **MySQL**, **брокер очередей** (NATS JetStream или Redis), опционально **Valkey/Redis** для кэша и rate-limit по модулям.
--- ---
@@ -94,19 +115,19 @@ flowchart TB
Рекомендуемые группы таблиц: Рекомендуемые группы таблиц:
| Область | Назначение | | Область | Назначение |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Модули** | Тип: `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`; включён/выключен; приоритет; ссылки на расписание и (для доменов) DoH-профиль. | | **Модули** | Тип: `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`; включён/выключен; приоритет; ссылки на расписание и (для доменов) DoH-профиль. |
| **Расписания обновления** | Базовый интервал на **модуль** (`refresh_interval_sec`, cron или interval); см. ниже про переопределение на CDN. | | **Расписания обновления** | Базовый интервал на **модуль** (`refresh_interval_sec`, cron или interval); см. ниже про переопределение на CDN. |
| **Источники CDN внутри модуля** | Для типа `CDN_CIDRS`: несколько записей «URL/статический список» на модуль; у **каждой** записи свой опциональный `refresh_interval_sec` (если NULL — брать интервал модуля). | | **Источники CDN внутри модуля** | Для типа `CDN_CIDRS`: несколько записей «URL/статический список» на модуль; у **каждой** записи свой опциональный `refresh_interval_sec` (если NULL — брать интервал модуля). |
| **Профили DoH** | URL HTTPS DoH (`https://…/dns-query`), опционально имя для SNI, таймауты, доверие к сертификату (политика); привязка к модулям `DOMAINS` или глобальный default. | | **Профили DoH** | URL HTTPS DoH (`https://…/dns-query`), опционально имя для SNI, таймауты, доверие к сертификату (политика); привязка к модулям `DOMAINS` или глобальный default. |
| **Содержимое модуля** | AS и префиксы; CDN-строки; FQDN; у каждой сущности — **привязка к community** (FK). | | **Содержимое модуля** | AS и префиксы; CDN-строки; FQDN; у каждой сущности — **привязка к community** (FK). |
| **Справочник BGP community** | Канонические записи: `standard` (65535:123), `large` (x:y:z) при необходимости, человекочитаемое имя, описание, `tenant_id`. | | **Справочник BGP community** | Канонические записи: `standard` (65535:123), `large` (x:y:z) при необходимости, человекочитаемое имя, описание, `tenant_id`. |
| **Привязки community** | Связь «сущность → community»: для **домена**, **ASN**, **префикса/CIDR** (в т.ч. из CDN-листа) — `community_id`; при генерации BIRD маршруты/фильтры получают соответствующий `bgp_community.add()`. | | **Привязки community** | Связь «сущность → community»: для **домена**, **ASN**, **префикса/CIDR** (в т.ч. из CDN-листа) — `community_id`; при генерации BIRD маршруты/фильтры получают соответствующий `bgp_community.add()`. |
| **Пиры** | neighbor IP, ASN, пароли/ключи (лучше ссылка на секреты), BGP параметры, привязка к группе узлов. | | **Пиры (клиенты)** | Удалённый BGP-сосед, который **получает** анонсы с вашего BIRD: neighbor IP, remote ASN, секреты, `export`/`import` политика, теги (группы клиентов). Одна строка ≈ одна сессия к одному клиентскому роутеру. |
| **Узлы** | Идентификатор узла (hostname), роль, теги для «каким пирам/политикам подчиняться». | | **Экземпляр BIRD (`bgp_speaker`)** | Мастерский и/или репликовый BIRD: `role` (`master` / `replica`), hostname, `last_applied_revision_id`, для реплики — URL мастера / токен ноды (или вне секретов). Реплика **не** ведёт ingest; только pull бандла. |
| **История (append-only)** | Снимок состояния или дифф после каждого успешного применения; `revision_id`, автор (API key/user), timestamp. | | **История (append-only)** | Снимок состояния или дифф после каждого успешного применения; `revision_id`, автор (API key/user), timestamp. |
| **Журнал заданий** | Очередь «пересобрать модуль X», «откатить на revision Y», статус, ошибки. | | **Журнал заданий** | Очередь «пересобрать модуль X», «откатить на revision Y», статус, ошибки. |
### Расписание: модуль и отдельно каждый CDN-источник ### Расписание: модуль и отдельно каждый CDN-источник
@@ -134,7 +155,7 @@ flowchart TB
**Откат:** не переписывать текущее состояние «вручную», а хранить **ревизии** (например JSON-снимок или нормализованные строки в history-таблицах). Операция rollback = `INSERT` новой ревизии с содержимым выбранной старой + триггер перегенерации. Так история остаётся линейной и аудируемой. **Откат:** не переписывать текущее состояние «вручную», а хранить **ревизии** (например JSON-снимок или нормализованные строки в history-таблицах). Операция rollback = `INSERT` новой ревизии с содержимым выбранной старой + триггер перегенерации. Так история остаётся линейной и аудируемой.
**Дополнительно:** мягкие блокировки (`SELECT ... FOR UPDATE` на уровне модуля/узла при применении), чтобы два REST-вызова не портили друг друга. **Дополнительно:** мягкие блокировки (`SELECT ... FOR UPDATE` на уровне модуля или экземпляра `bgp_speaker` при применении), чтобы два REST-вызова не портили друг друга.
### Диаграмма ETL (от источников до BIRD и ревизий) ### Диаграмма ETL (от источников до BIRD и ревизий)
@@ -156,7 +177,7 @@ flowchart LR
ART[Артефакты BIRD] ART[Артефакты BIRD]
end end
subgraph out [Выход] subgraph out [Выход]
BIRD[BIRD на узлах] BIRD[BIRD сервер анонсов]
end end
CDN --> NORM CDN --> NORM
DOH --> NORM DOH --> NORM
@@ -182,6 +203,7 @@ erDiagram
tenant ||--o{ module : owns tenant ||--o{ module : owns
tenant ||--o{ bgp_community : owns tenant ||--o{ bgp_community : owns
tenant ||--o{ bgp_peer : owns tenant ||--o{ bgp_peer : owns
tenant ||--o{ bgp_speaker : owns
doh_profile ||--o{ module : uses doh_profile ||--o{ module : uses
module ||--o{ module_cdn_source : contains module ||--o{ module_cdn_source : contains
module ||--o{ module_domain_entry : contains module ||--o{ module_domain_entry : contains
@@ -189,8 +211,7 @@ 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_node ||--o{ node_peer_binding : has bgp_speaker ||--o{ bgp_peer : optional_scope
bgp_peer ||--o{ node_peer_binding : has
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 : async_tasks
@@ -212,9 +233,8 @@ erDiagram
| **module_domain_entry** | Одна строка FQDN в модуле `DOMAINS`. | `module_id`, `fqdn`, `community_id`, опционально переопределение интервала; материализованные поля после резолва можно хранить в отдельной таблице или здесь (`last_resolved_at`, хэш ответа). | Ingest (DoH), render. | | **module_domain_entry** | Одна строка FQDN в модуле `DOMAINS`. | `module_id`, `fqdn`, `community_id`, опционально переопределение интервала; материализованные поля после резолва можно хранить в отдельной таблице или здесь (`last_resolved_at`, хэш ответа). | Ingest (DoH), render. |
| **module_as_entry** | Одна строка в модуле `AS_PREFIXES`: ASN и/или явный префикс. | `module_id`, `asn`, `prefix` (nullable если задаётся только ASN), `community_id`. | API, render (если данные не из внешнего IRR — тогда расширить провайдером). | | **module_as_entry** | Одна строка в модуле `AS_PREFIXES`: ASN и/или явный префикс. | `module_id`, `asn`, `prefix` (nullable если задаётся только ASN), `community_id`. | API, render (если данные не из внешнего IRR — тогда расширить провайдером). |
| **bgp_community** | Справочник BGP community для экспорта в BIRD. | `tenant_id`, `name`, `kind` (standard/large/extended), числовые поля значения, уникальность в рамках tenant. | API, render (генерация `filter` / `define`). | | **bgp_community** | Справочник BGP community для экспорта в BIRD. | `tenant_id`, `name`, `kind` (standard/large/extended), числовые поля значения, уникальность в рамках tenant. | API, render (генерация `filter` / `define`). |
| **bgp_peer** | Описание BGP-соседа (логический пир). | `tenant_id`, neighbor IP, remote ASN, локальные политики, **ссылка на секрет** (MD5/TC), `group_name`/`tags` для выбора на узлах. | API, render (`peers.conf`), deploy. | | **bgp_peer** | Клиентский BGP-пир. | Как выше; `bgp_speaker_id`: **NULL** — включить в **бандлы всех** спикеров (зеркало); иначе только выбранный мастер/реплика. | API, render, упаковка бандла. |
| **bgp_node** | Узел сети, где крутится BIRD и агент. | `tenant_id`, `hostname`, `api_endpoint` или идентификатор для mTLS, `tags`, `last_applied_revision_id`. | deploy, API (статус), мониторинг. | | **bgp_speaker** | Экземпляр BIRD: **master** или **replica**. | `role`, endpoint для agent/deploy, `last_applied_revision_id`; для replica — учётные данные к API бандла (вне БД). | deploy, бандлы, нода. |
| **node_peer_binding** | Какие пиры подняты на каком узле (many-to-many). | `bgp_node_id`, `bgp_peer_id`, возможно переопределение при необходимости. | API, render (генерация только релевантных сессий на узел). |
| **config_revision** | Неизменяемая точка истории после успешного применения политики. | `id`, `module_id` или `NULL` (глобальная ревизия), `created_at`, `author`, `hash` префикс-сета, ссылка на артефакт (путь/URL в object storage), `parent_revision_id` (для отката как «новая ревизия со старым содержимым»). | render, deploy, API (rollback, audit). | | **config_revision** | Неизменяемая точка истории после успешного применения политики. | `id`, `module_id` или `NULL` (глобальная ревизия), `created_at`, `author`, `hash` префикс-сета, ссылка на артефакт (путь/URL в object storage), `parent_revision_id` (для отката как «новая ревизия со старым содержимым»). | render, deploy, API (rollback, audit). |
| **revision_materialized_prefix** | Снимок итоговых префиксов для ревизии (для быстрого diff и отката без пересчёта из сырья). | `revision_id`, `prefix`, `cidr_len`, `community_id`, `source` (модуль/тип). | render (запись), API (preview, diff), откат. | | **revision_materialized_prefix** | Снимок итоговых префиксов для ревизии (для быстрого diff и отката без пересчёта из сырья). | `revision_id`, `prefix`, `cidr_len`, `community_id`, `source` (модуль/тип). | render (запись), API (preview, diff), откат. |
| **job_audit** | Журнал асинхронных операций (дополняет брокер, не заменяет его). | `id`, `kind` (refresh / apply / rollback), `module_id`, `status`, `error`, `idempotency_key`, `created_at`. | API, операторы, ретраи. | | **job_audit** | Журнал асинхронных операций (дополняет брокер, не заменяет его). | `id`, `kind` (refresh / apply / rollback), `module_id`, `status`, `error`, `idempotency_key`, `created_at`. | API, операторы, ретраи. |
@@ -258,7 +278,7 @@ flowchart TB
**Зависимости по данным:** все мутирующие сервисы согласуются через **MySQL** и **очередь**; агент не ходит в MySQL напрямую, только к API/deploy или к артефакт-хранилищу (S3/minio + подпись), в зависимости от выбранной реализации `evobgp-deploy`. **Зависимости по данным:** все мутирующие сервисы согласуются через **MySQL** и **очередь**; **evobgp-agent** на хосте BIRD не ходит в MySQL напрямую — получает артефакты от `evobgp-deploy` (pull/mTLS/SSH). **Клиентские роутеры** в БД не фигурируют как хосты EvoBGP — только как записи `bgp_peer`.
--- ---
@@ -294,10 +314,11 @@ flowchart TB
Минимальный набор эндпоинтов: Минимальный набор эндпоинтов:
- `POST /modules/{id}/refresh` — пересобрать только этот модуль (CDN fetch / DNS refresh / перечитать AS-данные). - `POST /modules/{id}/refresh` — пересобрать только этот модуль (CDN fetch / DNS refresh / перечитать AS-данные).
- `POST /apply` или `POST /nodes/{id}/apply` — сгенерировать конфиг и применить (см. раздел про узлы). - `POST /apply` или `POST /speakers/{id}/apply` (или `/bird/apply` при одном сервере) — сгенерировать конфиг и применить на **хосте BIRD-сервера**; клиентские пиры подтянут изменения после перезагрузки сессии/политики export в BIRD.
- `GET /revisions`, `POST /revisions/{id}/rollback`. - `GET /revisions`, `POST /revisions/{id}/rollback`.
- `POST /peers` / `PATCH /peers/{id}` — добавление/изменение пира; опционально `POST /peers/{id}/apply`. - `POST /peers` / `PATCH /peers/{id}` — добавление/изменение пира; опционально `POST /peers/{id}/apply`.
- CRUD для **профилей DoH**, **справочника community**, **расписаний** (если вынесены из PATCH модуля) — по необходимости UI/автоматизации. - CRUD для **профилей DoH**, **справочника community**, **расписаний** (если вынесены из PATCH модуля) — по необходимости UI/автоматизации.
- Для реплик: `**GET /v1/speakers/{id}/bundle/{revision}`**, `**GET .../revisions/latest`**, опционально enrollment нод.
Ответы — **202 Accepted** + `job_id`, если работа асинхронная; **GET /jobs/{id}** для статуса. Ответы — **202 Accepted** + `job_id`, если работа асинхронная; **GET /jobs/{id}** для статуса.
@@ -324,18 +345,54 @@ flowchart TB
--- ---
## 6. Масштаб до ~20 узлов: предложения по улучшению ## 6. Масштаб: до ~20 клиентских BGP-пиров и один (или несколько) BIRD-сервер
1. **Единый control-plane, много data-plane:** один API+воркер (или небольшой кластер API за балансировщиком), на каждом узле — **агент** (лёгкий daemon), который тянет готовый конфиг/дифф по **mTLS** или получает push через message queue. Так не нужен SSH с центра на 20 хостов. 1. **Control-plane на мастере:** Docker с API/воркерами; **мастерский BIRD** + `evobgp-agent`. **Опционально** дополнительные хосты только с `**evobgp-node` + BIRD** (реплики), без своей БД. Клиенты — внешние пиры к мастеру и/или к репликам.
2. **Идентичность конфигурации:** таблица `node_config_version`; после деплоя агент репортит `applied_revision`. Дашборд «какой узел отстаёт». 2. **Версия конфига:** `bgp_speaker.last_applied_revision_id` (или файл-маркер на хосте BIRD); агент репортит применённую ревизию.
3. **Canary / поэтапный rollout:** сначала 1–2 узла, затем остальные — снижает риск массового bad announce. 3. **Canary:** при **нескольких** `bgp_speaker` — сначала обновить один POP; при одном сервере — canary через **группу пиров** / отдельный `export` filter для подмножества `bgp_peer`, затем полный rollout.
4. **Очередь и rate-limit:** массовый refresh всех CDN-модулей не должен DDOSить внешние списки; **per-module cooldown** в воркере. 4. **Очередь и rate-limit:** массовый refresh CDN не должен DDOSить внешние списки; **per-module cooldown** в воркере.
5. **Наблюдаемость:** метрики (Prometheus): время генерации, размер префикс-сета, ошибки DNS/CDN, статус BIRD-сессий (через экспортер или scrape `birdc`). Алерты на **аномальный рост/падение** числа префиксов. 5. **Наблюдаемость:** метрики: размер префикс-сета, ошибки DNS/CDN, **состояние BGP-сессий с клиентами** (`birdc show protocols`). Алерты на скачок/пропадание префиксов и на **Down** сессий к критичным клиентам.
6. **Консистентность БД:** транзакции при записи ревизии + смене «текущего» указателя; миграции через Flyway/Liquibase или аналог. 6. **Консистентность БД:** транзакции при ревизиях; миграции `goose`/`golang-migrate`.
7. **Секреты:** пароли BGP не в открытом виде в MySQL — **Vault**, Kubernetes secrets, или зашифрованные поля с KMS. 7. **Секреты:** MD5/password пиров не в открытом виде — Vault/K8s secrets.
8. **Multi-tenant (если нужно):** `tenant_id` на модулях и пирах с самого начала — дешевле, чем латеральный рефакторинг. 8. **Multi-tenant:** `tenant_id` на модулях и пирах при необходимости.
9. **Dry-run:** `POST .../preview` возвращает diff префиксов и фрагмент BIRD без применения — обязателен для операций с 20 узлами. 9. **Dry-run:** `POST .../preview` перед apply на BIRD-сервер — обязателен при рискованных изменениях.
10. **Резервный путь:** локальный last-known-good конфиг на узле, если центр недоступен (только чтение, без изменения политики до восстановления связи). 10. **Резервный путь:** last-known-good конфиг **на хосте BIRD**, если control-plane недоступен.
---
## 7. Опциональная EvoBGP-нода: стянуть конфиг с мастера и поднять BIRD
**Цель:** на отдельной площадке запустить **второй (или N-й) BIRD**, чтобы клиенты могли строить сессии **и к мастеру, и к ноде**, получая **одинаковые** наборы префиксов, community и **те же** правила `export`/`import` (как в сгенерированном конфиге мастера). Control-plane и MySQL на ноде **не нужны**.
### Поток данных
1. На **мастере** после `evobgp-render` формируется **бандл ревизии**: каталог файлов, идентичный тому, что уходит на мастерский BIRD (`bird.conf` + includes: префиксы, фильтры, `peers.conf`, при необходимости отдельный фрагмент только для «общей» политики).
2. Добавляется **manifest** (JSON): список путей, SHA-256 каждого файла, `revision_id`, `speaker_id` или маркер «полное зеркало политики».
3. Бандл **подписывается** ключом мастера (например Ed25519); нода хранит **доверенный публичный** ключ / цепочку.
4. `**evobgp-node`** по расписанию или webhook: `GET` (или pull из S3/MinIO с тем же manifest) → проверка подписи и хэшей → атомарная распаковка в каталог BIRD → `birdc configure`.
5. Нода репортит на мастер опционально: `POST /nodes/{id}/applied` с `revision_id` (для дашборда «реплика отстаёт»).
### Одинаковые правила для клиентов на мастере и на ноде
- **Общая часть бандла** (префикс-листы, `filter`, `define` community) — **байт-в-байт** одинакова на мастере и реплике.
- **Пиры:** если в БД у `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 мастера (минимум)
- `GET /v1/speakers/{id}/revisions/latest` — метаданные и URL/тело бандла.
- `GET /v1/speakers/{id}/bundle/{revision_id}` — архив (например `tar.zst`) + заголовок или sidecar с подписью.
- Аутентификация ноды: **mTLS** или **Bearer** (токен выдачи при enrollment ноды).
### Когда полный клон пиров возможен
Клиенты «одинаково» подключаются, если с их стороны допустимы **две независимые сессии** (к мастеру и к реплике) с **теми же** параметрами политики; **neighbor** в BIRD — IP клиента на стороне реплики/мастера. Если у клиента **разные** source IP к разным серверам — в БД это либо **две** записи `bgp_peer`, либо одна запись с учётом того, как BIRD видит remote (уточняется при внедрении).
### Риски
- **Дублирование анонсов** в одну и ту же сеть от двух BIRD с разным `router id` — согласовать с дизайном AS/апстримами (могут быть допустимы как anycast/резерв, могут требовать политики).
- **Расхождение ревизий:** мастер обновился, нода отстала — мониторинг `applied_revision` на ноде.
- **Компрометация бандла** без подписи недопустима — только проверенные артефакты.
--- ---
@@ -344,9 +401,85 @@ flowchart TB
- **Backend:** **Go** (1.22+): REST на `chi` / `echo` / `fiber`; драйвер MySQL — `database/sql` + `sqlc` или GORM по согласованию команды; DNS DoH — HTTP-клиент с проверкой TLS. - **Backend:** **Go** (1.22+): REST на `chi` / `echo` / `fiber`; драйвер MySQL — `database/sql` + `sqlc` или GORM по согласованию команды; DNS DoH — HTTP-клиент с проверкой TLS.
- **Миграции:** `golang-migrate` или `goose`, SQL в репозитории. - **Миграции:** `golang-migrate` или `goose`, SQL в репозитории.
- **Контейнеры:** отдельный **multi-stage Dockerfile** на сервис (минимальный образ `distroless` или `alpine`); `docker compose.yaml` для локальной среды: `mysql`, `nats` или `redis`, сервисы `api`, `scheduler`, `ingest`, `render`, `deploy`, опционально `minio` для артефактов. - **Контейнеры:** отдельный **multi-stage Dockerfile** на сервис (минимальный образ `distroless` или `alpine`); `docker compose.yaml` для локальной среды: `mysql`, `nats` или `redis`, сервисы `api`, `scheduler`, `ingest`, `render`, `deploy`, опционально `minio` для артефактов.
- **BIRD и агент:** BIRD обычно на хосте или в **privileged** контейнере с `CAP_NET_ADMIN` и доступом к сетевому стеку; образ `evobgp-agent` монтирует volume с конфигом и взаимодействует с сокетом `birdc` (монтирование `bird.ctl`). - **BIRD и агент:** BIRD на **мастере** и опционально на **нодах**; `evobgp-agent` — у мастерского BIRD; `**evobgp-node`** — лёгкий образ (pull бандла + локальный BIRD) **без** MySQL. Клиентские роутеры в стек EvoBGP **не входят**.
- **Наблюдаемость:** OpenTelemetry / Prometheus metrics в каждом Go-сервисе; единый `health` endpoint для оркестратора. - **Наблюдаемость:** 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` выбирает количество контейнеров.
--- ---
## Риски и границы ## Риски и границы
@@ -355,14 +488,16 @@ flowchart TB
- **DoH:** недоступность выбранного резолвера блокирует обновление доменного модуля; иметь **fallback** (второй профиль или кратковременный отказ в смене префиксов с алертом) — по политике эксплуатации. - **DoH:** недоступность выбранного резолвера блокирует обновление доменного модуля; иметь **fallback** (второй профиль или кратковременный отказ в смене префиксов с алертом) — по политике эксплуатации.
- **Согласование «что анонсировать»** с регистрацией в RIR/IRR — отдельная дисциплина; система может лишь **не выходить за заданные в БД границы** (prefix filters). - **Согласование «что анонсировать»** с регистрацией в RIR/IRR — отдельная дисциплина; система может лишь **не выходить за заданные в БД границы** (prefix filters).
- **Community:** ошибка в справочнике или привязке ведёт к неверной маркировке трафика у апстримов; обязательны preview/diff перед apply и аудит изменений справочника. - **Community:** ошибка в справочнике или привязке ведёт к неверной маркировке трафика у апстримов; обязательны preview/diff перед apply и аудит изменений справочника.
- **Несколько BIRD с одной политикой (мастер + ноды):** возможны лишние/дублирующие анонсы в зависимости от топологии; проектировать совместно с маршрутизацией в AS.
--- ---
## Предлагаемые этапы внедрения ## Предлагаемые этапы внедрения
1. Репозиторий Go (monorepo `cmd/<service>` + `internal/`), MySQL-миграции, `docker compose` с MySQL и брокером. 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. 2. Сервис `evobgp-api` + `evobgp-ingest` (один тип модуля) + очередь; затем `evobgp-render` и генерация BIRD.
3. `evobgp-scheduler` и политики интервалов (модуль + CDN-строка). 3. `evobgp-scheduler` и политики интервалов (модуль + CDN-строка).
4. `evobgp-deploy` + `evobgp-agent`, ревизии и rollback в БД. 4. `evobgp-deploy` + `evobgp-agent` на **мастерском** BIRD; публикация бандла; ревизии и rollback в БД.
5. Наблюдаемость, hardening контейнеров (non-root где возможно, read-only root), пилот на 2–3 узлах, затем шаблон для остальных. 5. Опционально `**evobgp-node`**: pull бандла, подпись, второй BIRD, пилот с клиентами к мастеру и к ноде.
6. Наблюдаемость, hardening.