ayurishchevandClaude Sonnet 5.5 146259cabb Detach the floating IP when a self-check fails
A failed self-check returned the address to the queue and freed the
validator in the database, but left the floating IP attached to the
validator's port. Every later association on that port then failed with
409 ("fixed IP already has a floating IP"), so one failed self-check
poisoned a validator for good; on 2026-10-01 all 20 validators were
poisoned within 23 minutes after ifconfig.me timeouts.

SelfCheckResult now disassociates the floating IP before requeueing, and
ignores a late failed report for an address the validator no longer
holds (it could belong to another validator by then).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 03:24:19 +03:00
2026-08-29 16:05:48 +03:00
2026-08-21 07:34:45 +03:00
2026-08-21 07:34:45 +03:00
2026-08-21 07:34:45 +03:00

Cloud IP Validator

Система проверки освобождённых публичных IPv4-адресов перед их повторной выдачей. Каждый адрес временно привязывается как Floating IP к ВМ-валидатору в облаке (OpenStack) и проверяется в двух направлениях одновременно: исходящий трафик валидатора (egress: HTTPS/ICMP/опционально SSH до внешних целей) и входящая доступность самого адреса с нескольких независимых внешних площадок (inbound: TCP 22/80/443/8080 + ICMP). Итог по каждому адресу — pass/partial/fail; полная история проверок сохраняется в реестре даже после удаления адреса из очереди.

Быстрый старт

go build ./... && go test ./...            # сборка и юнит-тесты
scripts/run-local-e2e.sh                   # офлайн-прогон всей системы: mock OpenStack, без облака и интернета
cd deploy/docker
cp .env.example .env
cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml
docker compose up -d --build               # весь стенд на одной машине в mock-режиме (профили control-plane, dashboard, prober, validator)
  • UI: http://<хост>:8090/, API: http://<хост>:8080/api/v1, проверка живости: GET /healthz.
  • Docker-стенд работает с openstack.mode: mock и тестовыми адресами 203.0.113.10–12; реальный OpenStack и учётные данные не нужны.
  • Реальный стенд (systemd или Docker на нескольких хостах) — по шагам в docs/SETUP.md.

Конфигурация

Каждый компонент читает свой YAML (примеры — configs/*.example.yaml). Учётные данные OpenStack в YAML не хранятся — только имена переменных окружения.

control-api (control-api.yaml)

Ключ Назначение
server.listen_addr Адрес API (:8080)
database.path Файл SQLite (/var/lib/cloud-ip-validator/control-api.db)
openstack.mode real или mock (встроенная заглушка OpenStack для разработки и тестов)
openstack.auth_method token (готовый токен проекта из OS_TOKEN, сам не обновляется) или password (логин/пароль Keystone, токен перевыпускается автоматически)
openstack.*_env Имена переменных окружения: OS_AUTH_URL, OS_PROJECT_ID, OS_REGION_NAME, OS_INTERFACE, OS_TOKEN либо OS_USERNAME/OS_USER_DOMAIN_NAME/OS_PASSWORD
orchestrator.poll_interval_seconds Период такта оркестратора (5)
orchestrator.self_check_timeout_seconds, max_self_check_retries Ожидание self-check валидатора (60) и число его повторов (3)
orchestrator.checking_window_seconds Сколько ждать результаты проверок, прежде чем подвести итог (120)
orchestrator.lease_ttl_seconds, max_retries Лизинг адреса за валидатором (180) и число возвратов в очередь при его истечении (3)
orchestrator.heartbeat_timeout_seconds После скольких секунд тишины валидатор или площадка считаются потерянными (30)
orchestrator.fip_settle_seconds Только начальное значение: пауза между привязкой FIP и self-check; далее управляется на лету. Должно выполняться fip_settle_seconds + self_check_timeout_seconds < lease_ttl_seconds
orchestrator.fip_scan_interval_seconds Периодический скан Floating IP (0 — выключен). Не выполняется, пока включён автоматический цикл
orchestrator.fip_scan_timeout_seconds Предел всего сканирования Floating IP (1800)
openstack.list_page_size, list_page_retries, request_timeout_seconds Скан читает Floating IP страницами (200), повторяя страницу при обрыве/5xx/429 (5 раз); таймаут одного запроса к OpenStack (60 с)
auth.admin_token_env, auth.agent_token_env Имена переменных окружения с токеном администратора (CONTROL_API_ADMIN_TOKEN) и токеном агентов (CONTROL_API_AGENT_TOKEN). Значения в YAML не хранятся; пустой токен — соответствующий уровень API открыт (с предупреждением в логе)
aggregation.missing_counts_as_fail Отсутствие ответа источника засчитывается как провал (true)
validators, sites, check_types, targets, inbound_checks Начальная загрузка пустой БД: валидаторы (validator_id + os_port_id), внешние площадки, типы и цели egress-проверок, порты inbound-проверок. Дальше источник истины — БД, правки через API/UI
ip_addresses Адреса, которые доливаются в очередь при каждом старте (только новые)

Остальные компоненты

Компонент Ключи
validator-agent validator_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, self_check.* (таймаут, ip_echo_urls), checks.* (таймауты HTTPS/ICMP, число ICMP-пакетов, ssh.*)
prober site_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, checks.* (таймауты TCP/ICMP, число ICMP-пакетов)
admin-dashboard server.listen_addr (:8090), control_api.base_url, control_api.timeout_seconds, control_api.token_env (ADMIN_DASHBOARD_CONTROL_API_TOKEN), auth.username_env / password_env / session_secret_env (ADMIN_DASHBOARD_USERNAME / _PASSWORD / _SESSION_SECRET), auth.session_ttl_minutes (480), overview.last_completed_count (20), overview.poll_interval_seconds (5)

Секреты (токены, пароль дашборда, ключ сессии) задаются только переменными окружения; генерация — openssl rand -hex 32. Подробности и порядок включения — docs/SETUP.md.

Настройки, меняющиеся на лету (пауза перед self-check, глубина истории, типы inbound-проверок, автоматический цикл, валидаторы, площадки, цели), хранятся в БД и правятся через API или страницу /settings.

Архитектура

Слой Технологии
Backend Go 1.26, net/http (маршруты Go 1.22), SQLite (чистый Go-драйвер modernc.org/sqlite, WAL, одно соединение), встроенные миграции 0001–0008
Облако Интерфейс FloatingIPClient: реальный клиент OpenStack Neutron и MockClient
UI Серверный рендеринг (Go-шаблоны) + htmx и Alpine.js, шрифты и скрипты лежат в репозитории, внешних зависимостей нет
Сборка Статические бинарники (CGO_ENABLED=0) в bin/, Docker-образы, systemd-юниты
Компонент Роль Состояние Где работает
control-api Ведёт очередь адресов и реестр их истории, назначает валидаторов, агрегирует результаты. Единственная точка, с которой общаются все остальные компоненты Хранит (SQLite) Одна управляющая машина
validator-agent Привязывает к себе выданный Floating IP и прогоняет egress-проверки до внешних целей Без состояния Каждая ВМ-валидатор в облаке
prober Проверяет входящую доступность адреса снаружи (TCP/ICMP) — с независимой от облака сети Без состояния Каждая внешняя тестовая площадка
admin-dashboard Браузерная админ-панель — то же, что API control-api, но графически. Опционален Без состояния Любая машина с доступом к control-api

Все четыре общаются только через HTTP API control-api. Pull-модель: validator-agent и prober сами опрашивают control-api, он к ним не обращается. Схемы control/data plane — docs/DIAGRAMS.md.

cmd/            control-api  validator-agent  prober  admin-dashboard
internal/       config  db  orchestrator  httpapi  openstack  dashboard  agentcore  probercore  checkrunner  apiclient
  db/migrations/  схема SQLite (0001–0008)
configs/        примеры конфигураций компонентов
deploy/         docker/ (compose, Dockerfile, RUN.txt)   systemd/ (юниты)
rxprod-compose/ compose реального стенда (control-api на :8081, дашборд на :8091)
scripts/        run-local-e2e.sh   httpstub/ (заглушки целей для e2e)
bin/            готовые бинарники linux/amd64 и SHA256SUMS
docs/           документация и планы доработок

Модель данных и правила

ip_queue (текущая очередь) · ip_registry (все адреса, когда-либо бывшие в очереди) · checks (результаты по циклам) · events (журнал) · validators · sites · target_groups · check_types · settings · inbound_checks_settings · auto_cycle.

Очередь и состояния

  • Путь адреса: queued → assigning_fip → awaiting_self_check → checking → aggregating → done или failed. Все переходы, кроме команд оператора, выполняет оркестратор сам по таймеру.
  • Терминальные состояния: done, failed, occupied. occupied — Floating IP к моменту привязки уже занят чужим портом: цикл проверки не стартует, overall_result пуст, это не fail.
  • Оператор может отменить проверку (failed + cancelled), перепроверить завершённый адрес (возврат в queued) или удалить адрес из очереди.
  • Адреса обрабатываются в порядке sequence; каждый свободный валидатор получает следующий queued-адрес под лизинг. Истёкший лизинг возвращает адрес в очередь, после max_retries — в failed.

Итоговый результат

  • pass — прошли все проверки: исходящие и все настроенные площадки по всем портам и ICMP. partial — часть прошла, часть нет. fail — не прошла ни одна проверка или адрес не дошёл до проверок (self-check не подтвердился). cancelled — остановлено оператором.
  • Итог подводится, когда отчитались валидатор и все настроенные площадки либо истекло checking_window_seconds. Молчание источника — провал (aggregation.missing_counts_as_fail).
  • Площадки опциональны: с пустым списком sites итог строится только по egress-проверкам.

Реестр

  • Запись реестра создаётся при первой постановке адреса и не удаляется: она переживает удаление из очереди и повторное добавление.
  • История проверок хранится по циклам; history_retention_cycles ограничивает глубину (0 — без ограничения), сама запись реестра остаётся.

Автоматический цикл (опционально, по умолчанию выключен)

  • Один цикл: очистить очередь → просканировать Floating IP (фаза scanning, фоновое задание) → дождаться, пока все адреса станут терминальными → пауза interval_seconds → заново. Пауза считается от завершения цикла. Сканирование не блокирует оркестратор; чужое идущее сканирование цикл дожидается, а не присоединяется к нему.
  • interval_seconds — по умолчанию 3600, минимум 60; max_run_seconds — максимальное ожидание проверок (0 — без лимита), по истечении исход timeout.
  • Исходы цикла: completed, no_free_ips, timeout, error, stopped. Опустевшая очередь посреди цикла считается завершением.
  • Состояние хранится в БД и переживает перезапуск; выключение не прерывает идущие проверки. Подробности — docs/USAGE.md.

Удаление и сканирование

  • Удаление (точечное, списком, «очистить всё») убирает строку очереди, но не историю в реестре.
  • Скан Floating IP ставит в очередь только свободные адреса (не привязанные ни к одному порту); уже идущие проверки не трогаются. Он идёт в фоне и читает облако страницами: подходит и для тысяч адресов (на стенде 6441 Floating IP читаются ≈ 1,5–2 мин). Адреса ставятся в очередь только после полного обнаружения (кусками по 500, по возрастанию IP); при сбое чтения очередь не меняется. dry_run=true / «Пробное сканирование» считает адреса, не меняя очередь.
  • Пропускная способность: ≈ 50 с на адрес на валидатор — очередь из 6440 адресов это ≈ 18 ч на 5 валидаторах, ≈ 9 ч на 10 (рычаги: число валидаторов и fip_settle_seconds).

API (/api/v1)

Область Эндпоинты
Валидатор POST /agents/register, POST /agents/{id}/heartbeat, GET /agents/{id}/assignment, POST /agents/{id}/self-check|events|results|complete
Пробер POST /probers/register, POST /probers/{site_id}/heartbeat, GET /probers/{site_id}/assignments, POST /probers/{site_id}/results
Очередь GET /admin/status, GET|POST /admin/ips (limit/offset/state/q/result/order — постранично), GET /admin/ips/{ip}, POST /admin/ips/{ip}/cancel, DELETE /admin/ips/{ip}, POST /admin/ips/delete|clear, POST|GET /admin/ips/scan (фоновый скан: 202, dry_run, wait; статус и прогресс)
Реестр GET /admin/registry (limit/offset/q/last_result — постранично), GET /admin/registry/{ip}
Автоцикл GET|PUT /admin/auto-cycle, POST /admin/auto-cycle/start|stop
Конфигурация /admin/config/validators, /sites, /targets, /check-types, GET|PUT /admin/config/orchestrator, GET|PUT /admin/config/inbound-checks
Служебное GET /admin/validators, GET /healthz

Соглашения

  • Тело запросов и ответов — JSON. Успех — 200, 204 — когда данных нет (например, у валидатора нет назначения).
  • Ошибки — 4xx/5xx с телом {"error": "..."}; нет или неверен токен — 401 (с WWW-Authenticate: Bearer), неизвестная сущность — 404, конфликт состояния — 409, неверные данные — 400, ошибка OpenStack при скане — 502.
  • Токен передаётся заголовком Authorization: Bearer <токен> (docs/API.md).
  • Времена — RFC 3339. Полная спецификация и примеры curl — docs/API.md.

Безопасность

  • Доступ к API — два статических Bearer-токена (без срока жизни, из переменных окружения, сравнение в константное время):
    Уровень Токен Методы
    admin CONTROL_API_ADMIN_TOKEN все /api/v1/admin/*
    agent CONTROL_API_AGENT_TOKEN запись результатов валидатора и пробера: self-check, events, results, complete
    открыто — GET /healthz, register, heartbeat и получение задания (GET assignment / assignments)
    Токены разные: административный не открывает методы агентов, и наоборот. Валидатор и пробер получают настройку и задание без токена, но не могут отправить результат без токена агентов.
  • Пустой токен — уровень открыт (обратная совместимость): control-api стартует с предупреждением в логе. На реальном стенде задайте оба токена и ограничьте доступ на уровне сети (docs/SETUP.md); токены идут открытым текстом без TLS — публикуйте через reverse-proxy с TLS. Включать токен агентов нужно после его раздачи валидаторам и проберам (порядок).
  • Дашборд закрыт логином и паролем (один администратор; пароль и ключ сессии — из env). Сессия — подписанная cookie (HttpOnly, SameSite=Strict, без состояния на сервере), CSRF-защита по Origin, 5 неудачных входов за 10 минут с одного IP → 429. Без заданных логина/пароля дашборд открыт (с предупреждением в логе). Дашборд ходит в API с токеном администратора. Подробности — docs/DASHBOARD.md.
  • Учётные данные OpenStack передаются только через переменные окружения процесса (EnvironmentFile= в systemd, OS_* в Docker) и не попадают в YAML; режим password перевыпускает токен сам, режим token — нет.
  • Компоненты работают по pull-модели: на валидаторах и площадках не нужно открывать входящие порты для control-api.
  • Бинарники статические, без cgo; целостность проверяется sha256sum -c bin/SHA256SUMS.

Публикация и эксплуатация

  • Бинарники. Готовые linux/amd64 лежат в bin/ и не обновляются автоматически: после правок кода пересоберите их и обновите SHA256SUMS (команды — docs/SETUP.md); Dockerfile копируют именно bin/*.
  • systemd. Юниты в deploy/systemd/; у control-api — EnvironmentFile с учётными данными OpenStack.
  • Docker. deploy/docker/docker-compose.yml + docker-compose.override.yml (dev, mock) или docker-compose.prod.yml (без публикации портов, restart: unless-stopped). Какие сервисы поднимаются на хосте, задаёт COMPOSE_PROFILES: control-plane, dashboard, prober, validator. БД — volume cloud-ip-validator-db.
  • Реальный стенд. rxprod-compose/ — compose с готовыми образами, собственным control-api.yaml и каталогом БД capi-db/; .env с учётными данными в репозиторий не входит.
  • Миграции применяются при старте control-api; версия схемы — PRAGMA user_version. Начальная загрузка (validators, sites, targets, check_types, inbound_checks) выполняется только в пустые таблицы.
  • Остановка (SIGTERM) корректно завершает HTTP-сервер и фоновые циклы. Состояние автоцикла и очереди сохраняется в БД.

Интерфейс

Страницы admin-dashboard (подробно — docs/DASHBOARD.md):

Страница Назначение
/overview Счётчики и прогресс («Готово D из T», оценка времени), «в работе», «в очереди: Q», «последние завершённые», поиск по IP и фильтр по статусу, индикатор скана и автоцикла; работает на счётчиках и ограниченных списках, поэтому быстрый и при тысячах адресов
/ips, /ips/{ip} Очередь постранично с поиском и фильтром на сервере: добавление адресов, «Сканировать Floating IP» (панель прогресса) и «Пробное сканирование», перепроверка, отмена, удаление (страница или «все N по фильтру», «Очистить всё»); детали и события адреса
/registry, /registry/{ip} Реестр всех адресов (постранично) и полная история проверок адреса; поиск, фильтр и страница сохраняются в адресной строке
/validators, /sites, /targets, /check-types Управление валидаторами, внешними площадками, группами целей и типами проверок
/settings Панель «Автоматический цикл», пауза перед self-check, глубина истории, TCP-порты и ICMP для inbound-проверок
  • Порядок блоков на /overview фиксирован: статистика → фильтр → таблицы; поллится только блок таблиц, поэтому набранный в фильтре текст не сбрасывается.
  • Ошибки control-api показываются баннером; при недоступном API страница остаётся рабочей.
  • Тёмная и светлая темы, переключатель в шапке.

Тесты

go build ./... && go vet ./... && go test ./...      # юнит-тесты: db, orchestrator, httpapi, dashboard, agentcore, probercore, checkrunner, openstack
go test -race ./internal/orchestrator ./internal/httpapi ./internal/dashboard ./internal/db
scripts/run-local-e2e.sh                             # сквозной прогон: lease-reclaim, перепроверка, автоматический цикл

Юнит-тесты используют временную SQLite и MockClient, внешних ресурсов не требуют. E2E поднимает все компоненты локальными процессами с включёнными токенами (проверяет 401/открытые маршруты и работу агента и пробера с токеном) и завершается ненулевым кодом при провале проверок автоцикла — docs/LOCAL_E2E.md.

Документация

Документ Для чего
docs/SETUP.md Развёртывание с нуля: бинарники или сборка из исходников, конфигурация, systemd и Docker/docker-compose — пошагово
docs/USAGE.md Повседневная работа: очередь, сканирование Floating IP, автоматический цикл, статус, реестр и история, разбор результатов
docs/API.md Спецификация HTTP API control-api и примеры запросов (curl)
docs/DASHBOARD.md Устройство admin-dashboard: страницы, поиск и фильтр, обработка ошибок
docs/DIAGRAMS.md Диаграммы потоков данных: control plane, egress-проверка, телеметрия
docs/LOCAL_E2E.md Полностью офлайн-прогон всей системы одним скриптом
docs/changes/ Планы доработок и отчёты ревью с отметкой времени в имени файла (последняя: скан при тысячах адресов)
docs/CONTROL_DATA_PLANE.html Презентационные схемы control/data plane для docker-compose-деплоя — открыть в браузере

История изменений

Дизайн крупных доработок зафиксирован в планах docs/PLAN_*.md; остальное — в истории git (git log).

Изменение Документ
Веб-панель администратора admin-dashboard план · описание
Динамическое управление конфигурацией и очередью через API план · API
Удаление адресов: точечное, массовое, «очистить всё» план
Пауза перед self-check после привязки Floating IP (fip_settle_seconds) план · USAGE
Механизм аутентификации OpenStack-клиента (token / password) план
Дата Веха Документ
2026-10-01 Скан Floating IP при тысячах адресов: фоновый постраничный скан с прогрессом, фаза scanning в автоцикле, постраничные /ips и /registry, «Обзор» на счётчиках план · ревью и тесты · USAGE · API
2026-10-01 Аутентификация: токены администратора и агентов для API, логин и пароль для дашборда план · ревью и тесты · API
2026-10-01 Автоматический цикл проверок по сценарию: очистка → скан FIP → проверка → пауза USAGE · API
2026-09-23 Сканирование Floating IP и устойчивый реестр адресов с настраиваемой глубиной истории USAGE
2026-09-23 Поиск по IP и фильтр по статусу на «Обзоре» и «Реестре» DASHBOARD
2026-09-23 Пошаговое руководство по развёртыванию в Docker SETUP
2026-09-18 Массовая перепроверка в дашборде; compose реального стенда rxprod-compose DASHBOARD
2026-09-13 Состояние occupied: пропуск цикла для уже занятого Floating IP; схемы control/data plane DIAGRAMS
2026-08-26 Управление внешними площадками и heartbeat пробера, новый UI дашборда, переключатель темы DASHBOARD
S
Description
Сервис для проверки публичных IPv4 адресов
Readme
158 MiB
0 Stars 1 Watchers 0 Forks
Languages
Go 87.9%
HTML 7.2%
CSS 2.8%
Shell 1.8%
Dockerfile 0.2%
Other 0.1%