# Архитектура EvoBGP Высокоуровневое описание компонентов и потоков. Детальный продуктовый и инфраструктурный чертёж также зафиксирован во внутреннем плане репозитория: `.cursor/plans/evobgp_архитектура_0e73ef02.plan.md` (удобно для истории решений; пользовательская навигация — через этот раздел и [overview.md](overview.md)). ## Назначение слоёв - **Control plane** — HTTP API, хранилище состояния (PostgreSQL), фоновые задачи (jobs), подпись артефактов (бандлы), observability. - **Data plane** — демон BIRD, локальные конфиги в `/etc/bird`, сокет управления `birdc`, агент `evobgp-agent` для наблюдения/сопутствующих действий. - **Edge интеграция** — CLI `evobgp-node` на машине спикера: получение бандла по API, проверка подписи, применение конфигурации. ## Компоненты (бинарники `cmd/`) | Бинарник | Роль | |----------|------| | `evobgp-api` | Только HTTP API и связанная логика в одном процессе. | | `evobgp-all` | Тот же API + in-process **scheduler** (очередь `module_refresh` в общем Registry), **ingest** (prefetch ETag CDN), **render** (опционально auto-publish), **deploy** (лог расхождений published/applied). | | `evobgp-scheduler` | По `refresh_interval_sec` ставит tenant refresh. Reference Compose: доверенный DB-worker, общая PostgreSQL queue (`EVOBGP_SCHEDULER_MODE=db`). Опциональный HTTP-режим ограничен явным tenant; параметры и Compose override описаны в quickstart. | | `evobgp-ingest` | Периодический conditional GET по URL CDN-источников и обновление `etag` в БД. | | `evobgp-render` | По умолчанию только heartbeat; при `EVOBGP_RENDER_AUTOPUBLISH=1` выставляет всем спикерам tenant последнюю ревизию (упрощение для демо). | | `evobgp-deploy` | Периодически логирует **drift**: `last_applied_revision_id` vs опубликованная ревизия для ноды. | | `evobgp-node` | CLI реплики: `pull-bundle`, `verify-bundle`, `apply-bundle`. | | `evobgp-agent` | Локальный агент рядом с BIRD: `watch`, **`serve`** (Panel→Node sync API на реплике). | В Docker Compose профиль **reference** запускает отдельные контейнеры под `evobgp-api` и четыре воркера; профиль **microvps** использует один контейнер `evobgp-all`. Сервис NATS вынесен в отдельный профиль `nats` и не требуется для внутренней очереди задач. ## Пакеты `internal/` (сжатая карта) | Пакет | Назначение | |-------|------------| | `httpapi` | Маршруты REST, аутентификация, CORS, привязка к store и jobs. | | `store` | Абстракция бэкенда данных; реализации в памяти и через репозиторий. | | `repository` | Доступ к PostgreSQL, сущности и миграции на уровне приложения. | | `db` | Подключение к БД и применение миграций. | | `jobs` | Durable enqueue/claim, lease/heartbeat/fencing, выполнение задач и deployment reconciliation. | | `telemetry` | Ограниченный фоновый collector legacy BGP/health и local BIRD; persistent snapshots и fenced discovery. | | `bundle` | Упаковка и проверка бандлов для нод. | | `signing` | Криптографическая проверка подписей. | | `birdfmt` | Форматирование и фрагменты конфигурации BIRD, вызовы `birdc`. | | `birddeploy` | Логика применения конфигурации к BIRD (используется в цепочке деплоя). | | `config` | Переменные окружения `EVOBGP_*`. | | `observability` | Метрики Prometheus, HTTP middleware. | | `broker` | Опциональный `EVOBGP_BROKER_URL` для будущей шины; пакет лишь логирует URL, NATS не участвует в исполнении задач. Текущий Registry дополнен PostgreSQL persistence/claim/reclaim. | | `pipeline` | Ingest+render для `module_refresh`: CDN/AS/IP/DOMAINS → `module_prefix_snapshot` (batch `COPY`, per-module lock) → агрегация CIDR O(n log n) → `CreateRenderRevision`. Fast-path снапшота — `module.input_hash`; DoH — TTL-кэш `domain_resolve_cache`; scheduler — jitter границ интервала. | | `nodedispatch` | Panel→Node HTTP wake-up (`POST /v1/agent/sync`) после `deploy_apply`. | | `agentserver` | HTTP API на реплике (`serve`): sync + health для Traefik; опционально firewall failover (`/v1/firewall/*`). | | `firewall` | Вычисление policy block/accept → плоский CIDR blocklist. | ## Удалённые спикеры Реплики на отдельных VPS: [remote-speakers.md](remote-speakers.md). CP публикует ревизию и при `EVOBGP_NODE_DISPATCH_ENABLED=1` будит agent; agent тянет signed bundle и применяет BIRD. Compose: `deploy/compose/docker-compose.remote-speaker.yaml`. ```mermaid flowchart LR subgraph clients [Clients] WebUI[Web_UI] Operator[Operator_API_client] NodeCLI[evobgp_node] end subgraph control [Control_plane] API[evobgp_api] Sched[evobgp_scheduler] Ingest[evobgp_ingest] Render[evobgp_render] Deploy[evobgp_deploy] PG[(PostgreSQL)] end subgraph data [Data_plane] BIRD[BIRD2] Agent[evobgp_agent] end WebUI --> API Operator --> API NodeCLI --> API API --> PG Sched -->|optional_single_tenant_HTTP| API Sched --> PG Ingest --> PG Render --> PG Deploy --> PG Agent --> BIRD ``` При PostgreSQL очередь использует общий `job_audit`: enqueue фиксируется до HTTP `202`, claim получает owner/lease/fencing token, а heartbeat продлевает владение. List/get/cancel/report читают общее хранилище. `Server.StartBackground` запускает durable queue и deployment reconciliation; Memory-режим сохраняет задания только в процессе. Подробности и ограничения — [durable-jobs.md](durable-jobs.md). NATS не участвует в исполнении задач. Разрыв между сохранением CRUD и автоматическим enqueue пока не закрыт transactional outbox; ошибка постановки явно сообщается клиенту. ### Фоновая статистика `Server.StartBackground` также запускает `telemetry.Collector`. Фиксированное число workers получает due targets из store с lease/fencing, опрашивает прежние endpoints агента 2.56.2 и локальный BIRD, затем сохраняет независимые BGP/health результаты. Discovery записывается в той же fenced транзакции. Истёкший владелец не может заменить snapshot или discoveries. Конфигурация speaker проверяется перед фиксацией; смена endpoint/секрета не переносит успешное наблюдение прежней конфигурации на новую. GET peers/speakers/discoveries читает сохранённое состояние; `live=1` не вызывает сетевой fan-out. POST `/v1/telemetry/refresh` объединяет запросы в пределах 10 секунд и назначает следующий сбор. `received_at` и настроенный интервал определяют свежесть, `expires_at` передаёт её срок клиенту; просроченные sessions не становятся текущим Established. При недоступности хранилища snapshots API отвечает 503. Для local BIRD несколько CP различаются стабильной authority. `EVOBGP_TELEMETRY_LOCAL_ID` должен идентифицировать именно BIRD-узел; одинаковый ID допустим только при чтении одного экземпляра BIRD. Две одновременно актуальные authority одного master дают неизвестный статус. Устаревшая authority после перезапуска не скрывает единственный свежий снимок. Настройки интервала, workers и локальной идентичности — [quickstart.md](quickstart.md). ### Снимки префиксов модуля `UpdateModulePrefixSnapshot` выполняет read-modify-write атомарно. В PostgreSQL transaction advisory lock, чтение текущих строк, замена снимка и `module.input_hash` используют одну транзакцию и одно соединение. Потеря соединения не позволяет продолжить запись через другое. GET читает заголовок и строки в repeatable-read транзакции; ошибки scan/iteration не превращаются в неполный успешный результат. Memory использует блокировку ключа tenant/module и фиксирует snapshot/hash под общей mutex. Callback преобразует переданный снимок и не вызывает store I/O. Это сериализует фиксацию снимка, но ещё не гарантирует единую версию всех входов ingest: конфигурация и input hash вычисляются до транзакции, а tenant manifest/version CAS остаётся отдельной работой B04. Контекст передаётся в операции snapshot и lookup; устаревшие вызовы других store-сущностей требуют последующего расширения. ## Диаграмма: microvps (`evobgp-all`) ```mermaid flowchart LR Client[HTTP_clients] All[evobgp_all_process] PG[(PostgreSQL)] BIRD[BIRD2] Client --> All All --> PG All --> BIRD ``` Внутри `evobgp-all` все воркеры используют **тот же** `store` и `jobs.Registry`, что и HTTP handlers, поэтому `module_refresh` выполняется в том же процессе без HTTP. Профиль Compose **`microvps-full`** добавляет к этому стеку **Web UI** (nginx → `evobgp-all`) и **Prometheus** без отдельных контейнеров воркеров (функционально то же, что отдельные `scheduler`/`ingest`/… в reference). NATS включается отдельно через профиль `nats`. Запуск и текущие лимиты — в [quickstart.md](quickstart.md); пригодность для ~1 ГиБ RAM требует замера под нагрузкой. ## Поток pipeline (ingest → render) 1. **Scheduler** (`evobgp-scheduler` / in-process в `evobgp-all`) ставит `tenant_refresh`, если `ModuleDueForScheduler`: граница окна `refresh_interval_sec` со **сдвигом `fnv32(module.ID) % interval`**, чтобы модули с одним интервалом не били внешние API одновременно. 2. **Ingest** (`RefreshModuleIngest`): - `AS_PREFIXES` — RIPEstat prefixes + holder параллельно, кэш `asn_prefix_cache`; дедуп строк по `prefix + community`. - `CDN_CIDRS` — единый `fetchCDNSourceRows` (conditional GET); prefetch уважает `RefreshIntervalSec`; merge снапшота под `LockModuleSnapshot` (пропущенные по ошибке источники сохраняют prior-строки при `EVOBGP_CDN_PARTIAL_OK`). - `DOMAINS` — DoH A+AAAA параллельно; попадания в `domain_resolve_cache` с TTL `EVOBGP_DOMAIN_CACHE_TTL_SEC` (default 300). - `IP_RANGES` — напрямую из записей модуля. 3. Снапшот пишется **batch** (`pgx.CopyFrom` в PostgreSQL). Совпадение `module.input_hash` со снапшотом — O(1) пропуск повторного ingest при render; CRUD entries обнуляет hash. 4. **Render** (`RenderTenantRevision`): `smartAggregatePrefixRows` (стек-схлопывание O(n log n), IPv6 без `math/big`) → ревизия, если набор префиксов изменился. ## Поток: ревизия и бандл для ноды 1. Оператор (роль `operator` или выше по политике) изменяет модули и запускает цепочку, приводящую к новой **ревизии** (часть шагов может быть асинхронной через jobs — см. OpenAPI). 2. Control plane формирует **подписанный бандл** для пары спикер + ревизия. 3. `evobgp-node pull-bundle` с ключом роли `node` запрашивает `GET /v1/speakers/{id}/revisions/latest` и затем `GET /v1/speakers/{id}/bundle/{revision_id}`. 4. Локально выполняется проверка подписи (публичный ключ выдаётся при старте API) и применение к BIRD (`apply-bundle`). ## Связанные документы - [quickstart.md](quickstart.md) — как поднять стек. - [api.md](api.md) — точки входа HTTP. - [access.md](access.md) — ключи и роли.