diff --git a/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md b/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md index ad5460a..f503b02 100644 --- a/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md +++ b/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md @@ -1,9 +1,9 @@ --- 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: - id: schema-mysql - content: "Схема MySQL: модули, расписания, CDN-источники, DoH-профили, справочник community, привязки, пиры, узлы, ревизии, jobs" + content: "Схема БД (MySQL prod + SQLite edge): модули, CDN, DoH, community, пиры, ревизии, jobs" status: pending - id: bird-generator content: Определить формат bird.conf фрагментов, фильтры и точки reload/configure @@ -12,17 +12,20 @@ todos: content: Спецификация REST (refresh модуля, apply, preview, rollback) и async jobs status: pending - id: node-agent - content: Протокол доставки конфига на до 20 узлов (агент + версии + canary) + content: evobgp-agent на мастере; опционально evobgp-node (pull бандла + BIRD на реплике) status: pending - id: observability content: Метрики, алерты на дрейф префиксов, статус пиров status: pending - id: docker-ms - content: Dockerfile сервисов, compose (dev), сети/volumes, healthchecks + content: Dockerfile, compose profiles full vs edge_1g, сети/volumes, healthchecks status: pending - id: go-modules content: Структура Go-модулей, общие пакеты (db, models, bird templating) status: pending + - id: replica-bundle + content: Формат бандла ревизии, подпись, API выдачи, evobgp-node pull и локальный BIRD + status: pending isProject: false --- @@ -32,17 +35,20 @@ isProject: false ## Целевая картина (логическая) -- **BIRD** — источник истины на уровне маршрутизации; **MySQL** — для политики, ревизий и материализованных префиксов. -- **Быстрота:** очередь задач, идempotent-воркеры, при необходимости `birdc configure` после атомарной подмены include-файлов. +- **BIRD** на стороне **вашего сервера** — процесс, который **анонсирует** префиксы (и ведёт сессии с соседями). **Клиенты** — это **удалённые BGP-пиры** (до ~20 и более), которые **подключаются к этому серверу** и **получают** объявления; у них **свой** стек (не evobgp, не ваш Docker). +- **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 flowchart TB subgraph edge [Периметр] LB[Traefik или Nginx] end - subgraph docker [Docker host или кластер] + subgraph docker [Docker host control-plane] API[evobgp-api Go] SCH[evobgp-scheduler Go] ING[evobgp-ingest Go] @@ -51,11 +57,23 @@ flowchart TB MQ[(NATS или Redis Streams)] DB[(MySQL)] end - subgraph nodes [До 20 узлов] - AG1[evobgp-agent Go] - BR1[BIRD] + 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 --> MQ @@ -67,7 +85,9 @@ flowchart TB REN --> DB DEP --> MQ DEP --> DB - DEP -->|mTLS pull или push| AG1 + DEP -->|конфиг на хост BIRD| AG1 + BR1 <-->|BGP сессии| C1 + BR1 <-->|BGP сессии| C2 ``` @@ -75,17 +95,18 @@ flowchart TB Назначение сервисов (можно объединять на раннем 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** | Доставка артефактов на узлы, учёт `node_config_version`, canary. | -| **evobgp-agent** | Отдельный образ для узла: получение конфига, запись в volume, вызов `birdc`, отчёт о версии. | +| Сервис | Роль | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **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 по модулям. +**Инфраструктурные контейнеры:** **MySQL**, **брокер очередей** (NATS JetStream или Redis), опционально **Valkey/Redis** для кэша и rate-limit по модулям. --- @@ -94,19 +115,19 @@ flowchart TB Рекомендуемые группы таблиц: -| Область | Назначение | -| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Модули** | Тип: `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`; включён/выключен; приоритет; ссылки на расписание и (для доменов) DoH-профиль. | -| **Расписания обновления** | Базовый интервал на **модуль** (`refresh_interval_sec`, cron или interval); см. ниже про переопределение на CDN. | -| **Источники CDN внутри модуля** | Для типа `CDN_CIDRS`: несколько записей «URL/статический список» на модуль; у **каждой** записи свой опциональный `refresh_interval_sec` (если NULL — брать интервал модуля). | -| **Профили DoH** | URL HTTPS DoH (`https://…/dns-query`), опционально имя для SNI, таймауты, доверие к сертификату (политика); привязка к модулям `DOMAINS` или глобальный default. | -| **Содержимое модуля** | AS и префиксы; CDN-строки; FQDN; у каждой сущности — **привязка к community** (FK). | -| **Справочник BGP community** | Канонические записи: `standard` (65535:123), `large` (x:y:z) при необходимости, человекочитаемое имя, описание, `tenant_id`. | -| **Привязки community** | Связь «сущность → community»: для **домена**, **ASN**, **префикса/CIDR** (в т.ч. из CDN-листа) — `community_id`; при генерации BIRD маршруты/фильтры получают соответствующий `bgp_community.add()`. | -| **Пиры** | neighbor IP, ASN, пароли/ключи (лучше ссылка на секреты), BGP параметры, привязка к группе узлов. | -| **Узлы** | Идентификатор узла (hostname), роль, теги для «каким пирам/политикам подчиняться». | -| **История (append-only)** | Снимок состояния или дифф после каждого успешного применения; `revision_id`, автор (API key/user), timestamp. | -| **Журнал заданий** | Очередь «пересобрать модуль X», «откатить на revision Y», статус, ошибки. | +| Область | Назначение | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Модули** | Тип: `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`; включён/выключен; приоритет; ссылки на расписание и (для доменов) DoH-профиль. | +| **Расписания обновления** | Базовый интервал на **модуль** (`refresh_interval_sec`, cron или interval); см. ниже про переопределение на CDN. | +| **Источники CDN внутри модуля** | Для типа `CDN_CIDRS`: несколько записей «URL/статический список» на модуль; у **каждой** записи свой опциональный `refresh_interval_sec` (если NULL — брать интервал модуля). | +| **Профили DoH** | URL HTTPS DoH (`https://…/dns-query`), опционально имя для SNI, таймауты, доверие к сертификату (политика); привязка к модулям `DOMAINS` или глобальный default. | +| **Содержимое модуля** | AS и префиксы; CDN-строки; FQDN; у каждой сущности — **привязка к community** (FK). | +| **Справочник BGP community** | Канонические записи: `standard` (65535:123), `large` (x:y:z) при необходимости, человекочитаемое имя, описание, `tenant_id`. | +| **Привязки community** | Связь «сущность → community»: для **домена**, **ASN**, **префикса/CIDR** (в т.ч. из CDN-листа) — `community_id`; при генерации BIRD маршруты/фильтры получают соответствующий `bgp_community.add()`. | +| **Пиры (клиенты)** | Удалённый BGP-сосед, который **получает** анонсы с вашего BIRD: neighbor IP, remote ASN, секреты, `export`/`import` политика, теги (группы клиентов). Одна строка ≈ одна сессия к одному клиентскому роутеру. | +| **Экземпляр BIRD (`bgp_speaker`)** | Мастерский и/или репликовый BIRD: `role` (`master` / `replica`), hostname, `last_applied_revision_id`, для реплики — URL мастера / токен ноды (или вне секретов). Реплика **не** ведёт ingest; только pull бандла. | +| **История (append-only)** | Снимок состояния или дифф после каждого успешного применения; `revision_id`, автор (API key/user), timestamp. | +| **Журнал заданий** | Очередь «пересобрать модуль X», «откатить на revision Y», статус, ошибки. | ### Расписание: модуль и отдельно каждый CDN-источник @@ -134,7 +155,7 @@ flowchart TB **Откат:** не переписывать текущее состояние «вручную», а хранить **ревизии** (например JSON-снимок или нормализованные строки в history-таблицах). Операция rollback = `INSERT` новой ревизии с содержимым выбранной старой + триггер перегенерации. Так история остаётся линейной и аудируемой. -**Дополнительно:** мягкие блокировки (`SELECT ... FOR UPDATE` на уровне модуля/узла при применении), чтобы два REST-вызова не портили друг друга. +**Дополнительно:** мягкие блокировки (`SELECT ... FOR UPDATE` на уровне модуля или экземпляра `bgp_speaker` при применении), чтобы два REST-вызова не портили друг друга. ### Диаграмма ETL (от источников до BIRD и ревизий) @@ -156,7 +177,7 @@ flowchart LR ART[Артефакты BIRD] end subgraph out [Выход] - BIRD[BIRD на узлах] + BIRD[BIRD сервер анонсов] end CDN --> NORM DOH --> NORM @@ -182,6 +203,7 @@ erDiagram tenant ||--o{ module : owns tenant ||--o{ bgp_community : owns tenant ||--o{ bgp_peer : owns + tenant ||--o{ bgp_speaker : owns doh_profile ||--o{ module : uses module ||--o{ module_cdn_source : contains module ||--o{ module_domain_entry : contains @@ -189,8 +211,7 @@ erDiagram bgp_community ||--o{ module_domain_entry : tags bgp_community ||--o{ module_as_entry : tags bgp_community ||--o{ module_cdn_source : tags - bgp_node ||--o{ node_peer_binding : has - bgp_peer ||--o{ node_peer_binding : has + bgp_speaker ||--o{ bgp_peer : optional_scope module ||--o{ config_revision : produces config_revision ||--o{ revision_materialized_prefix : snapshot 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_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_peer** | Описание BGP-соседа (логический пир). | `tenant_id`, neighbor IP, remote ASN, локальные политики, **ссылка на секрет** (MD5/TC), `group_name`/`tags` для выбора на узлах. | API, render (`peers.conf`), deploy. | -| **bgp_node** | Узел сети, где крутится BIRD и агент. | `tenant_id`, `hostname`, `api_endpoint` или идентификатор для mTLS, `tags`, `last_applied_revision_id`. | deploy, API (статус), мониторинг. | -| **node_peer_binding** | Какие пиры подняты на каком узле (many-to-many). | `bgp_node_id`, `bgp_peer_id`, возможно переопределение при необходимости. | API, render (генерация только релевантных сессий на узел). | +| **bgp_peer** | Клиентский BGP-пир. | Как выше; `bgp_speaker_id`: **NULL** — включить в **бандлы всех** спикеров (зеркало); иначе только выбранный мастер/реплика. | API, render, упаковка бандла. | +| **bgp_speaker** | Экземпляр BIRD: **master** или **replica**. | `role`, endpoint для agent/deploy, `last_applied_revision_id`; для replica — учётные данные к API бандла (вне БД). | deploy, бандлы, нода. | | **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), откат. | | **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 /apply` или `POST /nodes/{id}/apply` — сгенерировать конфиг и применить (см. раздел про узлы). +- `POST /apply` или `POST /speakers/{id}/apply` (или `/bird/apply` при одном сервере) — сгенерировать конфиг и применить на **хосте BIRD-сервера**; клиентские пиры подтянут изменения после перезагрузки сессии/политики export в BIRD. - `GET /revisions`, `POST /revisions/{id}/rollback`. - `POST /peers` / `PATCH /peers/{id}` — добавление/изменение пира; опционально `POST /peers/{id}/apply`. - CRUD для **профилей DoH**, **справочника community**, **расписаний** (если вынесены из PATCH модуля) — по необходимости UI/автоматизации. +- Для реплик: `**GET /v1/speakers/{id}/bundle/{revision}`**, `**GET .../revisions/latest`**, опционально enrollment нод. Ответы — **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 хостов. -2. **Идентичность конфигурации:** таблица `node_config_version`; после деплоя агент репортит `applied_revision`. Дашборд «какой узел отстаёт». -3. **Canary / поэтапный rollout:** сначала 1–2 узла, затем остальные — снижает риск массового bad announce. -4. **Очередь и rate-limit:** массовый refresh всех CDN-модулей не должен DDOSить внешние списки; **per-module cooldown** в воркере. -5. **Наблюдаемость:** метрики (Prometheus): время генерации, размер префикс-сета, ошибки DNS/CDN, статус BIRD-сессий (через экспортер или scrape `birdc`). Алерты на **аномальный рост/падение** числа префиксов. -6. **Консистентность БД:** транзакции при записи ревизии + смене «текущего» указателя; миграции через Flyway/Liquibase или аналог. -7. **Секреты:** пароли BGP не в открытом виде в MySQL — **Vault**, Kubernetes secrets, или зашифрованные поля с KMS. -8. **Multi-tenant (если нужно):** `tenant_id` на модулях и пирах с самого начала — дешевле, чем латеральный рефакторинг. -9. **Dry-run:** `POST .../preview` возвращает diff префиксов и фрагмент BIRD без применения — обязателен для операций с 20 узлами. -10. **Резервный путь:** локальный last-known-good конфиг на узле, если центр недоступен (только чтение, без изменения политики до восстановления связи). +1. **Control-plane на мастере:** Docker с API/воркерами; **мастерский BIRD** + `evobgp-agent`. **Опционально** дополнительные хосты только с `**evobgp-node` + BIRD** (реплики), без своей БД. Клиенты — внешние пиры к мастеру и/или к репликам. +2. **Версия конфига:** `bgp_speaker.last_applied_revision_id` (или файл-маркер на хосте BIRD); агент репортит применённую ревизию. +3. **Canary:** при **нескольких** `bgp_speaker` — сначала обновить один POP; при одном сервере — canary через **группу пиров** / отдельный `export` filter для подмножества `bgp_peer`, затем полный rollout. +4. **Очередь и rate-limit:** массовый refresh CDN не должен DDOSить внешние списки; **per-module cooldown** в воркере. +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 + +**Цель:** на отдельной площадке запустить **второй (или 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. - **Миграции:** `golang-migrate` или `goose`, SQL в репозитории. - **Контейнеры:** отдельный **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 для оркестратора. +### Хранилище: 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` — **96–128 МиБ** (не дефолтные сотни МиБ). +- `max_connections` — **20–50** (достаточно для одного приложения). +- `table_open_cache` / `performance_schema` — снизить или отключить `performance_schema`, если допустимо. +- **Не** включать тяжёлые плагины; по возможности **одна** БД без реплики на этом же хосте. + +Ожидаемый RSS MySQL после тюнинга: **~250–400 МиБ** вместо 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 (после тюнинга) | **250–400 МиБ** | чуть выше при большом InnoDB workload | +| `evobgp-all` (один процесс Go) | **~30–80 МиБ** | **~80–180 МиБ** при нагрузке | +| Docker / ядро / page cache | **120–220 МиБ** | **150–250 МиБ** | + + +**Сумма ориентировочно:** **~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** (второй профиль или кратковременный отказ в смене префиксов с алертом) — по политике эксплуатации. - **Согласование «что анонсировать»** с регистрацией в RIR/IRR — отдельная дисциплина; система может лишь **не выходить за заданные в БД границы** (prefix filters). - **Community:** ошибка в справочнике или привязке ведёт к неверной маркировке трафика у апстримов; обязательны preview/diff перед apply и аудит изменений справочника. +- **Несколько BIRD с одной политикой (мастер + ноды):** возможны лишние/дублирующие анонсы в зависимости от топологии; проектировать совместно с маршрутизацией в AS. --- ## Предлагаемые этапы внедрения -1. Репозиторий Go (monorepo `cmd/` + `internal/`), MySQL-миграции, `docker compose` с MySQL и брокером. +1. Репозиторий Go (monorepo `cmd/evobgp-all` + `cmd/` + `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`, ревизии и rollback в БД. -5. Наблюдаемость, hardening контейнеров (non-root где возможно, read-only root), пилот на 2–3 узлах, затем шаблон для остальных. +4. `evobgp-deploy` + `evobgp-agent` на **мастерском** BIRD; публикация бандла; ревизии и rollback в БД. +5. Опционально `**evobgp-node`**: pull бандла, подпись, второй BIRD, пилот с клиентами к мастеру и к ноде. +6. Наблюдаемость, hardening.