# Работа со стендом Этот документ — для оператора, который уже развернул стенд (см. [SETUP.md](SETUP.md)) и теперь использует его в повседневной работе: добавляет адреса на проверку, следит за очередью, разбирается в результатах и реагирует на проблемы. Прямые вызовы API описаны в [API.md](API.md) — здесь мы используем их только как инструмент, не углубляясь в протокол. ## Содержание - [Как устроена работа с системой](#как-устроена-работа-с-системой) - [Добавление новых IP в очередь](#добавление-новых-ip-в-очередь) - [Наблюдение за очередью](#наблюдение-за-очередью) - [Значения полей IP](#значения-полей-ip) - [Как читать итоговый результат (pass/partial/fail)](#как-читать-итоговый-результат-passpartialfail) - [Просмотр деталей и истории по конкретному адресу](#просмотр-деталей-и-истории-по-конкретному-адресу) - [Управление валидаторами](#управление-валидаторами) - [Управление площадками (проберами)](#управление-площадками-проберами) - [Управление типами проверок пробера](#управление-типами-проверок-пробера) - [Управление целями проверки](#управление-целями-проверки) - [Повторная проверка адреса](#повторная-проверка-адреса) - [Принудительная остановка проверки](#принудительная-остановка-проверки) - [Удаление адресов из очереди](#удаление-адресов-из-очереди) - [Пауза перед self-check (fip_settle_seconds)](#пауза-перед-self-check-fip_settle_seconds) - [Частые проблемы и что с ними делать](#частые-проблемы-и-что-с-ними-делать) ## Как устроена работа с системой Оператор не взаимодействует с валидаторами и проберами напрямую — вся работа идёт через `control-api`. Цикл жизни одного IP-адреса: 1. Адрес встаёт в очередь (`queued`). 2. Control-api сам находит свободный валидатор, привязывает адрес к нему как Floating IP. 3. Валидатор проверяет, что действительно вышел в интернет именно через этот адрес (self-check), затем прогоняет исходящие проверки (HTTPS, ICMP, опционально SSH до заданных внешних целей). 4. Одновременно три внешние площадки проверяют, что этот адрес доступен *снаружи* (входящие TCP-подключения на 22/80/443/8080 и ICMP) — это ловит блокировки/чёрные списки на конкретных внешних сетях. 5. Как только все источники (валидатор + 3 площадки) отчитались — или истекло время ожидания — control-api подводит итог и освобождает адрес (отвязывает Floating IP). Всё это происходит автоматически, без участия оператора. Задача оператора — положить адреса в очередь и снять с них результат. ## Добавление новых IP в очередь Основной способ — API, без перезапуска процесса: ```bash curl -s -X POST http://:8080/api/v1/admin/ips \ -d '{"addresses": ["203.0.113.10", "203.0.113.11"]}' ``` ```json {"added": ["203.0.113.10", "203.0.113.11"], "requeued": [], "reordered": [], "skipped_in_progress": []} ``` Адреса обрабатываются в порядке, в котором перечислены в `addresses` — именно в этом порядке они и встанут в очередь друг за другом. Метод идемпотентен относительно уже идущих проверок: адрес, который сейчас активно проверяется, в ответе окажется в `skipped_in_progress` и не будет тронут (см. [«Повторная проверка адреса»](#повторная-проверка-адреса) ниже — тот же метод форсирует перепроверку уже завершённых адресов). Также по-прежнему можно добавить адреса через `ip_addresses` в `/etc/cloud-ip-validator/control-api.yaml` и перезапустить control-api — при каждом старте control-api доливает в очередь только новые адреса из этого списка (уже обработанные ранее не сбрасываются и повторно не проверяются). Держать `control-api.yaml` под версионным контролем (git) по-прежнему полезно как журнал того, что изначально ставилось в очередь при разворачивании стенда — но для повседневного добавления адресов проще и быстрее пользоваться API выше. ## Наблюдение за очередью Общая сводка: ```bash curl -s http://:8080/api/v1/admin/status | python3 -m json.tool ``` ```json { "total_ips": 25, "ips_by_state": {"queued": 10, "awaiting_self_check": 1, "checking": 3, "done": 10, "failed": 1}, "total_validators": 4 } ``` `ips_by_state` — сколько адресов в каждом состоянии прямо сейчас. Если хотите наблюдать за прогрессом в реальном времени: ```bash watch -n 2 'curl -s http://:8080/api/v1/admin/status | python3 -m json.tool' ``` Полный список всех адресов со всеми полями: ```bash curl -s http://:8080/api/v1/admin/ips | python3 -m json.tool ``` Только финальные результаты (уже готовые адреса), с помощью `jq`: ```bash curl -s http://:8080/api/v1/admin/ips \ | jq '[.[] | select(.State=="done" or .State=="failed") | {IPAddress, State, OverallResult}]' ``` ## Значения полей IP | Поле | Значение | |---|---| | `IPAddress` | Проверяемый адрес | | `Sequence` | Позиция в очереди (порядок постановки — из конфига при первом старте либо из последнего вызова `POST /api/v1/admin/ips`) | | `State` | Текущий этап: `queued`, `assigning_fip`, `awaiting_self_check`, `checking`, `aggregating`, `done`, `failed` | | `OwnerValidatorID` | Какой валидатор сейчас (или последним) занимался этим адресом | | `FIPID` | Идентификатор Floating IP в OpenStack, к которому привязан адрес (пусто, если ещё/уже не привязан) | | `AttemptNumber` | Номер попытки — растёт при каждом requeue (сбой привязки, сбой self-check, реклейм по таймауту) | | `RetryCount` | Сколько раз адрес уже переставлялся в очередь заново | | `EgressComplete` | Валидатор закончил исходящие проверки | | `Site1Complete` / `Site2Complete` / `Site3Complete` | Соответствующая площадка закончила входящие проверки | | `OverallResult` | Итог: `pass`, `partial`, `fail`, `cancelled` (принудительно остановлена, см. [«Принудительная остановка проверки»](#принудительная-остановка-проверки)), либо пусто, пока проверка не завершена | | `AssignedAt` / `AggregatedAt` / `FIPReleasedAt` | Метки времени соответствующих этапов | ## Как читать итоговый результат (pass/partial/fail) - **`pass`** — прошли все проверки: все исходящие, плюс все площадки по всем портам и ICMP — если площадки вообще настроены (`sites` в конфиге control-api может быть пустым, см. [«Управление площадками»](#управление-площадками-проберами) — тогда учитываются только исходящие). Адрес можно считать пригодным к повторной выдаче. - **`partial`** — часть проверок прошла, часть — нет (например, площадка site-2 не смогла достучаться по 8080/tcp, но остальное в порядке). Означает частичную деградацию — например, адрес заблокирован в отдельном сегменте сети/у отдельного провайдера. Требует решения оператора: годится ли адрес для данного случая использования. - **`fail`** — либо ни одна проверка не прошла, либо адрес вообще не дошёл до стадии проверок (например, self-check не подтвердился — трафик валидатора не пошёл через назначенный FIP — и попытки исчерпались). Смотрите `events` по этому адресу (см. ниже), чтобы понять, на каком шаге и почему. - **`cancelled`** — проверку остановил оператор через `POST /api/v1/admin/ips/{ip}/cancel` (`State` при этом — `failed`), а не система по итогам проверок. Отличать от обычного `fail` полезно, чтобы не путать «адрес не прошёл проверку» с «проверку прервали вручную». Отсутствие ответа от источника (площадка не прислала результат до истечения `checking_window_seconds`) засчитывается как провал — это управляется настройкой `aggregation.missing_counts_as_fail` в конфиге control-api (по умолчанию включено). ## Просмотр деталей и истории по конкретному адресу ```bash curl -s http://:8080/api/v1/admin/ips/203.0.113.10 | python3 -m json.tool ``` Ответ содержит три части: - `ip` — те же поля, что и в списке `admin/ips`, но для одного адреса; - `checks` — все отдельные проверки текущей попытки: кто проверял (`Source`: `egress` или `inbound-site-N`), что именно (`CheckType`, `Target`), результат (`Success`), задержка (`LatencyMS`), и человекочитаемая деталь (`Detail`, например текст ошибки при отказе); - `events` — журнал аудита по этому адресу в хронологическом порядке (регистрация, привязка FIP, self-check, агрегация и т.д.) — полезен, чтобы восстановить точную последовательность событий при разборе инцидента. Пример: почему адрес получил `fail`? ```bash curl -s http://:8080/api/v1/admin/ips/203.0.113.10 \ | jq '.checks[] | select(.Success==false)' ``` ## Управление валидаторами Список валидаторов и их текущее состояние: ```bash curl -s http://:8080/api/v1/admin/validators | python3 -m json.tool ``` Состояния валидатора: `unregistered` (в конфиге есть, агент ещё не подключался), `idle` (свободен, готов взять адрес), `assigned`/`checking` (занят), `unreachable` (пропустил heartbeat дольше `orchestrator.heartbeat_timeout_seconds`). **Добавление нового валидатора (без перезапуска control-api):** 1. Поднимите новую ВМ в сервисном проекте облака, узнайте её Neutron `port_id`. 2. Зарегистрируйте валидатора через API: ```bash curl -s -X POST http://:8080/api/v1/admin/config/validators \ -d '{"validator_id": "validator_05", "os_port_id": "port-abc123"}' ``` 3. Разверните и запустите `validator-agent` на новой ВМ с тем же `validator_id` в его конфиге (см. [SETUP.md](SETUP.md#развёртывание-validator-agent-на-вм-валидаторах)). Сменить `os_port_id` уже существующего валидатора (например, после пересоздания ВМ) — `PUT /api/v1/admin/config/validators/{id}` с телом `{"os_port_id": "новый-port-id"}`. Полный список зарегистрированных валидаторов — `GET /api/v1/admin/config/validators` (в отличие от `GET /api/v1/admin/validators`, отдаёт `snake_case` и без текущего IP — только конфигурационные поля). **Вывод валидатора из эксплуатации:** остановите на нём `validator-agent` (`systemctl stop validator-agent`). Он перестанет получать новые задания после того, как закончит текущее (если оно было); если он был убит посреди работы — control-api сам заберёт у него незавершённый адрес обратно в очередь по истечении `orchestrator.lease_ttl_seconds`. Удалять регистрацию валидатора не обязательно — просто выключенный агент не будет ничего забирать. Если всё же нужно убрать валидатора из системы совсем: ```bash curl -s -X DELETE http://:8080/api/v1/admin/config/validators/validator_05 ``` Возвращает `409`, если валидатор прямо сейчас владеет каким-то IP — дождитесь освобождения (или принудительно остановите его проверку, см. [«Принудительная остановка проверки»](#принудительная-остановка-проверки)) перед удалением. > Правки через `validators[]` в `control-api.yaml` тоже поддерживаются, > но только как bootstrap пустой базы данных при самом первом старте — как > только в БД есть хотя бы один валидатор, YAML для этой секции > игнорируется при всех последующих рестартах (см. > [SETUP.md](SETUP.md#развёртывание-control-api)). Для стенда, который уже > хоть раз запускался, используйте API выше. ## Управление площадками (проберами) **Входящие (inbound/prober) проверки полностью опциональны.** Список `sites` в конфиге control-api и есть переключатель: пусто — inbound- проверки выключены целиком, итоговый результат считается только по исходящим (egress) проверкам, и агрегация не ждёт вообще ни одного пробера. Указан один или два слота — ждём только их, остальные не учитываются. Указаны все три — работает как в исходной схеме процесса. Явного отдельного флага "включить/выключить" нет — самого списка `sites` достаточно. Чтобы добавить площадку (без перезапуска control-api) — назначьте `site_id` на один из трёх слотов (`index` 1, 2 или 3 — см. ограничение ниже) через API: ```bash curl -s -X PUT http://:8080/api/v1/admin/config/sites/1 \ -d '{"site_id": "site-1"}' ``` и разверните на площадке `prober` с тем же `site_id`. Чтобы отключить конкретную площадку — освободите слот: ```bash curl -s -X DELETE http://:8080/api/v1/admin/config/sites/1 ``` процесс `prober` на ней можно не останавливать (он просто перестанет получать назначения — `POST /api/v1/probers/register` для отвязанного `site_id` начнёт отвечать `400`). Текущее распределение слотов — `GET /api/v1/admin/config/sites`. > Правки через `sites[]` в `control-api.yaml` тоже поддерживаются, но > только как bootstrap пустой базы данных при самом первом старте — как > только в БД есть хотя бы одна площадка, YAML для этой секции > игнорируется при всех последующих рестартах. Для стенда, который уже > хоть раз запускался, используйте API выше. > Важно: количество *возможных* слотов площадок жёстко зашито в схему БД > (`Site1Complete`/`Site2Complete`/`Site3Complete`) — не более **трёх**, > как и описано в исходной схеме процесса. `index` может быть только 1, 2 > или 3. Использовать *меньше* трёх (в том числе ноль) — штатный, > поддерживаемый сценарий; *больше* трёх потребует доработки схемы > данных, одной правкой конфига не обойтись. ## Управление типами проверок пробера Список TCP-портов и флаг ICMP, которые `prober` проверяет на каждой настроенной площадке — единый глобальный набор, общий для всех площадок сразу (не то же самое, что список `sites` выше: `sites` решает, *сколько* точек его применяют, а этот набор — *что именно* они проверяют). Посмотреть текущий набор: ```bash curl -s http://:8080/api/v1/admin/config/inbound-checks | python3 -m json.tool ``` Изменить набор портов и/или ICMP (без перезапуска control-api — новое значение сразу видно и следующему опросу пробера, и уже идущей агрегации): ```bash curl -s -X PUT http://:8080/api/v1/admin/config/inbound-checks \ -d '{"ports": [22, 80, 443, 8080], "icmp": true}' ``` Порты должны быть в диапазоне `1..65535` и не повторяться — иначе `400`. Пустой список портов вместе с `"icmp": false` — штатный способ временно отключить inbound-проверки целиком, не трогая список площадок: ```bash curl -s -X PUT http://:8080/api/v1/admin/config/inbound-checks \ -d '{"ports": [], "icmp": false}' ``` То же самое — на странице `/settings` дашборда, второй формой рядом с паузой перед self-check (см. ниже): текстовое поле с портами через запятую и чекбокс ICMP. > Правки через `orchestrator.inbound_checks` в `control-api.yaml` тоже > поддерживаются, но только как bootstrap пустой базы данных при самом > первом старте — как только в БД есть эта настройка (а она появляется > сразу же при первом старте, значение по умолчанию — из YAML), YAML для > этой секции игнорируется при всех последующих рестартах. Для стенда, > который уже хоть раз запускался, используйте API выше. ## Управление целями проверки Набор egress-целей (`targets`) и типов проверок (`check_types`, привязывающих тип — `https`/`icmp`/`ssh` — к одной или нескольким группам целей) управляется через API так же, как валидаторы и площадки — изменения подхватываются немедленно, следующим же назначением от оркестратора, без перезапуска. Посмотреть текущий набор: ```bash curl -s http://:8080/api/v1/admin/config/targets | python3 -m json.tool curl -s http://:8080/api/v1/admin/config/check-types | python3 -m json.tool ``` Создать/заменить группу целей и включить тип проверки, ссылающийся на неё: ```bash curl -s -X PUT http://:8080/api/v1/admin/config/targets/web \ -d '{"targets": ["https://hub.docker.com", "https://github.com"]}' curl -s -X PUT http://:8080/api/v1/admin/config/check-types/ssh \ -d '{"enabled": true, "targets": ["web"]}' ``` Отключить тип проверки, не удаляя его (значения целей сохраняются): ```bash curl -s -X PUT http://:8080/api/v1/admin/config/check-types/ssh \ -d '{"enabled": false, "targets": ["web"]}' ``` Удалить группу целей можно только если на неё не ссылается ни один `check_type` (иначе — `409`); удалить сам `check_type` можно в любой момент (`DELETE /api/v1/admin/config/check-types/{name}`). **Важное следствие принятого компромисса**: если конфигурация меняется ровно в момент, когда чей-то IP уже находится в `checking` (self-check уже пройден, проверки уже назначены агенту), агрегация этой конкретной попытки посчитает уже изменённую конфигурацию, а не ту, что была на момент выдачи задания. На практике это узкое окно в несколько секунд; деградирует безопасно — через `aggregation.missing_counts_as_fail` худший исход для одной попытки — `partial` вместо `pass`, самоисправляется на следующей попытке (в том числе через принудительный повтор, см. ниже). > Правки через `targets`/`check_types` в `control-api.yaml` тоже > поддерживаются, но только как bootstrap пустой базы данных при самом > первом старте — см. примечание в разделах выше. ## Повторная проверка адреса Если адрес завершился с `failed` или `partial`, а вы хотите перепроверить его ещё раз (например, после устранения блокировки на стороне сети) — отправьте его тем же методом, что используется для постановки новых адресов в очередь: ```bash curl -s -X POST http://:8080/api/v1/admin/ips \ -d '{"addresses": ["203.0.113.10"]}' ``` ```json {"added": [], "requeued": ["203.0.113.10"], "reordered": [], "skipped_in_progress": []} ``` Адрес в `requeued` означает, что он был в терминальном состоянии (`done`/`failed`) и его перезапустили: `AttemptNumber` увеличился, `RetryCount` обнулён, предыдущий `OverallResult` сброшен, адрес снова `queued` и будет обработан на общих основаниях. Никакой особой обработки для уже проверенных адресов не требуется — тот же вызов безопасно принимает список из новых и уже проверенных адресов одновременно; единственное, что метод не сделает — не запустит вторую параллельную проверку адреса, который прямо сейчас уже проверяется (такой адрес вернётся в `skipped_in_progress`, см. [«Добавление новых IP в очередь»](#добавление-новых-ip-в-очередь)). ## Принудительная остановка проверки Если проверка адреса зависла дольше ожидаемого либо просто больше не актуальна, не дожидайтесь истечения `checking_window_seconds` — остановите её сразу: ```bash curl -s -X POST http://:8080/api/v1/admin/ips/203.0.113.10/cancel ``` Работает из любого состояния, кроме уже терминального (`done`/`failed` вернут `409` — отменять нечего). Если на момент отмены был привязан Floating IP — он отвязывается (best-effort, как и при обычном завершении проверки), владевший валидатор освобождается и снова становится `idle`. Итог записывается как `OverallResult: "cancelled"` (в `State: "failed"`), и виден в истории адреса наравне с обычными результатами: ```bash curl -s http://:8080/api/v1/admin/ips/203.0.113.10 | python3 -m json.tool ``` Чтобы позже всё же проверить этот адрес — используйте [«Повторную проверку адреса»](#повторная-проверка-адреса) выше, она одинаково работает и для отменённых, и для обычно завершённых адресов. ## Удаление адресов из очереди **Отличие от отмены (`cancel`) выше: удаление безвозвратно.** Cancel переводит адрес в `failed`/`cancelled` и сохраняет запись как историю — её видно в очереди и в деталях адреса. Delete физически стирает строку `ip_queue` и всю её историю проверок и событий: адрес полностью исчезает, восстановить его нельзя. Если нужно просто остановить зависшую проверку, но сохранить её результат в истории — используйте [«Принудительную остановку проверки»](#принудительная-остановка-проверки) выше, а не удаление. Удалить один адрес (работает из любого состояния, включая активно проверяемое — Floating IP при этом отвязывается, а владевший валидатор освобождается, точно так же, как при cancel): ```bash curl -s -X DELETE http://:8080/api/v1/admin/ips/203.0.113.10 ``` Удалить список адресов одним вызовом (неизвестные адреса просто попадают в `not_found`, не считаются ошибкой): ```bash curl -s -X POST http://:8080/api/v1/admin/ips/delete \ -d '{"addresses": ["203.0.113.10", "203.0.113.11"]}' ``` Полностью очистить очередь — **самая опасная операция**, удаляет вообще всё, включая адреса, которые прямо сейчас проверяются: ```bash curl -s -X POST http://:8080/api/v1/admin/ips/clear ``` В `admin-dashboard` то же самое доступно на странице `/ips`: чекбоксы у каждой строки + кнопка «Удалить выбранные» для точечного/массового удаления, кнопка «Удалить» в каждой строке, и отдельная кнопка «Очистить всё» — каждая с подтверждением, явно предупреждающим о необратимости (см. [DASHBOARD.md](DASHBOARD.md)). ## Пауза перед self-check (fip_settle_seconds) Как только Floating IP привязывается к валидатору, control-api по умолчанию сразу же позволяет агенту начать self-check — а data plane OpenStack может не успеть в этот же момент реально начать пропускать трафик через только что привязанный адрес, из-за чего self-check ложно проваливается по причине, не связанной с самой привязкой. Если это наблюдается на вашем стенде, задайте паузу между привязкой FIP и началом self-check: ```bash curl -s -X PUT http://:8080/api/v1/admin/config/orchestrator \ -d '{"fip_settle_seconds": 5}' ``` То же самое — на странице `/settings` дашборда. `0` (по умолчанию) — без паузы. Пока пауза не истекла, адрес уже в состоянии `awaiting_self_check`, но `GET /assignment` агенту продолжает отдавать `204` (агент просто ждёт следующего опроса, доработок на его стороне не требуется); в дашборде на `/ips` такой адрес в это время помечен бейджем «прогрев FIP» вместо обычного статуса. Значение обязано оставлять запас внутри лизинга адреса: `fip_settle_seconds + self_check_timeout_seconds` должно быть **меньше** `orchestrator.lease_ttl_seconds` — иначе пауза плюс сам self-check не влезут в лизинг, адрес не успеет пройти self-check до истечения `lease_ttl_seconds` и будет вечно возвращаться в очередь через `sweepExpiredLeases`. Попытка задать такое значение отклоняется `400`, не обрезается молча. ## Частые проблемы и что с ними делать **Валидатор долго висит в `unreachable`.** Проверьте сетевую связность ВМ-валидатора до `control-api` (порт из `server.listen_addr`) и что процесс `validator-agent` вообще запущен (`systemctl status validator-agent`, `journalctl -u validator-agent`). **Адрес не выходит из `awaiting_self_check` (статус не меняется вообще).** Если это длится всего несколько секунд и на стенде настроена [пауза перед self-check](#пауза-перед-self-check-fip_settle_seconds) (`fip_settle_seconds`) — это ожидаемое поведение, не сбой: адрес сознательно придерживается, прежде чем агенту разрешат начать проверку. Проблема — если статус не меняется значительно дольше этой паузы. Тогда дело обычно в self-check: он запрашивает внешние (вне облака) сервисы из `self_check.ip_echo_urls` в конфиге валидатора (по умолчанию `api.ipify.org`, `ifconfig.me`) — если у ВМ-валидатора нет исходящего доступа в интернет к этим адресам, запрос не проходит вообще, и агент даже не может *сообщить* результат control-api (ни успешный, ни неуспешный) — тогда статус реально зависает до истечения `orchestrator.lease_ttl_seconds`, после чего адрес возвращается в `queued` и цикл повторяется. Проверьте связность до `self_check.ip_echo_urls` прямо с ВМ-валидатора (`curl -s https://api.ipify.org`) и логи `journalctl -u validator-agent` на предмет `ip echo request failed`. **Адрес постоянно проваливает self-check (не зависает, а именно возвращается в очередь снова и снова).** Смотрите `events` по адресу (`GET /api/v1/admin/ips/{ip}`) — в детали события `self_check_result` будет указан обнаруженный исходящий адрес. Если он не совпадает с ожидаемым — вероятно, на ВМ-валидаторе есть другой маршрут наружу (не через назначенный Floating IP), либо привязка FIP на стороне OpenStack не применилась. Проверьте вручную в OpenStack (`openstack floating ip show <адрес>`), что `port_id` совпадает с портом валидатора. Обратите внимание: адрес для сравнения обязан быть **вне** облака (см. `self_check.ip_echo_urls`) — запрос к чему-либо внутри проекта (в том числе к самому control-api, если он в той же внутренней сети) покажет приватный адрес валидатора независимо от того, правильно ли привязан FIP, и всегда будет давать ложный провал. **Площадка (`site-N`) никогда не отчитывается (`SiteNComplete` всегда `false`).** Проверьте, что `prober` на этой площадке запущен и его `site_id` в конфиге совпадает с `site_id` в конфиге control-api. Проверьте, что площадка имеет сетевой доступ и до `control-api`, и до проверяемого адреса (входящий трафик на 22/80/443/8080 + ICMP — это отдельная связность от связи с control-api, см. [SETUP.md](SETUP.md#сетевые-доступы)). **Много адресов зависло в `checking` дольше ожидаемого.** Это нормально, если ещё не истёк `orchestrator.checking_window_seconds` — агрегация ждёт либо полного набора ответов, либо истечения окна. Если адрес завис заметно дольше окна — проверьте, что фоновый цикл control-api вообще работает (смотрите `journalctl -u control-api` на предмет ошибок в `sweep checking window`). **Нужно посмотреть на данные "из первых рук", в обход API.** `control-api` использует SQLite, файл — по пути `database.path` из конфига. Можно (только для чтения, на **той же машине**, где крутится control-api) открыть его `sqlite3` в режиме WAL — это безопасно для чтения параллельно с работающим процессом: ```bash sqlite3 /var/lib/cloud-ip-validator/control-api.db \ "select ip_address, state, overall_result from ip_queue order by sequence" ``` Не редактируйте эту базу вручную во время работы control-api — это может рассинхронизировать состояние с реальными привязками Floating IP в OpenStack.