repo init

This commit is contained in:
ayurishchev committed 2026-08-21 07:34:45 +03:00
commit 7e44db87b2
48 files changed
+5346

No files matched your search

+376
View File
@@ -0,0 +1,376 @@
# API Control API
Control API — единственная точка входа в систему для `validator-agent`,
`prober` и оператора (администратора). Все данные передаются в формате
JSON, базовый префикс прикладных методов — `/api/v1`.
> **Важно.** На данный момент API не защищён аутентификацией/авторизацией
> — эндпоинты доступны любому, кто может достучаться до порта control-api
> по сети. Для эксплуатации за пределами доверенного сегмента сети
> обязательно ограничьте доступ на уровне сети/файрвола (см.
> [SETUP.md](SETUP.md#сетевые-доступы)). Добавление bearer-токена — известное
> направление доработки, в текущей версии не реализовано.
Базовый URL в примерах — `http://control-api.internal:8080`, замените на
адрес вашего стенда (см. `server.listen_addr` в конфиге control-api).
## Содержание
- [Общие соглашения](#общие-соглашения)
- [Методы для validator-agent](#методы-для-validator-agent)
- [Методы для prober](#методы-для-prober)
- [Служебные и административные методы](#служебные-и-административные-методы)
- [Модель состояний и связь методов с ней](#модель-состояний-и-связь-методов-с-ней)
- [Сквозной пример работы (curl)](#сквозной-пример-работы-curl)
## Общие соглашения
- Тело запроса и ответа — JSON (`Content-Type: application/json`).
- Успешные ответы возвращают `200 OK`, либо `204 No Content` (когда
данных нет — например, у валидатора сейчас нет назначения).
- Ошибки возвращают `4xx`/`5xx` и тело вида:
```json
{"error": "текст ошибки"}
```
- Временные метки (`checked_at` в запросах) передаются в формате
RFC3339/RFC3339Nano, например `2026-08-21T09:15:00.123456789Z`. Если поле
не удалось распарсить, сервер молча подставит текущее время сервера — не
полагайтесь на это в продакшене, всегда передавайте валидную метку.
- `validator_id` и `site_id` в пути запроса должны совпадать со
значениями, заданными в конфиге control-api (`validators[].validator_id`,
`sites[].site_id`) — иначе методы, требующие существующую сущность,
вернут `404`.
## Методы для validator-agent
Эти методы вызывает бинарник `validator-agent`, работающий на ВМ-валидаторе.
Оператору вручную дёргать их обычно не требуется — они приведены для
понимания протокола и для отладки через curl.
### `POST /api/v1/agents/register`
Регистрация/переактивация валидатора. Вызывается один раз при старте
агента (и безопасно при каждом рестарте — идемпотентна).
Запрос:
```json
{
"validator_id": "validator_01",
"hostname": "vm-validator-01",
"agent_version": "1.0.0"
}
```
Ответ:
```json
{"ok": true, "poll_interval_seconds": 5}
```
`poll_interval_seconds` — рекомендованный интервал опроса, значение берётся
из `orchestrator.poll_interval_seconds` конфига control-api.
> `validator_id` должен быть заранее описан в конфиге control-api
> (`validators[].validator_id`) вместе с `os_port_id` — сам агент порт ID
> не передаёт и не может его сменить через API.
### `POST /api/v1/agents/{id}/heartbeat`
"Я жив". Обновляет `last_heartbeat_at` валидатора. Если валидатор не
присылает heartbeat дольше `orchestrator.heartbeat_timeout_seconds`, он
помечается `unreachable`.
Запрос (тело необязательно, поля информационные):
```json
{"local_state": "idle"}
```
Ответ: `{"ok": true}`. `404`, если `validator_id` не зарегистрирован.
### `GET /api/v1/agents/{id}/assignment`
Есть ли у валидатора сейчас работа. Опрашивается в каждом цикле.
- `204 No Content` — заданий нет.
- `200 OK` с телом:
```json
{
"ip_id": 42,
"ip_address": "203.0.113.10",
"phase": "awaiting_self_check",
"check_config": [
{"type": "https", "targets": ["https://hub.docker.com", "https://github.com", "https://packages.ubuntu.com"]},
{"type": "icmp", "targets": ["https://hub.docker.com", "https://github.com", "https://packages.ubuntu.com"]}
]
}
```
`phase` — `awaiting_self_check` (нужно выполнить self-check) либо
`checking` (self-check уже пройден, можно/нужно выполнять проверки).
`check_config` — уже развёрнутая конфигурация проверок (тип + список
целей), агенту не нужно самому сопоставлять группы целей.
### `POST /api/v1/agents/{id}/self-check`
Отчёт о результате self-check — подтверждение, что исходящий трафик
валидатора действительно идёт через только что назначенный FIP. Агент
определяет это, вызвав `GET /api/v1/whatsmyip` и сравнив ответ с
`ip_address` из задания.
Запрос:
```json
{
"ip_id": 42,
"detected_egress_ip": "203.0.113.10",
"success": true,
"detail": "matched"
}
```
Ответ: `{"ok": true}`. При `success: false` control-api сам решает —
повторить попытку назначения FIP или пометить IP как `failed` (после
исчерпания `orchestrator.max_self_check_retries`).
### `POST /api/v1/agents/{id}/events`
Произвольная запись в журнал аудита, привязанная (опционально) к IP.
Используется агентом для событий `config_received`, `self_check_result`
и т.п.
Запрос:
```json
{
"event_type": "config_received",
"ip_id": 42,
"payload": "{\"checks\":2}"
}
```
Ответ: `{"ok": true}`.
### `POST /api/v1/agents/{id}/results`
Отчёт о результатах исходящих (egress) проверок. Можно отправлять по
одной проверке сразу после выполнения (рекомендуется — так прогресс не
теряется при падении агента) либо пачкой.
Запрос:
```json
{
"results": [
{
"ip_id": 42,
"check_type": "https",
"target": "https://github.com",
"success": true,
"latency_ms": 87,
"detail": "ok",
"checked_at": "2026-08-21T09:15:00.123Z"
}
]
}
```
Ответ: `{"ok": true}`. Повторная отправка того же `(ip_id, check_type,
target)` в рамках текущей попытки — безопасна и просто перезапишет
результат (upsert по уникальному ключу).
### `POST /api/v1/agents/{id}/complete`
Сигнал "все исходящие проверки для этого IP выполнены".
Запрос:
```json
{"ip_id": 42}
```
Ответ: `{"ok": true}`.
## Методы для prober
Эти методы вызывает бинарник `prober`, работающий на внешней площадке.
### `POST /api/v1/probers/register`
Регистрация пробера. `site_id` должен присутствовать в конфиге control-api
(`sites[].site_id`), иначе — `400`.
Запрос:
```json
{"site_id": "site-1", "hostname": "probe-host-1"}
```
Ответ: `{"ok": true, "poll_interval_seconds": 5}`.
### `GET /api/v1/probers/{site_id}/assignments`
Список всех IP, которые сейчас находятся в состоянии `checking` — то есть
всё, что нужно прозондировать на этом цикле опроса (валидаторов может
работать несколько параллельно, поэтому список, а не один IP).
Ответ:
```json
[
{"ip_id": 42, "ip_address": "203.0.113.10", "ports": [22, 80, 443, 8080], "icmp": true}
]
```
Пустой список `[]`, если сейчас нечего проверять.
### `POST /api/v1/probers/{site_id}/results`
Отчёт о результатах входящих (inbound) проверок с данной площадки.
Запрос:
```json
{
"results": [
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-22", "success": true, "latency_ms": 12, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-80", "success": true, "latency_ms": 9, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-443", "success": true, "latency_ms": 10, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-8080","success": false,"latency_ms": 0, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "icmp", "success": true, "latency_ms": 5, "checked_at": "2026-08-21T09:15:01Z", "complete": true}
]
}
```
`complete: true` нужно проставить ровно на одном (обычно последнем)
результате в пачке — это сигнал "площадка N закончила зондирование этого
IP на данном проходе". До этого момента control-api не будет считать
данные с этой площадки завершёнными.
Ответ: `{"ok": true}`.
## Служебные и административные методы
### `GET /healthz`
Проверка живости процесса. Ответ: `{"ok": true}`. Используется в systemd/
внешних системах мониторинга.
### `GET /api/v1/whatsmyip`
Возвращает IP-адрес, с которого пришёл TCP-запрос (без учёта заголовков
`X-Forwarded-For` — специально, чтобы self-check нельзя было подделать).
Это основа механизма self-check.
Ответ:
```json
{"ip": "203.0.113.10"}
```
### `GET /api/v1/admin/status`
Сводка по очереди — сколько IP в каком состоянии.
```json
{
"total_ips": 25,
"ips_by_state": {"queued": 10, "checking": 3, "done": 11, "failed": 1},
"total_validators": 4
}
```
### `GET /api/v1/admin/ips`
Полный список всех IP из очереди со всеми полями (см.
[USAGE.md](USAGE.md#значения-полей-ip) — расшифровка полей и статусов).
### `GET /api/v1/admin/ips/{ip}`
Детали по одному адресу: сам объект IP, все проверки текущей попытки и
вся история событий по нему.
```json
{
"ip": { "ID": 42, "IPAddress": "203.0.113.10", "State": "done", "OverallResult": "pass", "...": "..." },
"checks": [ {"Source": "egress", "CheckType": "https", "Target": "https://github.com", "Success": true, "...": "..."} ],
"events": [ {"EventType": "fip_associated", "OccurredAt": "...", "...": "..."} ]
}
```
> Обратите внимание: вложенные объекты `ip`/`checks`/`events` сериализуются
> без переопределения имён полей (используются имена Go-структур, например
> `IPAddress`, `State`, `Success`) — в отличие от методов для
> agent/prober, где поля в `snake_case`. Это осознанная асимметрия:
> административные методы — для человека/дашборда, а не для машинного
> протокола.
### `GET /api/v1/admin/validators`
Список всех валидаторов с их текущим состоянием (`unregistered`, `idle`,
`assigned`, `checking`, `unreachable`) и `CurrentIPID`, если валидатор
сейчас занят.
## Модель состояний и связь методов с ней
```
queued ──(control-api сам, без вызова API)──▶ assigning_fip
│
OpenStack FIP associate успешен
▼
awaiting_self_check
│
POST .../self-check {success:true}
▼
checking
│ POST .../results (agent) │ POST .../results (prober, x3 площадки)
▼ ▼
egress_complete=true siteN_complete=true (N=1,2,3)
│
все complete=true ИЛИ истекло checking_window_seconds
▼
aggregating
│
done (pass/partial/fail)
или failed
```
Переходы `queued → assigning_fip → awaiting_self_check` и финальная
агрегация выполняются control-api самостоятельно по таймеру (см.
`orchestrator.poll_interval_seconds`), явного HTTP-метода для их запуска
нет — это фоновый цикл (`Tick`), а не запрос/ответ.
## Сквозной пример работы (curl)
Ниже — минимальный ручной прогон одного IP через API, как если бы вы
писали собственного клиента вместо `validator-agent`/`prober`. Полезно
для отладки и для понимания протокола.
```bash
BASE=http://127.0.0.1:8080
# 1. Регистрация валидатора (validator_01 уже должен быть в конфиге control-api)
curl -s -X POST "$BASE/api/v1/agents/register" \
-d '{"validator_id":"validator_01","hostname":"debug-host","agent_version":"manual"}'
# 2. Подождать, пока control-api (фоновым тиком) назначит IP и привяжет FIP —
# проверяем через admin/status или admin/ips, либо просто опрашиваем assignment
curl -s "$BASE/api/v1/agents/validator_01/assignment"
# => {"ip_id":1,"ip_address":"203.0.113.10","phase":"awaiting_self_check","check_config":[...]}
# 3. Self-check: спросить у control-api, каким адресом мы к нему пришли
curl -s "$BASE/api/v1/whatsmyip"
# => {"ip":"203.0.113.10"} (в реальном стенде это и есть проверка через FIP)
curl -s -X POST "$BASE/api/v1/agents/validator_01/self-check" \
-d '{"ip_id":1,"detected_egress_ip":"203.0.113.10","success":true,"detail":"matched"}'
# 4. Отправить результаты egress-проверок (по одному check_config пункту)
curl -s -X POST "$BASE/api/v1/agents/validator_01/results" \
-d '{"results":[{"ip_id":1,"check_type":"https","target":"https://github.com","success":true,"latency_ms":80,"checked_at":"2026-08-21T09:00:00Z"}]}'
# 5. Сообщить, что все egress-проверки выполнены
curl -s -X POST "$BASE/api/v1/agents/validator_01/complete" -d '{"ip_id":1}'
# 6. Со стороны пробера: узнать, что сейчас проверяется, и отправить результат
curl -s -X POST "$BASE/api/v1/probers/register" -d '{"site_id":"site-1"}'
curl -s "$BASE/api/v1/probers/site-1/assignments"
curl -s -X POST "$BASE/api/v1/probers/site-1/results" \
-d '{"results":[{"ip_id":1,"ip_address":"203.0.113.10","check_type":"icmp","success":true,"latency_ms":5,"checked_at":"2026-08-21T09:00:01Z","complete":true}]}'
# 7. Проверить итоговый результат (после того как control-api агрегирует)
curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
```
Для полностью автоматизированного локального прогона (без ручных curl)
см. `scripts/run-local-e2e.sh` и [docs/LOCAL_E2E.md](LOCAL_E2E.md).
+92
View File
@@ -0,0 +1,92 @@
# Local end-to-end smoke test
`scripts/run-local-e2e.sh` runs the full system as local processes with no
real OpenStack cloud and no real internet access:
- **control-api** with `openstack.mode: mock` — the in-memory
`openstack.MockClient` stands in for Neutron, pre-seeded with one
synthetic floating IP per configured address (see
`cmd/control-api/main.go`'s `newOpenStackClient`).
- **1 validator-agent** (`validator_01`), started with
`-stub-ports 12022,18081,18443,18888` — trivial accept-and-close TCP
listeners standing in for the base-minimum services (22/80/443/8080) a
real validator would run. ICMP needs no stub: the kernel answers echo
requests to any local address (127.0.0.0/8) on its own.
- **3 probers** (`site-1`/`site-2`/`site-3`), all probing `127.0.0.1`.
- **3 `httpstub` instances** (`scripts/httpstub`) standing in for the real
outbound targets (hub.docker.com / github.com / packages.ubuntu.com),
always returning `200 OK`.
The generated config uses a short `lease_ttl_seconds: 8` and
`checking_window_seconds: 15` so the whole run finishes in well under a
minute instead of using the (much longer) production defaults.
**Why only one IP address (`127.0.0.1`), and why it must be exactly that
one:** the self-check mechanism (`GET /whatsmyip`, see
`internal/httpapi/server.go`'s `remoteIP`) compares the TCP source address
the validator-agent's own outbound connection to control-api arrives with
against the address it was just assigned. In a real deployment, Neutron
actually SNATs the validator's egress traffic through whichever floating
IP is attached, so any configured address self-checks correctly. This
offline harness has no real network-level SNAT — the agent's traffic to
control-api always really originates from `127.0.0.1` — so only that
literal loopback address can ever pass self-check here. This is a
limitation of the harness's fidelity, not of the self-check mechanism
itself.
## Running it
```
scripts/run-local-e2e.sh
```
It will:
1. Build all three binaries plus `httpstub` into a temp workdir.
2. Start the 3 stub HTTP targets, control-api, the validator-agent, and the
3 probers.
3. Wait for `GET /healthz` to come up.
4. A few seconds in, **kill the validator-agent mid-run** and wait past the
8s lease TTL, to demonstrate that control-api's lease sweep reclaims the
in-flight IP (moves it back to `queued`, bumps `retry_count`) without
any special crash-recovery code — it's the same sweep that runs every
tick. It then restarts the validator-agent so the queue can finish.
5. Poll `GET /api/v1/admin/status` until every configured IP has reached a
terminal state (`done` or `failed`).
6. Print the final `/api/v1/admin/status` and `/api/v1/admin/ips` output.
Expect to see `127.0.0.1` end with `"state":"done"` and
`"overall_result":"pass"` (all egress checks against the stub targets
succeed, and all 3 probers can reach the stub TCP listeners and get ICMP
replies from loopback).
## Inspecting a run
The workdir (printed at the end, `/tmp/cloud-ip-validator-e2e.XXXXXX`) is
**not** deleted automatically, so you can inspect:
- `logs/control-api.log`, `logs/validator-agent.log`, `logs/prober-site-*.log`
- `control-api.db` — open with `sqlite3` to inspect the `checks` and
`events` tables directly, e.g.:
```
sqlite3 /tmp/cloud-ip-validator-e2e.XXXXXX/control-api.db \
"select ip_address, source, check_type, success from checks order by id"
```
## What this does *not* cover
This harness proves the orchestration, HTTP protocol, and check-running
logic all work together correctly. It does **not** exercise the real
`internal/openstack/client.go` (gophercloud) path — that only runs against
`openstack.mode: real` with actual OpenStack credentials. That path has its
own read-only smoke test, `internal/openstack/client_live_test.go`, skipped
by default and gated behind `OPENSTACK_LIVE_TEST=1`:
```
OPENSTACK_LIVE_TEST=1 \
OS_AUTH_URL=https://keystone.example:5000/v3 \
OS_TOKEN=... \
OS_PROJECT_ID=... \
OS_TEST_FLOATING_IP=203.0.113.10 \
go test ./internal/openstack/... -run TestClientLive -v
```
+275
View File
@@ -0,0 +1,275 @@
# Подготовка стенда и первичная инициализация
Документ описывает, как собрать компоненты, подготовить конфигурацию и
запустить стенд с нуля — от чистой машины до работающего control-api,
валидаторов и проберов. Если нужно просто быстро посмотреть систему в
работе без реального OpenStack — сразу переходите к разделу
[«Быстрая проверка без OpenStack»](#быстрая-проверка-без-openstack-offline-режим).
## Содержание
- [Компоненты и роли машин](#компоненты-и-роли-машин)
- [Требования](#требования)
- [Сборка бинарников](#сборка-бинарников)
- [Быстрая проверка без OpenStack (offline-режим)](#быстрая-проверка-без-openstack-offline-режим)
- [Подготовка конфигурации для реального стенда](#подготовка-конфигурации-для-реального-стенда)
- [Развёртывание control-api](#развёртывание-control-api)
- [Развёртывание validator-agent на ВМ-валидаторах](#развёртывание-validator-agent-на-вм-валидаторах)
- [Развёртывание prober на внешних площадках](#развёртывание-prober-на-внешних-площадках)
- [Проверка после запуска](#проверка-после-запуска)
- [Сетевые доступы](#сетевые-доступы)
## Компоненты и роли машин
| Компонент | Где запускается | Кол-во |
|---|---|---|
| `control-api` | Отдельная управляющая машина/ВМ с доступом к OpenStack API | 1 (без HA) |
| `validator-agent` | Каждая ВМ-валидатор в сервисном проекте облака | по числу валидаторов |
| `prober` | По одному на каждой из внешних тестовых площадок | 3 (по числу площадок) |
`control-api` — единственный компонент с состоянием (SQLite). Валидаторы и
проберы не хранят локального состояния и полностью управляются через опрос
control-api (см. [API.md](API.md)).
## Требования
- **Go 1.22+** для сборки (проверено на Go 1.26). Собранные бинарники —
статические, дополнительных зависимостей на целевых машинах не требуют
(используется чистый Go-драйвер SQLite, без cgo).
- Для `control-api` в боевом режиме (`openstack.mode: real`) — учётная
запись OpenStack с правами на чтение/изменение floating IP (Neutron)
в сервисном проекте, и заранее выделенные (allocated) floating IP —
инструмент их **не создаёт**, только привязывает/отвязывает
существующие.
- Для `validator-agent` и `prober` — возможность отправлять ICMP echo
(нужен root либо capability `CAP_NET_RAW`, см. юниты systemd).
- `curl`, `sqlite3` (опционально, для ручной инспекции БД) на машине с
control-api пригодятся для диагностики.
## Сборка бинарников
Из корня репозитория:
```bash
export PATH=$PATH:/usr/local/go/bin # если go не в PATH
go build -o bin/control-api ./cmd/control-api
go build -o bin/validator-agent ./cmd/validator-agent
go build -o bin/prober ./cmd/prober
```
Каждый бинарник самодостаточен — скопируйте нужный файл на
соответствующую машину (control-api → управляющая машина, validator-agent
→ каждый валидатор, prober → каждая площадка).
Убедиться, что всё собирается и юнит-тесты проходят:
```bash
go build ./... && go test ./...
```
## Быстрая проверка без OpenStack (offline-режим)
Прежде чем разворачивать реальный стенд, рекомендуется убедиться, что всё
собирается и работает корректно на локальной машине — без облака и
внешних площадок:
```bash
scripts/run-local-e2e.sh
```
Скрипт сам поднимает control-api (в режиме `openstack.mode: mock`),
одного validator-agent и трёх проберов как локальные процессы, прогоняет
один тестовый адрес через полный цикл проверки и печатает итоговый
результат. Подробности — в [docs/LOCAL_E2E.md](LOCAL_E2E.md). Это же
хороший способ разобраться в поведении системы перед первым боевым
запуском.
## Подготовка конфигурации для реального стенда
Все три компонента конфигурируются YAML-файлами. Шаблоны лежат в
`configs/*.example.yaml` — скопируйте их и заполните под ваш стенд.
### 1. `control-api.yaml`
```bash
cp configs/control-api.example.yaml /etc/cloud-ip-validator/control-api.yaml
```
Что обязательно нужно заполнить:
- **`validators`** — список валидаторов, у каждого `validator_id`
(произвольное имя, должно совпадать с `validator_id` в конфиге
соответствующего `validator-agent`) и `os_port_id` — **ID Neutron-порта**
основного сетевого интерфейса ВМ-валидатора (узнать: `openstack port
list --server <имя-ВМ>` или в веб-консоли облака).
- **`sites`** — три внешние площадки, `site_id` + `index` (1, 2 или 3).
`site_id` должен совпадать с `site_id` в конфиге соответствующего
`prober`.
- **`ip_addresses`** — список публичных IPv4-адресов на проверку, **в
порядке обработки**. Адреса должны существовать в сервисном проекте как
уже выделенные (allocated) floating IP — инструмент их не создаёт.
- **`openstack.mode: "real"`** и `*_env` поля — имена переменных
окружения, из которых будут прочитаны реальные учётные данные (сами
значения в этот файл **не пишутся**, см. следующий пункт).
- **`targets`** и **`check_types`** — при необходимости смените набор
целей для egress-проверок (по умолчанию — hub.docker.com, github.com,
packages.ubuntu.com) или включите `ssh` (по умолчанию выключен).
### 2. Переменные окружения для OpenStack
Учётные данные передаются **только** через переменные окружения — никогда
через YAML. Создайте файл (доступный на чтение только сервисному
пользователю):
```bash
install -m 0600 -o cloud-ip-validator -g cloud-ip-validator /dev/null /etc/cloud-ip-validator/control-api.env
cat >> /etc/cloud-ip-validator/control-api.env <<'EOF'
OS_AUTH_URL=https://keystone.example.com:5000/v3
OS_TOKEN=<токен администратора с правами на управление floating IP>
OS_PROJECT_ID=<id сервисного проекта>
OS_REGION_NAME=<регион>
EOF
```
Имена переменных должны совпадать с тем, что указано в
`control-api.yaml` в секции `openstack` (`auth_url_env`, `token_env` и
т.д.) — в шаблоне это ровно `OS_AUTH_URL`, `OS_TOKEN`, `OS_PROJECT_ID`,
`OS_PROJECT_NAME`, `OS_PROJECT_DOMAIN_NAME`, `OS_REGION_NAME`.
### 3. `validator-agent.yaml` (свой на каждом валидаторе)
```bash
cp configs/validator-agent.example.yaml /etc/cloud-ip-validator/validator-agent.yaml
```
Обязательно поменять:
- `validator_id` — должен совпадать с одним из `validators[].validator_id`
в конфиге control-api.
- `control_api_url` — адрес, по которому эта ВМ достучится до control-api.
### 4. `prober.yaml` (свой на каждой площадке)
```bash
cp configs/prober.example.yaml /etc/cloud-ip-validator/prober.yaml
```
Обязательно поменять:
- `site_id` — должен совпадать с одним из `sites[].site_id` в конфиге
control-api (для трёх площадок — три разных файла с `site-1`,
`site-2`, `site-3` или как вы их назвали).
- `control_api_url` — адрес control-api, доступный с площадки (обычно
через интернет — площадки внешние).
## Развёртывание control-api
```bash
useradd --system --no-create-home --shell /usr/sbin/nologin cloud-ip-validator
mkdir -p /var/lib/cloud-ip-validator /etc/cloud-ip-validator
chown cloud-ip-validator:cloud-ip-validator /var/lib/cloud-ip-validator
cp bin/control-api /usr/local/bin/control-api
cp deploy/systemd/control-api.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now control-api
```
**Первичная инициализация базы данных происходит автоматически** — при
первом старте `control-api` создаёт файл SQLite по пути `database.path`
из конфига (миграция схемы применяется один раз, повторные запуски —
no-op). Отдельной команды "init db" не требуется.
При каждом старте control-api также:
1. Регистрирует в БД всех валидаторов из `validators` конфига (если их
там ещё нет).
2. Добавляет в очередь все адреса из `ip_addresses`, которых там ещё нет
(уже обработанные ранее адреса повторно не добавляются и не
сбрасываются — см. [USAGE.md](USAGE.md#добавление-новых-ip-в-очередь)).
Проверить, что процесс поднялся:
```bash
curl -s http://localhost:8080/healthz
# {"ok":true}
journalctl -u control-api -f
```
## Развёртывание validator-agent на ВМ-валидаторах
Повторить на каждой ВМ-валидаторе:
```bash
cp bin/validator-agent /usr/local/bin/validator-agent
cp deploy/systemd/validator-agent.service /etc/systemd/system/
mkdir -p /etc/cloud-ip-validator
# скопировать сюда заполненный validator-agent.yaml с уникальным validator_id
systemctl daemon-reload
systemctl enable --now validator-agent
journalctl -u validator-agent -f
```
Юнит выдаёт процессу capability `CAP_NET_RAW` (без root) — она нужна для
отправки ICMP echo в рамках проверок.
## Развёртывание prober на внешних площадках
Аналогично, на каждой из трёх площадок:
```bash
cp bin/prober /usr/local/bin/prober
cp deploy/systemd/prober.service /etc/systemd/system/
mkdir -p /etc/cloud-ip-validator
# скопировать сюда prober.yaml с уникальным site_id для этой площадки
systemctl daemon-reload
systemctl enable --now prober
journalctl -u prober -f
```
## Проверка после запуска
После того как control-api, все валидаторы и все три пробера запущены:
```bash
curl -s http://<control-api>:8080/api/v1/admin/status | python3 -m json.tool
```
Ожидаемая картина сразу после старта: часть адресов в состоянии `queued`,
часть уже переходит в `assigning_fip`/`awaiting_self_check`/`checking` по
мере того, как освобождаются валидаторы. Через некоторое время появляются
записи в `done`/`failed`. Подробнее о том, как читать этот вывод и что
делать дальше — в [USAGE.md](USAGE.md).
Также стоит убедиться, что все валидаторы видны и не «зависли»:
```bash
curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
```
Все зарегистрированные валидаторы должны рано или поздно оказываться в
состоянии `idle` (между заданиями) — если валидатор надолго застрял в
`unreachable`, проверьте сетевую связность до control-api и логи агента
(`journalctl -u validator-agent`).
## Сетевые доступы
Минимально необходимая связность:
- `validator-agent` → `control-api`: TCP, порт из `server.listen_addr`
(обычно 8080).
- `prober` (на каждой из 3 площадок) → `control-api`: тот же порт, обычно
через интернет.
- `prober` → адрес, который в данный момент проверяется (динамический,
меняется по ходу работы очереди): TCP 22/80/443/8080 + ICMP —
собственно и есть проверяемый трафик, его нельзя заранее ограничить
одним IP.
- `validator-agent` → интернет: HTTPS/ICMP до целей из `targets` конфига
(по умолчанию hub.docker.com, github.com, packages.ubuntu.com) — именно
через floating IP, который в данный момент привязан к валидатору.
- `control-api` → OpenStack Keystone/Neutron API (`OS_AUTH_URL` и далее по
каталогу сервисов).
API control-api сейчас не аутентифицирован (см. предупреждение в начале
[API.md](API.md)) — ограничивайте доступ к порту control-api на уровне
сети/firewall теми хостами, где реально работают валидаторы и проберы.
+269
View File
@@ -0,0 +1,269 @@
# Работа со стендом
Этот документ — для оператора, который уже развернул стенд (см.
[SETUP.md](SETUP.md)) и теперь использует его в повседневной работе:
добавляет адреса на проверку, следит за очередью, разбирается в
результатах и реагирует на проблемы. Прямые вызовы API описаны в
[API.md](API.md) — здесь мы используем их только как инструмент, не
углубляясь в протокол.
## Содержание
- [Как устроена работа с системой](#как-устроена-работа-с-системой)
- [Добавление новых IP в очередь](#добавление-новых-ip-в-очередь)
- [Наблюдение за очередью](#наблюдение-за-очередью)
- [Значения полей IP](#значения-полей-ip)
- [Как читать итоговый результат (pass/partial/fail)](#как-читать-итоговый-результат-passpartialfail)
- [Просмотр деталей и истории по конкретному адресу](#просмотр-деталей-и-истории-по-конкретному-адресу)
- [Управление валидаторами](#управление-валидаторами)
- [Управление площадками (проберами)](#управление-площадками-проберами)
- [Повторная проверка адреса](#повторная-проверка-адреса)
- [Частые проблемы и что с ними делать](#частые-проблемы-и-что-с-ними-делать)
## Как устроена работа с системой
Оператор не взаимодействует с валидаторами и проберами напрямую — вся
работа идёт через `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 в очередь
**В текущей версии добавление адресов происходит только через конфиг
control-api**, отдельного API-метода "добавить IP в очередь" нет.
1. Добавьте новые адреса в список `ip_addresses` в
`/etc/cloud-ip-validator/control-api.yaml` (в конец списка, либо в
нужном порядке — очередь обрабатывается строго в порядке следования
списка, `sequence`).
2. Перезапустите control-api:
```bash
systemctl restart control-api
```
Это безопасно для уже идущей работы: при старте control-api добавляет в
очередь только **новые** адреса (те, которых там ещё нет) — уже
обработанные ранее адреса не сбрасываются и повторно не проверяются.
Адреса, которые были удалены из `ip_addresses`, но уже есть в базе,
**не удаляются** из очереди/истории автоматически — если конкретный адрес
больше не нужно проверять и его нет в очереди/в процессе, можно просто
оставить как есть (историю он не портит).
> Совет: держите `control-api.yaml` под версионным контролем (git) —
> список адресов на проверку тогда одновременно служит и журналом того,
> что вообще когда-либо ставилось в очередь.
## Наблюдение за очередью
Общая сводка:
```bash
curl -s http://<control-api>: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://<control-api>:8080/api/v1/admin/status | python3 -m json.tool'
```
Полный список всех адресов со всеми полями:
```bash
curl -s http://<control-api>:8080/api/v1/admin/ips | python3 -m json.tool
```
Только финальные результаты (уже готовые адреса), с помощью `jq`:
```bash
curl -s http://<control-api>:8080/api/v1/admin/ips \
| jq '[.[] | select(.State=="done" or .State=="failed") | {IPAddress, State, OverallResult}]'
```
## Значения полей IP
| Поле | Значение |
|---|---|
| `IPAddress` | Проверяемый адрес |
| `Sequence` | Позиция в очереди (порядок из конфига) |
| `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`, либо пусто, пока проверка не завершена |
| `AssignedAt` / `AggregatedAt` / `FIPReleasedAt` | Метки времени соответствующих этапов |
## Как читать итоговый результат (pass/partial/fail)
- **`pass`** — прошли все проверки (все исходящие + все три площадки по
всем портам и ICMP). Адрес можно считать пригодным к повторной выдаче.
- **`partial`** — часть проверок прошла, часть — нет (например, площадка
site-2 не смогла достучаться по 8080/tcp, но остальное в порядке).
Означает частичную деградацию — например, адрес заблокирован в
отдельном сегменте сети/у отдельного провайдера. Требует решения
оператора: годится ли адрес для данного случая использования.
- **`fail`** — либо ни одна проверка не прошла, либо адрес вообще не
дошёл до стадии проверок (например, self-check не подтвердился —
трафик валидатора не пошёл через назначенный FIP — и попытки
исчерпались). Смотрите `events` по этому адресу (см. ниже), чтобы
понять, на каком шаге и почему.
Отсутствие ответа от источника (площадка не прислала результат до
истечения `checking_window_seconds`) засчитывается как провал — это
управляется настройкой `aggregation.missing_counts_as_fail` в конфиге
control-api (по умолчанию включено).
## Просмотр деталей и истории по конкретному адресу
```bash
curl -s http://<control-api>: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://<control-api>:8080/api/v1/admin/ips/203.0.113.10 \
| jq '.checks[] | select(.Success==false)'
```
## Управление валидаторами
Список валидаторов и их текущее состояние:
```bash
curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
```
Состояния валидатора: `unregistered` (в конфиге есть, агент ещё не
подключался), `idle` (свободен, готов взять адрес), `assigned`/`checking`
(занят), `unreachable` (пропустил heartbeat дольше
`orchestrator.heartbeat_timeout_seconds`).
**Добавление нового валидатора:**
1. Поднимите новую ВМ в сервисном проекте облака, узнайте её Neutron
`port_id`.
2. Добавьте запись в `validators` в `control-api.yaml`
(`validator_id` + `os_port_id`) и перезапустите `control-api`.
3. Разверните и запустите `validator-agent` на новой ВМ с тем же
`validator_id` в его конфиге (см. [SETUP.md](SETUP.md#развёртывание-validator-agent-на-вм-валидаторах)).
**Вывод валидатора из эксплуатации:** остановите на нём
`validator-agent` (`systemctl stop validator-agent`). Он перестанет
получать новые задания после того, как закончит текущее (если оно было);
если он был убит посреди работы — control-api сам заберёт у него
незавершённый адрес обратно в очередь по истечении
`orchestrator.lease_ttl_seconds`. Удалять запись из `control-api.yaml`
не обязательно — просто выключенный агент не будет ничего забирать.
## Управление площадками (проберами)
Аналогично валидаторам: чтобы добавить площадку, добавьте `site_id` +
`index` (свободный от 1 до 3, или больше — но текущая схема БД
рассчитана ровно на 3 площадки, см. ниже) в `sites` конфига control-api,
разверните на площадке `prober` с тем же `site_id`.
> Важно: количество площадок в текущей версии жёстко зашито в схему БД
> (`Site1Complete`/`Site2Complete`/`Site3Complete`) — система рассчитана
> ровно на **три** внешние площадки, как и описано в исходной схеме
> процесса. Изменение их числа потребует доработки схемы данных, это не
> делается только правкой конфига.
## Повторная проверка адреса
Если адрес завершился с `failed` или `partial`, а вы хотите перепроверить
его ещё раз (например, после устранения блокировки на стороне сети):
на данный момент нет отдельного API-метода "перезапустить проверку".
Самый простой путь:
1. Убедитесь, что адрес не находится в активном состоянии (`checking`
и т.п.) — то есть уже `done`/`failed`.
2. Временно уберите и снова добавьте адрес в список `ip_addresses`
(либо просто пересоздайте запись в БД вручную, если это единичный
случай и у вас есть доступ к SQLite) и перезапустите `control-api`.
Поскольку сидирование очереди идёт по уникальности `ip_address`
(конфликт по уже существующей записи просто игнорируется), самый чистый
способ гарантированно перепроверить конкретный адрес — обратиться к
администратору БД (см. следующий раздел) либо дождаться штатной
доработки API под повторные проверки.
## Частые проблемы и что с ними делать
**Валидатор долго висит в `unreachable`.**
Проверьте сетевую связность ВМ-валидатора до `control-api` (порт из
`server.listen_addr`) и что процесс `validator-agent` вообще запущен
(`systemctl status validator-agent`, `journalctl -u validator-agent`).
**Адрес постоянно проваливает self-check.**
Смотрите `events` по адресу (`GET /api/v1/admin/ips/{ip}`) — в детали
события `self_check_result` будет указан обнаруженный исходящий адрес.
Если он не совпадает с ожидаемым — вероятно, на ВМ-валидаторе есть другой
маршрут наружу (не через назначенный Floating IP), либо привязка FIP на
стороне OpenStack не применилась. Проверьте вручную в OpenStack
(`openstack floating ip show <адрес>`), что `port_id` совпадает с портом
валидатора.
**Площадка (`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.