diff --git a/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md b/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md index f503b02..38ceac7 100644 --- a/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md +++ b/.cursor/plans/evobgp_архитектура_0e73ef02.plan.md @@ -1,202 +1,209 @@ --- name: EvoBGP архитектура -overview: Control-plane на мастере; BIRD на мастере и опционально на EvoBGP-нодах, стягивающих подписанный бандл (префиксы+пиры+фильтры) для одинаковых правил. Клиенты — внешние BGP-пиры. MySQL/SQLite, Go, REST, ревизии. +overview: 'Оба профиля по умолчанию на PostgreSQL (один движок, одни миграции). reference — микросервисы + PG + брокер; microVPS — evobgp-all + контейнер PG (жёсткий тюнинг). SQLite — опция только для одного контейнера без PG.' todos: - - id: schema-mysql - content: "Схема БД (MySQL prod + SQLite edge): модули, CDN, DoH, community, пиры, ревизии, jobs" + - id: schema-db + content: "Схема БД: PostgreSQL (основной); опционально SQLite (microVPS single-container); модули, ревизии, jobs" status: pending - id: bird-generator - content: Определить формат bird.conf фрагментов, фильтры и точки reload/configure + content: Формат bird.conf, include-фрагменты, фильтры, birdc configure status: pending - id: rest-jobs - content: Спецификация REST (refresh модуля, apply, preview, rollback) и async jobs + content: REST (refresh, apply, preview, rollback, bundle API) и async jobs status: pending - id: node-agent - content: evobgp-agent на мастере; опционально evobgp-node (pull бандла + BIRD на реплике) + content: evobgp-agent на мастере; evobgp-node на реплике (отдельная ВМ) status: pending - id: observability - content: Метрики, алерты на дрейф префиксов, статус пиров + content: Метрики, алерты префиксов и BGP-сессий status: pending - id: docker-ms - content: Dockerfile, compose profiles full vs edge_1g, сети/volumes, healthchecks + content: Compose profiles reference + microVPS, лимиты, логи, prune status: pending - id: go-modules - content: Структура Go-модулей, общие пакеты (db, models, bird templating) + content: Monorepo internal/*, cmd/evobgp-all и cmd/* для reference status: pending - id: replica-bundle - content: Формат бандла ревизии, подпись, API выдачи, evobgp-node pull и локальный BIRD + content: Подписанный бандл ревизии, API, evobgp-node status: pending 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 + данные** | по объёму проекта | **7–10 ГиБ** (бюджет; не весь диск ОС) | +| **ОС** | 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 flowchart TB - subgraph edge [Периметр] - LB[Traefik или Nginx] + subgraph cp [Control plane] + API[evobgp-api] + SCH[scheduler] + ING[ingest] + REN[render] + DEP[deploy] end - subgraph docker [Docker host control-plane] - API[evobgp-api Go] - 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 + DB[(PostgreSQL)] + MQ[(Broker)] API --> DB API --> MQ SCH --> DB SCH --> MQ - ING --> MQ ING --> DB - REN --> MQ + ING --> MQ REN --> DB - DEP --> MQ + REN --> MQ DEP --> DB - DEP -->|конфиг на хост BIRD| AG1 - BR1 <-->|BGP сессии| C1 - BR1 <-->|BGP сессии| C2 + DEP --> MQ + DEP --> AG[evobgp-agent] + 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-профиль. | -| **Расписания обновления** | Базовый интервал на **модуль** (`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», статус, ошибки. | +| Область | Назначение | +| ------------------------------------------------------ | ----------------------------------------------------------------------- | +| **module** | Тип `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS`, расписание, DoH, приоритет | +| **module_cdn_source** | URL/inline CDN; свой `refresh_interval` опционально | +| **doh_profile** | URL DoH, таймауты, секреты по ссылке | +| **module_domain_entry** / **module_as_entry** | FQDN или ASN/префикс + `community_id` | +| **bgp_community** | Справочник community для BIRD | +| **bgp_peer** | Клиентский пир; `speaker_id` NULL = все спикеры в бандле | +| **bgp_speaker** | master / replica, `last_applied_revision_id` | +| **config_revision** / **revision_materialized_prefix** | История, diff, откат | +| **job_audit** | Асинхронные задачи; в reference дополняет брокер | -### Расписание: модуль и отдельно каждый CDN-источник - -- **Уровень модуля:** поля вроде `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 и ревизий) +### 4.2. ETL ```mermaid flowchart LR - subgraph extract [Extract] - CDN[CDN URL и HTTP fetch] - DOH[DoH A или AAAA] - AS[AS и префиксы из БД] + subgraph ex [Extract] + CDN[CDN fetch] + DOH[DoH] + AS[AS из БД] end - subgraph transform [Transform] + subgraph tr [Transform] NORM[Нормализация CIDR] - DEDUP[Дедуп и конфликты] - COMM[Подстановка community] + DEDUP[Дедуп] + COMM[Community] end - subgraph load [Load] - MYSQL[(MySQL материализация)] - REV[revision и snapshot] - ART[Артефакты BIRD] - end - subgraph out [Выход] - BIRD[BIRD сервер анонсов] + subgraph ld [Load] + DB[(SQL БД)] + REV[revision] + ART[артефакты BIRD] end CDN --> NORM DOH --> NORM AS --> NORM - NORM --> DEDUP - DEDUP --> COMM - COMM --> MYSQL + NORM --> DEDUP --> COMM + COMM --> DB COMM --> REV REV --> ART - ART --> BIRD ``` -Пояснение: **Extract** разнесён по сервису `evobgp-ingest`; **Transform** частично в ingest, частично в `evobgp-render` (финальное объединение модулей); **Load** — транзакции в MySQL + запись файлов/объектов для деплоя. - -### Диаграмма схемы MySQL (сущности и зависимости FK) - -Схема упрощена; имена таблиц — ориентир для миграций (`golang-migrate` / `goose`). +### 4.3. ER (упрощённо) ```mermaid erDiagram @@ -211,293 +218,196 @@ erDiagram bgp_community ||--o{ module_domain_entry : tags bgp_community ||--o{ module_as_entry : 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 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, все сервисы при фильтрации. | -| **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 (содержимое). | -| **doh_profile** | Параметры DNS over HTTPS для резолва доменов. | `url` (HTTPS DoH), таймауты, ссылка на секрет (ID в Vault/K8s, не сам пароль в открытом виде), политика TLS. | Модули 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_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-пир. | Как выше; `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, операторы, ретраи. | +| Таблица | Назначение | Ключевые поля | Кто использует | +| -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| **tenant** | Multi-tenant | `id`, `name`, `slug` | API, все сервисы | +| **module** | Блок политики | `type`, `enabled`, `priority`, `doh_profile_id`, `refresh_interval_sec`, `cron_expr`, `default_community_id` | API, scheduler, ingest, render | +| **doh_profile** | DoH | `url`, таймауты, ссылка на секрет | DOMAINS, ingest | +| **module_cdn_source** | Строка CDN | `source_kind`, `url`, `etag`, `refresh_interval_sec`, `community_id` | ingest, render | +| **module_domain_entry** | FQDN | `fqdn`, `community_id`, метаданные резолва | ingest, render | +| **module_as_entry** | AS/префикс | `asn`, `prefix`, `community_id` | API, render | +| **bgp_community** | Справочник | `kind`, значения, уникальность в tenant | API, render | +| **bgp_peer** | Клиентский пир | neighbor, ASN, политики, `bgp_speaker_id` (NULL = все спикеры) | API, render, бандл | +| **bgp_speaker** | Экземпляр BIRD | `role` master/replica, endpoint, `last_applied_revision_id` | deploy, нода | +| **config_revision** | История | `hash`, артефакт, `parent_revision_id` | render, deploy, rollback | +| **revision_materialized_prefix** | Снимок префиксов | `revision_id`, `prefix`, `community_id`, `source` | preview, diff, откат | +| **job_audit** | Задачи | `kind`, `status`, `idempotency_key` | API, воркеры | -**Дополнительно (по необходимости):** +Дополнительно: `**global_settings`** (KV); `**module_cdn_fetch_log`** (опционально, TTL). -- `**global_settings`** — ключ–значение для дефолтного DoH, лимитов, feature flags; одна строка на tenant или плоская таблица. -- `**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`. +В **reference** очередь: брокер + `job_audit`; в **microVPS** — только БД и идемпотентность в `job_audit`. --- -## 2. Три типа include-модулей +## 5. Модули префиксов (AS / CDN / домены) -| Тип | Ввод | Поведение | -| ----------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- | -| **AS / префиксы** | Список AS или явные префиксы | Импорт из ваших таблиц или опционально **pdb/API RIR** (вне scope MVP — заложить интерфейс `PrefixProvider`). | -| **CDN CIDR** | URL или статический список | Периодический fetch + парсинг (plain text, JSON); хранить `etag`/`last_modified` для условных запросов. | -| **Домены** | FQDN | См. ниже: в BIRD **не** попадают строки доменов — только результат резолва. | +| Тип | Ввод | Поведение | +| ---------- | -------------- | -------------------------------------------------------- | +| **AS** | ASN / префиксы | Таблица + опционально внешний `PrefixProvider` | +| **CDN** | URL или список | Fetch, ETag, интервал модуля или строки | +| **Домены** | FQDN | В BIRD попадают **только** IP-префиксы после DoH-резолва | -### FQDN → что именно кладётся в `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 или явный приоритет модулей). +**FQDN:** воркер резолвит через **DoH-профиль** модуля → `/32` / `/128` (или политика) → материализация в БД → генерация static include для BIRD. --- -## 3. REST API: обновление «отдельного модуля» +## 6. REST API -Минимальный набор эндпоинтов: - -- `POST /modules/{id}/refresh` — пересобрать только этот модуль (CDN fetch / DNS refresh / перечитать AS-данные). -- `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}** для статуса. - -Аутентификация: API keys или mTLS для production. +- `POST /modules/{id}/refresh` +- `POST /apply`, `POST /speakers/{id}/apply` или `/bird/apply` +- `GET /revisions`, `POST /revisions/{id}/rollback` +- `POST|PATCH /peers`, CRUD DoH / community / модулей +- Реплики: `GET /v1/speakers/{id}/bundle/{revision}`, `GET .../revisions/latest`, enrollment нод +- Асинхронно: **202** + `job_id`, `GET /jobs/{id}` +- Auth: API keys / mTLS --- -## 4. Генерация BIRD и «на лету» +## 7. Генерация BIRD и ревизии -- Генерировать **фрагменты** (`/etc/bird.d/*.conf`) и один `bird.conf` с `include`. -- Статические фильтры: префиксы из БД; для экспорта — **community** из справочника по привязкам (отдельный include с `define` или динамика в `filter` по классам префиксов). -- **RPKI** (опционально позже) — отдельный блок. -- **Пиры:** отдельный include `peers.conf`; при изменении — перезапись файла и `birdc configure` (или полный reload по политике безопасности). - -Где возможно, использовать **runtime** команды BIRD для соседей; если версия/политика требует только файл — документировать один поддерживаемый путь (проще сопровождать). +- Фрагменты `bird.d/*.conf`, `include`, фильтры, `peers.conf`, community из справочника. +- Ревизия: хэш набора префиксов, артефакты, откат = новая ревизия со старым содержимым. --- -## 5. История и быстрый откат +## 8. Эксплуатация и масштаб -- Каждое успешное применение создаёт **revision** с хэшем содержимого анонсируемого набора и ссылкой на сгенерированные артефакты (опционально хранить сам `bird` snippet в BLOB для форензики). -- Откат = создание новой ревизии с данными из выбранной + тот же pipeline генерации. -- Индексы по `(module_id, created_at)` и `(revision_id)` для быстрых запросов. +- До **~20+** клиентских пиров — строки `bgp_peer`, не отдельные хосты EvoBGP. +- Rate-limit CDN, per-module cooldown. +- 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** (реплики), без своей БД. Клиенты — внешние пиры к мастеру и/или к репликам. -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 недоступен. +1. Render на мастере упаковывает **бандл** (как для мастерского BIRD) + **manifest** (SHA-256) + **подпись** (например Ed25519). +2. **evobgp-node**: fetch → проверка → распаковка → `birdc configure`. +3. Общие фильтры/префиксы **идентичны**; `local.conf` на ноде (router id, source) — вне бандла. +4. Риски: дубли анонсов, отставание ревизий, компрометация без подписи. --- -## 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); нода хранит **доверенный публичный** ключ / цепочку. -4. `**evobgp-node`** по расписанию или webhook: `GET` (или pull из S3/MinIO с тем же manifest) → проверка подписи и хэшей → атомарная распаковка в каталог BIRD → `birdc configure`. -5. Нода репортит на мастер опционально: `POST /nodes/{id}/applied` с `revision_id` (для дашборда «реплика отстаёт»). +| Движок | Роль в плане | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| **PostgreSQL** | **Основной** для `reference` и `microVPS` (различаются только ресурсы и `postgresql.conf`). | +| **SQLite** | **Опция** `microVPS_sqlite`: один контейнер без PG — только при жёстком лимите «ровно один контейнер приложения+БД в одном процессе». | +| **MySQL / MariaDB** | **Опционально** по требованию заказчика/хостинга; тот же слой DAO через второй драйвер — вне дефолтного пути. | +| **rqlite** | HA без отдельного DBA Postgres; **+контейнеры**; на 1 ГиБ тесно. | -### Одинаковые правила для клиентов на мастере и на ноде -- **Общая часть бандла** (префикс-листы, `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 общих файлов, без изменения семантики фильтров. +### Почему PG «не много» и всё же два контейнера на microVPS -### API мастера (минимум) +При **узкой** конфигурации (`shared_buffers` **64–128 МиБ**, `max_connections` **15–30**, **один** пул коннектов в `evobgp-all`, без сотен долгоживущих backend’ов) суммарный вклад PG **сопоставим** с практичным использованием SQLite в том же объёме RAM, зато: -- `GET /v1/speakers/{id}/revisions/latest` — метаданные и URL/тело бандла. -- `GET /v1/speakers/{id}/bundle/{revision_id}` — архив (например `tar.zst`) + заголовок или sidecar с подписью. -- Аутентификация ноды: **mTLS** или **Bearer** (токен выдачи при enrollment ноды). +- нет отличий DDL/типов между профилями; +- нормальные **advisory locks** / `SKIP LOCKED` для очереди `job_audit`; +- проще подключить **внешний** 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 · **7–10 ГиБ** 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 | ~50–150 МиБ | +| Образ PostgreSQL | ~80–200 МиБ (слои образа) | +| Данные PG | ~200 МиБ – 2 ГиБ (политика ревизий) | +| Конфиги / бандлы | ~10–200 МиБ | +| Логи Docker | **max-size** / **max-file** в compose | +| Prune | `docker system prune` по расписанию | +| Резерв ОС/пики | ≥1–2 ГиБ | + + +### CPU и RAM + +- Worker pool ingest/render: **1–2**; без агрессивного параллелизма на одном ядре. +- `deploy.resources`: лимиты на контейнеры **evobgp-all** и **postgres** (например PG `mem_limit` **256m–384m**, приложение **384m–512m** — подобрать по `free -m` на хосте). +- Ориентир суммарно: **~350–650 МиБ** простой, **~450–800 МиБ** пик (**PG + Go + система + BIRD**) — на **1 ГиБ** нужны swap или ещё более жёсткий `shared_buffers`. + +### Tuning PostgreSQL (microVPS) + +Пример направлений (не копировать слепо — проверить по мониторингу): + +- `shared_buffers` = **64–128 МиБ**; `max_connections` = **20–40**; `work_mem` умеренно низкий. +- Отключить или минимизировать `parallel_workers` на 1 vCPU. +- `effective_cache_size` подсказка планировщику без выделения RAM. + +### Tuning PostgreSQL (reference) + +Обычные практики под размер ВМ; **PgBouncer** при многих сервисах Go; резерв под autovacuum и пики ingest. + +--- + +## 12. Риски и этапы внедрения ### Риски -- **Дублирование анонсов** в одну и ту же сеть от двух BIRD с разным `router id` — согласовать с дизайном AS/апстримами (могут быть допустимы как anycast/резерв, могут требовать политики). -- **Расхождение ревизий:** мастер обновился, нода отстала — мониторинг `applied_revision` на ноде. -- **Компрометация бандла** без подписи недопустима — только проверенные артефакты. +- Домены и устаревшие IP; DoH down; IRR/RIR vs локальные фильтры; community-ошибки; дубли при master+node; **microVPS:** переполнение диска логами/ревизиями, OOM без swap. + +### Этапы + +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, документация операторская. --- -## Технологический стек - -- **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` — **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` выбирает количество контейнеров. - ---- - -## Риски и границы - -- **Домены в BGP:** IP меняются; без короткого TTL и мониторинга возможны утечки/дыры. Заложить политику «максимальный срок жизни записи». -- **DoH:** недоступность выбранного резолвера блокирует обновление доменного модуля; иметь **fallback** (второй профиль или кратковременный отказ в смене префиксов с алертом) — по политике эксплуатации. -- **Согласование «что анонсировать»** с регистрацией в RIR/IRR — отдельная дисциплина; система может лишь **не выходить за заданные в БД границы** (prefix filters). -- **Community:** ошибка в справочнике или привязке ведёт к неверной маркировке трафика у апстримов; обязательны preview/diff перед apply и аудит изменений справочника. -- **Несколько BIRD с одной политикой (мастер + ноды):** возможны лишние/дублирующие анонсы в зависимости от топологии; проектировать совместно с маршрутизацией в AS. - ---- - -## Предлагаемые этапы внедрения - -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` на **мастерском** BIRD; публикация бандла; ревизии и rollback в БД. -5. Опционально `**evobgp-node`**: pull бандла, подпись, второй BIRD, пилот с клиентами к мастеру и к ноде. -6. Наблюдаемость, hardening. - +**Соответствие старым именам:** `full` ≈ `reference`, `target_vps` ≈ `microVPS`. \ No newline at end of file