An admin-controlled scenario that repeats what the operator does by hand: clear the IP queue, scan and enqueue all free Floating IPs, wait until every queued address reaches a terminal state (so results are in the Registry), then wait a configurable interval and start over. - control-api: new auto_cycle singleton table (migration 0008) holding enabled/interval/max-run settings and persisted phase state, so the cycle survives restarts; engine in orchestrator/autocycle.go driven from the existing loop tick with an injectable "now" for deterministic tests. - Interval (default 1h, min 60s) and max wait (default unlimited, timeout outcome) are runtime settings, never hardcoded. - The periodic fip_scan_interval_seconds scan is skipped while the cycle is enabled. An emptied queue mid-cycle counts as finished; stopping during the pause keeps the last cycle's outcome. - API: GET/PUT /api/v1/admin/auto-cycle, POST .../start, POST .../stop. - admin-dashboard: "Автоматический цикл" panel on /settings and an "Автоцикл активен" indicator on /overview. - Tests for db, orchestrator, httpapi and dashboard; run-local-e2e.sh now exercises a full auto cycle; docs updated; bin/ rebuilt with refreshed SHA256SUMS. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
15 KiB
Диаграммы потоков данных
Три диаграммы, поясняющие устройство системы на уровне потоков данных — дополнение к API.md (протокол) и USAGE.md (работа оператора). Диаграммы — в формате Mermaid, рендерятся нативно на GitHub/GitLab и в большинстве современных Markdown-просмотрщиков.
Презентационная версия этих же двух разрезов (control plane и data plane)
под микросервисный (docker-compose) деплой, с топологией по хостам и
профилям COMPOSE_PROFILES — CONTROL_DATA_PLANE.html
(самодостаточный HTML-файл, открыть в браузере).
Как читать диаграммы
Единое условное обозначение стрелок для всех диаграмм:
| Обозначение | Значение |
|---|---|
--> тонкая сплошная |
Управляющий вызов / API-запрос |
-.-> пунктирная |
Ответ, уведомление, асинхронный результат |
==> жирная сплошная |
Реальный сетевой трафик проверки (data-plane) — именно то, что валидируется, а не служебный вызов |
Содержание
- 1. Control plane сервиса
- 2. Поток данных: от валидатора к целевому серверу (egress-проверка)
- 3. Поток данных телеметрии
1. Control plane сервиса
Кто кем управляет и через какой канал: конфигурация оператора, HTTP API control-api, фоновый оркестратор, база данных, вызовы к OpenStack и опрос со стороны validator-agent/prober.
flowchart TB
subgraph OP["Оператор"]
CFG["control-api.yaml<br/>(bootstrap пустой БД:<br/>validators, sites, targets,<br/>check_types, ip_addresses)"]
ENV["control-api.env<br/>(OS_AUTH_URL, OS_TOKEN, ...)"]
ADMIN["curl /api/v1/admin/*<br/>(status/ips/validators,<br/>ips submit/cancel,<br/>auto-cycle start/stop,<br/>config CRUD)"]
end
subgraph CAPI["control-api (управляющая машина, 1 экземпляр)"]
HTTP["HTTP API<br/>/api/v1/agents/*<br/>/api/v1/probers/*<br/>/api/v1/admin/*<br/>/healthz"]
ORCH["Оркестратор: Tick раз в<br/>poll_interval_seconds<br/>claim → associate FIP →<br/>ожидание self-check →<br/>checking → aggregate → release<br/>+ lease sweep + heartbeat sweep<br/>+ автоцикл (если включён):<br/>очистка → скан FIP → ожидание →<br/>пауза interval_seconds"]
DB[("SQLite<br/>validators / ip_queue / sites /<br/>target_groups / check_types /<br/>checks / events / auto_cycle")]
OSCLIENT["OpenStack-клиент<br/>(mode: mock | real)"]
end
OS["OpenStack Neutron<br/>Floating IP API"]
VA["validator-agent ×N<br/>(на каждой ВМ-валидаторе)"]
PR["prober ×3<br/>(на каждой внешней площадке)"]
CFG -->|"читается только один раз,<br/>на пустых таблицах (bootstrap)"| DB
ENV -->|"переменные окружения процесса"| OSCLIENT
ADMIN --> HTTP
HTTP -->|"config/queue CRUD:<br/>источник истины после<br/>первого изменения"| DB
HTTP --> ORCH
ORCH <--> DB
ORCH --> OSCLIENT
OSCLIENT -->|"associate / disassociate<br/>floating ip"| OS
VA <-->|"register, heartbeat,<br/>GET assignment,<br/>POST self-check/events/<br/>results/complete"| HTTP
PR <-->|"register,<br/>GET assignments,<br/>POST results"| HTTP
Пояснение. control-api — единственный компонент с состоянием и
единственная точка принятия решений (какой IP кому назначить, когда
считать проверку завершённой). control-api.yaml используется только как
одноразовый bootstrap для четырёх секций (validators, sites,
targets, check_types) — читается лишь пока соответствующая таблица в
БД пуста; ip_addresses — отдельный, всегда аддитивный путь постановки в
очередь при каждом старте. После bootstrap все изменения этих сущностей,
включая состав очереди и принудительные повтор/остановку проверки, идут
через /api/v1/admin/* — «на лету», без systemctl restart control-api
(см. API.md). Оркестратор
работает по таймеру независимо от HTTP-запросов — назначение IP
валидаторам и агрегация результатов не привязаны к конкретному входящему
запросу, читая актуальную конфигурацию из БД на каждом проходе, а не
единожды при старте. Опциональный автоцикл — часть того же оркестратора:
на каждом тике он читает из таблицы auto_cycle флаг enabled, интервал и
фазу (idle/running/waiting), поэтому включение, выключение и смена
интервала действуют без перезапуска, а состояние переживает рестарт (см.
USAGE.md). validator-agent и prober — активная сторона: они
сами инициируют все HTTP-запросы к control-api (pull-модель), сам
control-api к ним не обращается.
2. Поток данных: от валидатора к целевому серверу (egress-проверка)
Как валидатор проверяет, что через назначенный ему публичный адрес реально работает исходящий доступ в интернет.
flowchart LR
subgraph VM["ВМ-валидатор (сервисный проект OpenStack)"]
AGENT["validator-agent"]
end
FIP(["Floating IP<br/>203.0.113.10<br/>(адрес под проверкой,<br/>привязан оркестратором заранее)"])
IPECHO["Внешний IP-echo сервис<br/>(api.ipify.org и т.п.,<br/>вне облака)"]
CAPI["control-api"]
subgraph TARGETS["Целевые серверы (targets из конфига)"]
T1["hub.docker.com"]
T2["github.com"]
T3["packages.ubuntu.com"]
end
AGENT ==>|"1 self-check: исходящий трафик<br/>ВМ идёт через FIP (SNAT облака)"| FIP
FIP ==>|"1 GET"| IPECHO
IPECHO -.->|"2 наблюдаемый исходящий IP"| AGENT
AGENT -->|"3 POST self-check {success}"| CAPI
AGENT ==>|"4 проверки: тоже через FIP"| FIP
FIP ==>|"4 HTTPS GET"| T1
FIP ==>|"4 HTTPS GET"| T2
FIP ==>|"4 ICMP echo"| T3
Пояснение. Шаги 1–2 — самопроверка (self-check): агент сам (без
участия control-api) обращается к внешнему IP-echo сервису и сравнивает
адрес, с которого пришёл его собственный запрос, с адресом, который ему
назначен. Ресурс для сравнения обязан быть вне облака: OpenStack
применяет SNAT через Floating IP только к трафику, уходящему через
внешнюю сеть — обращение к чему-либо внутри проекта (включая сам
control-api, если он в той же внутренней сети) показало бы приватный
адрес валидатора независимо от корректности привязки FIP. Итог
самопроверки агент затем сообщает control-api отдельным вызовом (шаг 3) —
это уже управляющий, а не проверяемый трафик. Только после успешного
self-check агент переходит к шагу 4 и выполняет проверки из
check_config (HTTPS/ICMP/опционально SSH) против целей из конфига —
тем же путём, через тот же FIP. Каждый результат отправляется обратно в
control-api сразу после выполнения (см. диаграмму телеметрии ниже) — сам
этот data-plane трафик (шаги 1 и 4) в control-api не проходит и им не
наблюдается напрямую, control-api видит только заявленный агентом
результат.
Дополнительно: обратное направление (пробер к валидатору)
Тот же Floating IP одновременно проверяется и «снаружи» — с трёх внешних площадок, независимо от исходящих проверок агента:
flowchart LR
subgraph SITES["3 внешние площадки"]
P1["prober site-1"]
P2["prober site-2"]
P3["prober site-3"]
end
FIP(["тот же Floating IP<br/>203.0.113.10"])
VM["ВМ-валидатор<br/>(слушает 22/80/443/8080)"]
P1 ==>|"TCP connect + ICMP echo"| FIP
P2 ==>|"TCP connect + ICMP echo"| FIP
P3 ==>|"TCP connect + ICMP echo"| FIP
FIP -.-> VM
Именно сочетание двух направлений (egress от валидатора и inbound от трёх площадок) и даёт полную картину: адрес может нормально работать «наружу», но быть заблокирован для конкретных внешних сетей — это увидит только inbound-проверка, и наоборот.
3. Поток данных телеметрии
Как результаты проверок и события аудита от validator-agent и трёх проберов попадают в базу данных control-api, агрегируются в итоговый результат и становятся видны оператору.
flowchart TB
subgraph SOURCES["Источники телеметрии"]
VA["validator-agent<br/>(egress-проверки + self-check)"]
P1["prober site-1"]
P2["prober site-2"]
P3["prober site-3"]
end
subgraph API["HTTP API control-api"]
EP1["POST /agents/{id}/events"]
EP2["POST /agents/{id}/self-check"]
EP3["POST /agents/{id}/results"]
EP4["POST /probers/{site_id}/results"]
end
subgraph DB["SQLite (control-api)"]
EVENTS[("events<br/>журнал аудита")]
CHECKS[("checks<br/>результаты проверок,<br/>source=egress|inbound-site-N")]
IPQ[("ip_queue<br/>state, *_complete,<br/>overall_result")]
end
AGG["Агрегация (оркестратор):<br/>по достижении всех *_complete<br/>или по таймауту checking_window_seconds"]
ADMIN["Оператор:<br/>GET /api/v1/admin/status<br/>GET /api/v1/admin/ips<br/>GET /api/v1/admin/ips/{ip}"]
VA -->|"config_received,<br/>self_check_result, ..."| EP1 --> EVENTS
VA -->|"success, detected_egress_ip"| EP2 --> EVENTS
EP2 -.->|"успех → egress_complete"| IPQ
VA -->|"check_type, target,<br/>success, latency_ms"| EP3 --> CHECKS
P1 & P2 & P3 -->|"check_type, success,<br/>latency_ms, complete"| EP4 --> CHECKS
EP4 -.->|"complete=true → siteN_complete"| IPQ
CHECKS --> AGG
IPQ --> AGG
AGG -->|"overall_result:<br/>pass / partial / fail"| IPQ
EVENTS --> ADMIN
CHECKS --> ADMIN
IPQ --> ADMIN
Пояснение. Телеметрия стекается в БД из четырёх независимых потоков
(один от агента-валидатора, три — по одному с каждой площадки),
записываясь в две таблицы: events — сырой журнал аудита (что и когда
произошло), checks — результат каждой отдельной проверки с указанием
источника (source: egress для валидатора, inbound-site-1/2/3 для
площадок). Отдельно, по мере поступления данных, выставляются флаги
завершения (egress_complete, site1/2/3_complete) в ip_queue. Как
только все ожидаемые флаги выставлены — либо истекло время ожидания
(checking_window_seconds) — фоновая агрегация суммирует все строки
checks по текущей попытке и записывает итог (pass/partial/fail)
обратно в ip_queue. Оператор в любой момент читает уже накопленные
данные через административные GET-методы, не дожидаясь завершения
проверки — подробнее о значениях полей см.
USAGE.md.
На диаграмме показан пример с тремя площадками — это лишь иллюстрация, не ограничение. Площадки опциональны и их число не ограничено: сколько их ожидать, определяется списком
sitesв конфиге control-api (пустой список — ни одного потокаinbound-site-N, агрегация ждёт толькоegress_complete; N площадок — N параллельных потоков телеметрии). См. USAGE.md.