Files
cloud-ip-validator/docs/SETUP.md
T
ayurishchevandClaude Sonnet 5.5 abbee9a08a Add self-check via control-api (self_check.methods)
control-api is hosted outside the cloud and validators reach it directly,
so it sees the floating IP as the connection's source address. New open
route GET /api/v1/agents/{id}/observed-ip returns that address (taken only
from the TCP peer; forwarding headers are ignored so a validator cannot
forge it).

The agent gets self_check.methods, a priority-ordered list of ip_echo
(unchanged) and control_api; the default stays [ip_echo]. The self-check
passes when any method confirms the address; the next method is tried on
no answer and on a mismatch. Each method has its own timeout so a hung
first method cannot starve the fallback, and control_api uses a new TCP
connection per call (a connection opened before the floating IP was
attached would keep reporting the old address).

Also: docker agent template/env, example config, docs, plan in
docs/changes, e2e script switch E2E_SELF_CHECK_METHODS, rebuilt
bin/control-api and bin/validator-agent.

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

54 KiB
Raw Blame History

Подготовка стенда и первичная инициализация

Документ описывает, как подготовить конфигурацию и запустить стенд с нуля — от чистой машины до работающего control-api, валидаторов и проберов. Бинарники брать не обязательно из исходников: в репозитории уже лежат готовые сборки для Linux x86_64 (bin/) — это самый быстрый путь к развёртыванию, см. «Получение бинарников». Если нужно просто быстро посмотреть систему в работе без реального OpenStack — сразу переходите к разделу «Быстрая проверка без OpenStack». Ниже описано развёртывание как systemd-юнитами (по умолчанию), так и 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, 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» и далее).

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" (без путей сборки и отладочной информации — компактнее и без утечки информации о машине сборки).

Убедиться в отсутствии динамических зависимостей и целостности файлов после переноса на целевой сервер:

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

Перенос на целевые серверы, например:

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: сборка из исходников

Из корня репозитория:

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).

Убедиться, что всё собирается и юнит-тесты проходят:

go build ./... && go test ./...

Пересобирайте из исходников и обновляйте bin/ с зафиксированными SHA256SUMS, если меняли код — готовые бинарники в репозитории не обновляются автоматически:

sha256sum bin/control-api bin/validator-agent bin/prober bin/admin-dashboard | sed 's#bin/##' > bin/SHA256SUMS

Быстрая проверка без OpenStack (offline-режим)

Прежде чем разворачивать реальный стенд, рекомендуется убедиться, что всё собирается и работает корректно на локальной машине — без облака и внешних площадок:

scripts/run-local-e2e.sh

Скрипт сам поднимает control-api (в режиме openstack.mode: mock), одного validator-agent и трёх проберов как локальные процессы, прогоняет один тестовый адрес через полный цикл проверки и печатает итоговый результат. Подробности — в docs/LOCAL_E2E.md. Это же хороший способ разобраться в поведении системы перед первым боевым запуском.

Подготовка конфигурации для реального стенда

Все три компонента конфигурируются YAML-файлами. Шаблоны лежат в configs/*.example.yaml — скопируйте их и заполните под ваш стенд.

1. control-api.yaml

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 и 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, пока вы вручную не перевыпустите токен, не обновите переменную и не перезапустите процесс.

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 и автоматически переполучает новый при истечении текущего (весь срок жизни процесса, без ручного вмешательства). Компромисс — в файле окружения лежит долгоживущий пароль, а не токен.

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 (свой на каждом валидаторе)

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 (свой на каждой площадке)

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.

Где Переменная Назначение
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

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), YAML для этой секции больше не перечитывается ни при одном последующем рестарте — источник истины переключается на БД. Это осознанное отличие от более ранних версий, где validators из YAML переприменялись при каждом рестарте: теперь правки, сделанные через admin API (например, смена os_port_id валидатора), переживают рестарт вместо того, чтобы тихо откатываться.
  2. Всегда аддитивно добавляет в очередь все адреса из ip_addresses, которых там ещё нет (уже обработанные ранее адреса повторно не добавляются и не сбрасываются — см. USAGE.md). Это отдельный, не завязанный на bootstrap-once путь — не путайте с POST /api/v1/admin/ips, который умеет то же самое (и ещё принудительный повтор уже проверенных адресов) без перезапуска.

Проверить, что процесс поднялся:

curl -s http://localhost:8080/healthz
# {"ok":true}
journalctl -u control-api -f

Развёртывание validator-agent на ВМ-валидаторах

Повторить на каждой ВМ-валидаторе:

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 на внешних площадках

Аналогично, на каждой из трёх площадок:

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). На любой машине с сетевым доступом до control-api:

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.

Развёртывание в 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-режим)

Самый быстрый способ увидеть всю систему целиком работающей — без реального облака, все четыре сервиса на одной машине:

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, а не пытаться скачать несуществующие в реестре.

Проверить, что всё поднялось:

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 успешно зарегистрировались):

docker compose logs -f control-api
docker compose logs -f prober validator-agent admin-dashboard

Остановить:

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):

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):

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» ниже или прямо в docker-compose.yml/.env.prod.example.

Вариант 3: docker build/docker run по одному компоненту

Для точечной пересборки/перезапуска одного сервиса напрямую, без compose — например, обновить только prober на одной площадке, не трогая остальной стенд. Все команды — из корня репозитория.

prober:

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:

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:

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:

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 и self_check.methods в переменные не вынесены — при отсутствии в конфиге агент сам подставляет дефолты (api.ipify.org, ifconfig.me и methods: [ip_echo]); свои значения задавайте через смонтированный конфиг вместо шаблона, если нужно переопределить. methods — способы самопроверки в порядке приоритета (ip_echo, control_api), достаточно подтверждения любым. Способ control_api спрашивает у control-api, с какого адреса он видит валидатора (GET /agents/{id}/observed-ip); при внешнем размещении control-api рекомендуется [control_api, ip_echo]. Ограничение: если control-api достижим из облака по внутренней сети, он увидит приватный адрес валидатора и этот способ всегда даст несовпадение — используйте ip_echo.

Обновление образов после изменения кода

Как уже сказано в требованиях — образы ничего не компилируют, а копируют bin/<компонент>. После правок кода:

# 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: сборка из исходников», и уберите --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).
  • После 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, все валидаторы и все три пробера запущены:

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.

Также стоит убедиться, что все валидаторы видны и не «зависли»:

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 способом ip_echo (при self_check.methods с control_api достаточно ещё и доступа к control-api по внешней сети; см. DIAGRAMS.md). Если валидатор не может достучаться ни до одного из этих адресов, self-check никогда не пройдёт и IP будет бесконечно возвращаться в очередь — см. 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) — то же самое верно и для admin-dashboard, который это API оборачивает. Ограничивайте доступ к обоим портам на уровне сети/firewall: к control-api — теми хостами, где реально работают валидаторы, проберы и сам дашборд; к дашборду — теми, кому разрешено администрировать стенд.