Files
cloud-ip-validator/docs/SETUP.md
T
ayurishchevandClaude Sonnet 5.5 debf2afed2 Add authentication: admin/agent bearer tokens for the API, login for the dashboard
control-api: every route now carries a mandatory access level (admin / agent /
open) in a route table. All /api/v1/admin/* require the admin token; the
write calls of validator-agent and prober (self-check, events, results,
complete) require a separate static agent token; register, heartbeat and
fetching the assignment stay open. Tokens come from env vars, are compared in
constant time and never logged. An empty token leaves that level open with a
startup warning (backward compatible).

validator-agent / prober: apiclient sends the agent token only to control-api.

admin-dashboard: login/password (from env) with a stateless HMAC session
cookie, Origin-based CSRF check, per-IP brute-force throttle, HX-Redirect for
htmx polls, logout in the sidebar; the dashboard calls control-api with the
admin token. Login page layout fixed after review.

Also: env plumbing in docker-compose/rxprod-compose/systemd/config examples,
e2e script with token assertions, tests, docs (API, SETUP, USAGE, DASHBOARD,
README), plan and review under docs/changes/, bin/ rebuilt with new
SHA256SUMS.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 11:35:24 +03:00

825 lines
53 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Подготовка стенда и первичная инициализация
Документ описывает, как подготовить конфигурацию и запустить стенд с нуля
— от чистой машины до работающего control-api, валидаторов и проберов.
Бинарники брать не обязательно из исходников: в репозитории уже лежат
готовые сборки для Linux x86_64 (`bin/`) — это самый быстрый путь к
развёртыванию, см. [«Получение бинарников»](#получение-бинарников). Если
нужно просто быстро посмотреть систему в работе без реального OpenStack —
сразу переходите к разделу
[«Быстрая проверка без OpenStack»](#быстрая-проверка-без-openstack-offline-режим).
Ниже описано развёртывание как systemd-юнитами (по умолчанию), так и
Docker-контейнерами — оба пути равноправны и описаны исчерпывающе, см.
[«Развёртывание в Docker»](#развёртывание-в-docker).
## Содержание
- [Компоненты и роли машин](#компоненты-и-роли-машин)
- [Требования](#требования)
- [Получение бинарников](#получение-бинарников)
- [Вариант A: готовые бинарники из репозитория (рекомендуется)](#вариант-a-готовые-бинарники-из-репозитория-рекомендуется)
- [Вариант B: сборка из исходников](#вариант-b-сборка-из-исходников)
- [Быстрая проверка без OpenStack (offline-режим)](#быстрая-проверка-без-openstack-offline-режим)
- [Подготовка конфигурации для реального стенда](#подготовка-конфигурации-для-реального-стенда)
- [Развёртывание control-api](#развёртывание-control-api)
- [Развёртывание validator-agent на ВМ-валидаторах](#развёртывание-validator-agent-на-вм-валидаторах)
- [Развёртывание prober на внешних площадках](#развёртывание-prober-на-внешних-площадках)
- [Развёртывание admin-dashboard](#развёртывание-admin-dashboard)
- [Развёртывание в Docker](#развёртывание-в-docker)
- [Требования для Docker-развёртывания](#требования-для-docker-развёртывания)
- [Вариант 1: docker compose, один хост (знакомство/dev, mock-режим)](#вариант-1-docker-compose-один-хост-знакомстводev-mock-режим)
- [Вариант 2: docker compose, реальный стенд по нескольким хостам](#вариант-2-docker-compose-реальный-стенд-по-нескольким-хостам)
- [Вариант 3: docker build/docker run по одному компоненту](#вариант-3-docker-builddocker-run-по-одному-компоненту)
- [Обновление образов после изменения кода](#обновление-образов-после-изменения-кода)
- [Диагностика Docker-развёртывания](#диагностика-docker-развёртывания)
- [Проверка после запуска](#проверка-после-запуска)
- [Сетевые доступы](#сетевые-доступы)
## Компоненты и роли машин
| Компонент | Где запускается | Кол-во |
|---|---|---|
| `control-api` | Отдельная управляющая машина/ВМ с доступом к OpenStack API | 1 (без HA) |
| `validator-agent` | Каждая ВМ-валидатор в сервисном проекте облака | по числу валидаторов |
| `prober` | По одному на каждой из внешних тестовых площадок | 3 (по числу площадок) |
| `admin-dashboard` | Любая машина с сетевым доступом до `control-api` (опционально) | 0 или 1 |
`control-api` — единственный компонент с состоянием (SQLite). Валидаторы,
проберы и `admin-dashboard` не хранят локального состояния и полностью
управляются через опрос control-api (см. [API.md](API.md),
[DASHBOARD.md](DASHBOARD.md)). `admin-dashboard` не обязателен — вся его
функциональность доступна и через `curl` напрямую по API.
## Требования
- Целевые серверы (control-api, валидаторы, площадки) — **Linux
x86_64 (amd64)**. Для этой платформы в репозитории уже лежат готовые
бинарники (см. ниже) — устанавливать Go на целевые машины не требуется.
- **Go 1.22+** нужен только если вы пересобираете бинарники из
исходников (проверено на Go 1.26) — например, для другой платформы
(arm64, другая ОС) или после изменения кода.
- Для `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 пригодятся для диагностики.
## Получение бинарников
### Вариант A: готовые бинарники из репозитория (рекомендуется)
В директории `bin/` репозитория уже лежат четыре готовых бинарника —
собирать их на целевых серверах не нужно, разворачивание сразу
начинается с копирования и запуска (раздел
[«Развёртывание control-api»](#развёртывание-control-api) и далее).
```
bin/
├── control-api # ~11 МБ
├── validator-agent # ~7 МБ
├── prober # ~7 МБ
├── admin-dashboard # ~9 МБ (опционален, см. DASHBOARD.md)
└── SHA256SUMS
```
Характеристики сборки:
- Платформа: `GOOS=linux GOARCH=amd64`.
- `CGO_ENABLED=0` — статическая линковка, без cgo (используется чистый
Go-драйвер SQLite `modernc.org/sqlite`). Дополнительные `.so`-библиотеки
и конкретная версия glibc на целевом сервере **не требуются**.
- Собрано флагами `-trimpath -ldflags="-s -w"` (без путей сборки и
отладочной информации — компактнее и без утечки информации о машине
сборки).
Убедиться в отсутствии динамических зависимостей и целостности файлов
после переноса на целевой сервер:
```bash
ldd bin/control-api # => "not a dynamic executable"
file bin/control-api # => ELF 64-bit LSB executable, x86-64, statically linked
# После scp/rsync на целевой сервер — проверить, что файлы не повреждены:
sha256sum -c bin/SHA256SUMS
```
Перенос на целевые серверы, например:
```bash
scp bin/control-api control-api-host:/tmp/
scp bin/validator-agent validator-host-01:/tmp/
scp bin/prober probe-site-1:/tmp/
scp bin/admin-dashboard dashboard-host:/tmp/ # опционально
```
> Если целевая платформа отличается от linux/amd64 (например, ВМ на
> arm64) — готовые бинарники не подойдут, используйте
> [вариант B](#вариант-b-сборка-из-исходников).
### Вариант B: сборка из исходников
Из корня репозитория:
```bash
export PATH=$PATH:/usr/local/go/bin # если go не в PATH
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # поменяйте GOARCH для другой платформы
go build -trimpath -ldflags="-s -w" -o bin/control-api ./cmd/control-api
go build -trimpath -ldflags="-s -w" -o bin/validator-agent ./cmd/validator-agent
go build -trimpath -ldflags="-s -w" -o bin/prober ./cmd/prober
go build -trimpath -ldflags="-s -w" -o bin/admin-dashboard ./cmd/admin-dashboard
```
Каждый бинарник самодостаточен — скопируйте нужный файл на
соответствующую машину (control-api → управляющая машина, validator-agent
→ каждый валидатор, prober → каждая площадка, admin-dashboard →
опционально, любая машина с доступом до control-api).
Убедиться, что всё собирается и юнит-тесты проходят:
```bash
go build ./... && go test ./...
```
Пересобирайте из исходников и обновляйте `bin/` с зафиксированными
`SHA256SUMS`, если меняли код — готовые бинарники в репозитории **не
обновляются автоматически**:
```bash
sha256sum bin/control-api bin/validator-agent bin/prober bin/admin-dashboard | sed 's#bin/##' > bin/SHA256SUMS
```
## Быстрая проверка без 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` (число слотов не
ограничено; в примере ниже — три). `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` (по умолчанию выключен).
> `validators`, `sites`, `targets` и `check_types` читаются из этого файла
> только один раз — при самом первом старте против пустой базы данных
> (bootstrap). После этого все последующие изменения этих четырёх секций
> вносятся через `/api/v1/admin/config/*`, а не правкой YAML — см.
> [API.md](API.md#управление-очередью-и-конфигурацией) и
> [USAGE.md](USAGE.md#управление-валидаторами). `ip_addresses` — исключение,
> он остаётся YAML + аддитивным добавлением при каждом старте (плюс
> `POST /api/v1/admin/ips` для управления очередью без рестарта).
### 2. Переменные окружения для OpenStack
Учётные данные передаются **только** через переменные окружения — никогда
через YAML. Есть два режима аутентификации, выбираются полем
`openstack.auth_method` в `control-api.yaml`:
**`auth_method: "token"` (по умолчанию)** — вы предоставляете уже
готовый, заранее scoped на нужный проект токен (например, полученный
через `openstack --os-project-id=<id> token issue`). Control-api
использует его как есть, ни на что не обменивает. Просто, но токен не
самообновляется: когда он истечёт, `control-api` начнёт получать ошибки
от OpenStack API, пока вы вручную не перевыпустите токен, не обновите
переменную и не перезапустите процесс.
```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=<токен администратора, уже scoped на сервисный проект>
OS_PROJECT_ID=<id сервисного проекта>
OS_REGION_NAME=<регион>
EOF
```
**`auth_method: "password"`** — вы предоставляете обычные логин/пароль;
control-api сам получает токен через Keystone и автоматически
переполучает новый при истечении текущего (весь срок жизни процесса, без
ручного вмешательства). Компромисс — в файле окружения лежит долгоживущий
пароль, а не токен.
```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_PROJECT_ID=<id сервисного проекта>
OS_REGION_NAME=<регион>
OS_USERNAME=<логин>
OS_USER_DOMAIN_NAME=<домен пользователя>
OS_PASSWORD=<пароль>
EOF
```
и в `control-api.yaml`: `openstack.auth_method: "password"`.
Опционально в обоих режимах — `OS_INTERFACE` (`public`/`internal`/`admin`,
по умолчанию `public`): выбирает, какой из адресов Neutron в каталоге
сервисов использовать, если у эндпоинта их несколько.
Имена переменных должны совпадать с тем, что указано в
`control-api.yaml` в секции `openstack` (`auth_url_env`, `token_env` и
т.д.) — в шаблоне это ровно `OS_AUTH_URL`, `OS_PROJECT_ID`,
`OS_REGION_NAME`, `OS_INTERFACE`, и, в зависимости от режима, либо
`OS_TOKEN`, либо `OS_USERNAME`/`OS_USER_DOMAIN_NAME`/`OS_PASSWORD` — это
стандартные имена, принятые в python-openstackclient/RC-файлах, менять их
обычно не требуется.
### 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, доступный с площадки (обычно
через интернет — площадки внешние).
### 5. Аутентификация: токены и пароль дашборда
Доступ к API и дашборду защищается секретами из переменных окружения (в YAML значения не хранятся; в `*.yaml` — только *имена*
переменных, и менять их нужно редко). Токены генерируются случайными: `openssl rand -hex 32`. Схема доступа к методам —
[API.md](API.md#аутентификация).
| Где | Переменная | Назначение |
|---|---|---|
| control-api | `CONTROL_API_ADMIN_TOKEN` | токен администратора: закрывает `/api/v1/admin/*` |
| control-api | `CONTROL_API_AGENT_TOKEN` | токен агентов: закрывает запись результатов валидаторов и проберов |
| validator-agent, prober | `CONTROL_API_AGENT_TOKEN` | тот же токен агентов (отправляется как Bearer) |
| admin-dashboard | `ADMIN_DASHBOARD_CONTROL_API_TOKEN` | токен администратора control-api (то же значение, что `CONTROL_API_ADMIN_TOKEN`) |
| admin-dashboard | `ADMIN_DASHBOARD_USERNAME`, `ADMIN_DASHBOARD_PASSWORD` | логин и пароль единственного администратора дашборда |
| admin-dashboard | `ADMIN_DASHBOARD_SESSION_SECRET` | ключ подписи cookie-сессии (случайная строка; без него — случайный на каждый запуск, сессии сбрасываются рестартом) |
- **Пустое значение = защита выключена.** Токен не задан — соответствующий уровень API открыт; логин/пароль не заданы — дашборд открыт. В обоих
случаях в логе при старте — предупреждение. Это сделано для обратной совместимости; на реальном стенде задайте всё.
- systemd: добавьте переменные в `/etc/cloud-ip-validator/<компонент>.env` (подключается `EnvironmentFile=`, файл `chmod 600`). Docker: переменные
из `.env` (см. `.env.example`).
- Ключи `auth.*` и `*_token_env` в YAML меняют только имена переменных; время жизни сессии — `auth.session_ttl_minutes` дашборда (по умолчанию 480).
- Токены и пароль передаются открытым текстом, если перед сервисами нет TLS: публикуйте API и дашборд через reverse-proxy с TLS.
**Порядок включения без простоя** (особенно когда валидаторы и пробер на других машинах):
1. обновите бинарники всех компонентов — токены ещё не заданы, всё работает как раньше;
2. задайте `CONTROL_API_AGENT_TOKEN` на валидаторах и проберах, `ADMIN_DASHBOARD_*` на дашборде и перезапустите их;
3. **последним** задайте `CONTROL_API_ADMIN_TOKEN` и `CONTROL_API_AGENT_TOKEN` на control-api и перезапустите его.
Если включить токен агентов на control-api раньше, чем он появится у валидатора или пробера, их результаты будут получать `401` и проверки не завершатся.
Ротация токена — та же последовательность с новым значением.
## Развёртывание 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. **Bootstrap-once для `validators`/`sites`/`targets`/`check_types`.**
YAML применяется **только если соответствующая таблица в БД сейчас
пуста** — то есть только на самом первом старте против чистой базы.
Как только в таблице появилась хотя бы одна строка (через этот
bootstrap либо через `/api/v1/admin/config/*`, см.
[API.md](API.md#управление-очередью-и-конфигурацией)), YAML для этой
секции больше не перечитывается ни при одном последующем рестарте —
источник истины переключается на БД. Это осознанное отличие от более
ранних версий, где `validators` из YAML переприменялись при каждом
рестарте: теперь правки, сделанные через admin API (например, смена
`os_port_id` валидатора), переживают рестарт вместо того, чтобы
тихо откатываться.
2. **Всегда аддитивно** добавляет в очередь все адреса из
`ip_addresses`, которых там ещё нет (уже обработанные ранее адреса
повторно не добавляются и не сбрасываются — см.
[USAGE.md](USAGE.md#добавление-новых-ip-в-очередь)). Это отдельный,
не завязанный на bootstrap-once путь — не путайте с
`POST /api/v1/admin/ips`, который умеет то же самое (и ещё
принудительный повтор уже проверенных адресов) без перезапуска.
Проверить, что процесс поднялся:
```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
```
## Развёртывание admin-dashboard
Опционально — вся его функциональность доступна и через `curl` напрямую
по API (см. [API.md](API.md)). На любой машине с сетевым доступом до
`control-api`:
```bash
cp bin/admin-dashboard /usr/local/bin/admin-dashboard
cp deploy/systemd/admin-dashboard.service /etc/systemd/system/
mkdir -p /etc/cloud-ip-validator
cp configs/admin-dashboard.example.yaml /etc/cloud-ip-validator/admin-dashboard.yaml
# отредактировать control_api.base_url под ваш стенд
systemctl daemon-reload
systemctl enable --now admin-dashboard
journalctl -u admin-dashboard -f
```
Открыть `http://<admin-dashboard>:8090/` в браузере. Подробнее о
страницах и о том, что дашборд может (и не может) — в
[DASHBOARD.md](DASHBOARD.md).
## Развёртывание в Docker
Альтернатива всем systemd-разделам выше — те же четыре бинарника, но
упакованные в Docker-образы. Не обязательно выбирать одно или другое —
можно, например, гонять `control-api` под systemd, а `prober` на внешней
площадке — в контейнере; главное, чтобы они видели друг друга по сети (см.
[«Сетевые доступы»](#сетевые-доступы)).
Три способа запуска, по возрастанию гранулярности:
1. **`docker compose`, один хост** — быстрее всего увидеть всё в работе
(mock-режим, без OpenStack).
2. **`docker compose`, несколько хостов** — реальный стенд, тот же compose
с профилями решает, какие сервисы поднимать на каждой машине.
3. **`docker build`/`docker run` по одному компоненту** — точечная отладка
одного сервиса без всего compose-стека.
### Требования для Docker-развёртывания
- Docker Engine и Compose plugin v2 (`docker compose version`; отдельная
утилита `docker-compose` v1 не поддерживается — команды ниже используют
синтаксис `docker compose ...`).
- Хост под управлением Docker — **linux/amd64** рекомендуется. На другой
архитектуре (например, Apple Silicon Mac) обязательно указывать
`--platform linux/amd64` при сборке/запуске (в `docker-compose.yml` он
уже прописан для каждого сервиса) — иначе получится образ, чей слой ОС
собран под архитектуру хоста, а внутри лежит скопированный amd64-бинарник
(см. ниже), и контейнер не запустится (`exec format error`).
- **Важное отличие от типичных Go-проектов: Dockerfile'ы здесь ничего не
компилируют.** `deploy/docker/<компонент>/Dockerfile` — это просто
`FROM alpine:3.20` + `COPY bin/<компонент> ...` (иногда плюс шаблон
конфига и `docker-entrypoint.sh` — см. ниже). Собственно Go-сборка
происходит заранее, отдельно от Docker (см.
[«Получение бинарников»](#получение-бинарников)) — то есть **перед
`docker build`/`docker compose build` в каталоге `bin/` уже должны лежать
актуальные бинарники под linux/amd64**: либо уже закоммиченные в
репозитории (вариант A — тогда просто `docker compose up -d --build`
сработает сразу), либо свежесобранные после правок кода (вариант B —
`go build ...`, см. [«Обновление образов после
изменения кода»](#обновление-образов-после-изменения-кода)).
- Все команды `docker build`/`docker compose build` запускаются из
каталога `deploy/docker/` (или с указанием контекста `../..`) — контекст
сборки каждого сервиса в `docker-compose.yml` это корень репозитория
(`context: ../..`), поэтому на каждом хосте, где вы собираете образы
локально, должен быть выкачан весь репозиторий (`git clone`), а не
только каталог `deploy/docker/`.
### Вариант 1: docker compose, один хост (знакомство/dev, mock-режим)
Самый быстрый способ увидеть всю систему целиком работающей — без
реального облака, все четыре сервиса на одной машине:
```bash
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
```
Что при этом происходит:
- `docker-compose.yml` (общие определения сервисов) объединяется с
`docker-compose.override.yml` — dev-надстройка, которую `docker compose`
подхватывает **автоматически**, без явного `-f` (именно её в проде
заменяют на `docker-compose.prod.yml`, см. следующий раздел).
- `.env` (см. `.env.example`) выставляет `COMPOSE_PROFILES=control-plane,
dashboard,prober,validator` — включены все четыре профиля/сервиса сразу,
то есть весь стенд поднимается на одной машине. В реальном
распределённом развёртывании на каждом хосте включают только нужные
профили (см. вариант 2).
- `control-api.docker.yaml`, скопированный из
`control-api.docker.example.yaml`, — заранее заполненный конфиг с
`openstack.mode: "mock"` и тестовыми `validator_01`/`site-1`/тремя
IP-заглушками (`203.0.113.10-12`) — совпадает с дефолтами
`PROBER_SITE_ID`/`VALIDATOR_AGENT_VALIDATOR_ID` в `.env.example`, так что
`prober`/`validator-agent` сразу находят себя в конфиге control-api и
успешно регистрируются. Реального OpenStack и учётных данных для этого
режима не нужно.
- Dev-надстройка пробрасывает порты наружу (`8080` — control-api, `8090` —
admin-dashboard) и добавляет `depends_on: control-api: condition:
service_healthy` для `admin-dashboard`/`prober`/`validator-agent` —
они не пытаются зарегистрироваться раньше, чем `control-api` пройдёт
свой healthcheck (`GET /healthz`, настроен в базовом
`docker-compose.yml`).
- `-d` — фоновый режим; `--build` — собрать образы из `Dockerfile`, а не
пытаться скачать несуществующие в реестре.
Проверить, что всё поднялось:
```bash
docker compose ps
curl -s http://localhost:8080/healthz
curl -s http://localhost:8080/api/v1/admin/status | python3 -m json.tool
```
Открыть дашборд в браузере: `http://localhost:8090/`.
Посмотреть логи (в т.ч. чтобы убедиться, что `prober`/`validator-agent`
успешно зарегистрировались):
```bash
docker compose logs -f control-api
docker compose logs -f prober validator-agent admin-dashboard
```
Остановить:
```bash
docker compose down # контейнеры + сеть; volume с БД (cloud-ip-validator-db) остаётся
docker compose down -v # то же самое + удалить volume с БД (полный сброс состояния)
```
### Вариант 2: docker compose, реальный стенд по нескольким хостам
Та же пара файлов (`docker-compose.yml` + профили), но с
`docker-compose.prod.yml` вместо dev-надстройки и реальным конфигом
control-api вместо mock. `docker-compose.prod.yml` не публикует порты
наружу напрямую (стенд предполагается за reverse-proxy/файрволом) и не
использует `depends_on` (на разнесённом по хостам стенде профиль
`control-plane` может вообще отсутствовать в compose-вызове конкретного
хоста, и `depends_on` на неактивный профиль — ошибка конфигурации
`docker compose`); вместо этого у каждого сервиса `restart:
unless-stopped` — `prober`/`validator-agent` при недоступном на старте
`control-api` просто падают и перезапускаются политикой рестарта, пока
`control-api` не станет доступен.
На каждом хосте — свой `.env.prod` (по образцу `.env.prod.example`) с
`COMPOSE_PROFILES`, задающим, какие сервисы именно этот хост поднимает:
| Хост | `COMPOSE_PROFILES` |
|---|---|
| Управляющая машина (control-api + опционально дашборд) | `control-plane,dashboard` |
| Внешняя площадка (prober) | `prober` |
| ВМ-валидатор (validator-agent) | `validator` |
| Всё на одной машине (как вариант 1, но прод-режим) | `control-plane,dashboard,prober,validator` |
**На управляющей машине** (`control-plane`/`dashboard`):
```bash
git clone <repo> && cd <repo>/deploy/docker # если ещё не склонировано
cp .env.prod.example .env.prod
# отредактировать .env.prod:
# COMPOSE_PROFILES=control-plane,dashboard
# CONTROL_API_CONFIG_PATH=/etc/cloud-ip-validator/control-api.yaml (реальный конфиг,
# заполненный по образцу configs/control-api.example.yaml — см.
# "Подготовка конфигурации для реального стенда" выше)
# OS_AUTH_URL / OS_PROJECT_ID / OS_REGION_NAME / OS_TOKEN (или пароль-режим) —
# те же переменные, что и для systemd-развёртывания, см. раздел 2 выше
# ADMIN_DASHBOARD_CONTROL_API_URL=http://control-api:8080 (тот же docker-сеть,
# менять не нужно, если admin-dashboard в том же compose-вызове)
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build
```
Конфиг control-api при этом **монтируется файлом** (путь из
`CONTROL_API_CONFIG_PATH`), а не генерируется из переменных окружения, как
у остальных трёх компонентов — у него в конфиге списки (`validators`,
`sites`, `targets`, `ip_addresses`), которые не выразить одной
переменной. Данные (`control-api.db`) живут в volume `cloud-ip-validator-db`
— переживают `docker compose down` (без `-v`) и пересоздание контейнера.
**На каждой внешней площадке** (`prober`):
```bash
git clone <repo> && cd <repo>/deploy/docker
cp .env.prod.example .env.prod
# COMPOSE_PROFILES=prober
# PROBER_SITE_ID=<site_id, уже зарегистрированный на control-api через
# PUT /api/v1/admin/config/sites/{index} — см. USAGE.md>
# PROBER_CONTROL_API_URL=<реальный, сетевой доступный адрес control-api,
# НЕ http://control-api:8080 — тот хост есть только внутри compose-сети
# самой управляющей машины>
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d --build
```
**На каждой ВМ-валидаторе** (`validator`) — аналогично, с
`COMPOSE_PROFILES=validator`, `VALIDATOR_AGENT_VALIDATOR_ID` (должен
совпадать с `validators[].validator_id` в конфиге control-api) и
`VALIDATOR_AGENT_CONTROL_API_URL`.
> Полный список переменных окружения для каждого сервиса, с дефолтами —
> см. таблицы в разделе [«Вариант 3»](#вариант-3-docker-builddocker-run-по-одному-компоненту)
> ниже или прямо в `docker-compose.yml`/`.env.prod.example`.
### Вариант 3: docker build/docker run по одному компоненту
Для точечной пересборки/перезапуска одного сервиса напрямую, без compose
— например, обновить только `prober` на одной площадке, не трогая
остальной стенд. Все команды — из корня репозитория.
**prober:**
```bash
docker build --platform linux/amd64 -t cloud-ip-validator-prober -f deploy/docker/prober/Dockerfile .
docker run -d --platform linux/amd64 --cap-add NET_RAW --name prober \
-e PROBER_SITE_ID=<site_id> \
-e PROBER_CONTROL_API_URL=<http://control-api-host:port> \
cloud-ip-validator-prober
```
| Переменная | Обязательна | По умолчанию |
|---|---|---|
| `PROBER_SITE_ID` | да | — (должен быть зарегистрирован в control-api, `PUT /api/v1/admin/config/sites/{index}`) |
| `PROBER_CONTROL_API_URL` | да | — |
| `PROBER_POLL_INTERVAL_SECONDS` | нет | `5` |
| `PROBER_TCP_TIMEOUT_SECONDS` | нет | `5` |
| `PROBER_ICMP_TIMEOUT_SECONDS` | нет | `5` |
| `PROBER_ICMP_COUNT` | нет | `3` |
`--cap-add NET_RAW` обязателен — без него ICMP-проверки не заработают.
**admin-dashboard:**
```bash
docker build --platform linux/amd64 -t cloud-ip-validator-admin-dashboard -f deploy/docker/admin-dashboard/Dockerfile .
docker run -d --platform linux/amd64 -p 8090:8090 --name admin-dashboard \
-e ADMIN_DASHBOARD_CONTROL_API_URL=<http://control-api-host:port> \
cloud-ip-validator-admin-dashboard
```
| Переменная | Обязательна | По умолчанию |
|---|---|---|
| `ADMIN_DASHBOARD_CONTROL_API_URL` | да | — |
| `ADMIN_DASHBOARD_LISTEN_ADDR` | нет | `:8090` |
| `ADMIN_DASHBOARD_CONTROL_API_TIMEOUT_SECONDS` | нет | `10` |
| `ADMIN_DASHBOARD_LAST_COMPLETED_COUNT` | нет | `20` |
| `ADMIN_DASHBOARD_POLL_INTERVAL_SECONDS` | нет | `5` |
**control-api:**
```bash
docker build --platform linux/amd64 -t cloud-ip-validator-control-api -f deploy/docker/control-api/Dockerfile .
docker run -d --platform linux/amd64 -p 8080:8080 --name control-api \
-v /path/to/control-api.yaml:/etc/cloud-ip-validator/control-api.yaml:ro \
-v cloud-ip-validator-db:/var/lib/cloud-ip-validator \
-e OS_AUTH_URL=<keystone_url> \
-e OS_PROJECT_ID=<project_id> \
-e OS_REGION_NAME=<region> \
-e OS_TOKEN=<token> \
cloud-ip-validator-control-api
```
В отличие от остальных трёх компонентов, у control-api конфиг **не**
генерируется из переменных окружения — он содержит списки
(`validators`/`sites`/`targets`/`ip_addresses`) и имена переменных для
OpenStack-креденшлов, которые проще смонтировать файлом (по образцу
`configs/control-api.example.yaml`, см.
[«Подготовка конфигурации»](#подготовка-конфигурации-для-реального-стенда)). Сама
база данных — отдельным volume (`cloud-ip-validator-db`) для
персистентности между перезапусками контейнера. При `auth_method:
password` вместо `OS_TOKEN` передайте `OS_USERNAME`,
`OS_USER_DOMAIN_NAME`, `OS_PASSWORD`; для `openstack.mode: mock`
переменные OpenStack не нужны вовсе.
**validator-agent:**
```bash
docker build --platform linux/amd64 -t cloud-ip-validator-validator-agent -f deploy/docker/validator-agent/Dockerfile .
docker run -d --platform linux/amd64 --cap-add NET_RAW --name validator-agent \
-e VALIDATOR_AGENT_VALIDATOR_ID=<validator_id> \
-e VALIDATOR_AGENT_CONTROL_API_URL=<http://control-api-host:port> \
cloud-ip-validator-validator-agent
```
| Переменная | Обязательна | По умолчанию |
|---|---|---|
| `VALIDATOR_AGENT_VALIDATOR_ID` | да | — (должен совпадать с `validators[].validator_id` в конфиге control-api) |
| `VALIDATOR_AGENT_CONTROL_API_URL` | да | — |
| `VALIDATOR_AGENT_POLL_INTERVAL_SECONDS` | нет | `5` |
| `VALIDATOR_AGENT_SELF_CHECK_TIMEOUT_SECONDS` | нет | `10` |
| `VALIDATOR_AGENT_HTTPS_TIMEOUT_SECONDS` | нет | `10` |
| `VALIDATOR_AGENT_ICMP_TIMEOUT_SECONDS` | нет | `5` |
| `VALIDATOR_AGENT_ICMP_COUNT` | нет | `3` |
| `VALIDATOR_AGENT_SSH_ENABLED` | нет | `false` |
| `VALIDATOR_AGENT_SSH_TIMEOUT_SECONDS` | нет | `5` |
`--cap-add NET_RAW` обязателен для ICMP-проверок, как и у `prober`.
`self_check.ip_echo_urls` в переменные не вынесен — при отсутствии в
конфиге агент сам подставляет дефолт (`api.ipify.org`, `ifconfig.me`);
свой список задавайте через смонтированный конфиг вместо шаблона, если
нужно переопределить.
### Обновление образов после изменения кода
Как уже сказано в требованиях — образы ничего не компилируют, а копируют
`bin/<компонент>`. После правок кода:
```bash
# 1. пересобрать бинарники (из корня репозитория)
export PATH=$PATH:/usr/local/go/bin
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build -trimpath -ldflags="-s -w" -o bin/control-api ./cmd/control-api
go build -trimpath -ldflags="-s -w" -o bin/validator-agent ./cmd/validator-agent
go build -trimpath -ldflags="-s -w" -o bin/prober ./cmd/prober
go build -trimpath -ldflags="-s -w" -o bin/admin-dashboard ./cmd/admin-dashboard
# 2. пересобрать и перезапустить образы — Docker сам заметит, что
# содержимое bin/ изменилось (COPY инвалидирует кэш слоя по хэшу
# файла), отдельный --no-cache не нужен
cd deploy/docker
docker compose up -d --build
```
Для варианта 3 (`docker build`/`docker run` вручную) — то же самое: шаг 1
не меняется, дальше `docker build ...` (та же команда, что и при первой
сборке) и `docker rm -f <имя> && docker run ... ` (или `docker restart`,
если менялись только переменные окружения, а не сам бинарник/образ).
### Диагностика Docker-развёртывания
- **Контейнер сразу падает, в логах `exec format error`** — образ собран
не под ту архитектуру: пересоберите с явным `--platform linux/amd64`
(или, если хост реально не amd64 — соберите бинарники под нужную
архитектуру, `GOARCH=arm64` и т.д., см.
[«Вариант B: сборка из исходников»](#вариант-b-сборка-из-исходников), и
уберите `--platform`/`platform:` из образов).
- **`prober`/`validator-agent`/`admin-dashboard` не стартуют, пишут
`... is required`** — не задана обязательная переменная окружения
(`PROBER_SITE_ID`, `VALIDATOR_AGENT_VALIDATOR_ID`,
`ADMIN_DASHBOARD_CONTROL_API_URL`) — см. таблицы выше/`.env`.
- **`prober`/`validator-agent` в цикле рестартов** — обычно означает, что
`control-api` недоступен по указанному URL, либо `site_id`/
`validator_id` не зарегистрирован в конфиге control-api. Смотрите
`docker compose logs -f <сервис>` — при ошибке регистрации процесс
завершается с ненулевым кодом и в dev/prod оверлеях перезапускается
политикой `restart`.
- **`docker compose config` ругается на `depends_on` для неактивного
профиля** — это специфика `docker-compose.override.yml` (dev): он
подходит только когда все четыре профиля включены на одном хосте. Для
разнесённого по хостам стенда используйте `docker-compose.prod.yml` (в
нём `depends_on` нет намеренно, см. [вариант 2](#вариант-2-docker-compose-реальный-стенд-по-нескольким-хостам)).
- **После `docker compose down -v` пропали данные** — `-v` удаляет и volume
с базой control-api (`cloud-ip-validator-db`); без `-v` volume
сохраняется между запусками.
- Общие команды диагностики: `docker compose ps`, `docker compose logs -f
[сервис]`, `docker compose exec control-api sh`, `curl -s
http://localhost:8080/healthz`.
## Проверка после запуска
После того как 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, который в данный момент привязан к валидатору.
- `validator-agent` → внешние IP-echo сервисы из `self_check.ip_echo_urls`
(по умолчанию `api.ipify.org`, `ifconfig.me`) — **обязательно вне
облака**: это и есть механизм self-check (см.
[DIAGRAMS.md](DIAGRAMS.md#2-поток-данных-от-валидатора-к-целевому-серверу-egress-проверка)).
Если валидатор не может достучаться ни до одного из этих адресов,
self-check никогда не пройдёт и IP будет бесконечно возвращаться в
очередь — см. [USAGE.md](USAGE.md#частые-проблемы-и-что-с-ними-делать).
- `control-api` → OpenStack Keystone/Neutron API (`OS_AUTH_URL` и далее по
каталогу сервисов).
- `admin-dashboard` → `control-api`: тот же порт (`server.listen_addr`),
адрес задаётся в `control_api.base_url` конфига дашборда.
- Оператор (браузер) → `admin-dashboard`: порт из `server.listen_addr`
дашборда (по умолчанию 8090).
API control-api сейчас не аутентифицирован (см. предупреждение в начале
[API.md](API.md)) — то же самое верно и для `admin-dashboard`, который
это API оборачивает. Ограничивайте доступ к обоим портам на уровне
сети/firewall: к control-api — теми хостами, где реально работают
валидаторы, проберы и сам дашборд; к дашборду — теми, кому разрешено
администрировать стенд.