Files
cloud-ip-validator/deploy/ansible/README.md
T
ayurishchevandClaude Sonnet 5.5 cf4a883363 ansible: fix container name and git user, refuse to start a second agent
The real container on the validators is named validator-agent (the image
is cloud-ip-validator-validator-agent); the playbook used the image name
as the container name, so it would have started a second agent next to
the old one with the same validator_id. The clone on the validators is
owned by root, so git must run as root (git_user), otherwise fetch fails
with "cannot open .git/FETCH_HEAD: Permission denied".

Preflight now stops when the host has another container of this agent
(by name or image) besides container_name.

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

11 KiB
Raw Blame History

Доставка validator-agent на валидаторы (Ansible)

Сценарий запускается с jump-хоста и на каждой ВМ-валидаторе: обновляет git-клон в /opt/cloud-ip-validator, собирает образ на самом хосте, останавливает и удаляет старый контейнер validator-agent (образ — cloud-ip-validator-validator-agent) и поднимает на его месте новый. Параметры запуска агента вынесены в env-файл.

Порядок безопасен: сначала проверки, обновление кода и сборка образа, и только потом замена контейнера. Если что-то упало до замены, старый контейнер продолжает работать. Простой валидатора — секунды (stop + rm + run).

Требования

Где Что
jump-хост (Debian 13) apt install ansible-core; SSH-доступ по ключу ко всем валидаторам
валидаторы Docker, git, клон репозитория в /opt/cloud-ip-validator, sudo без пароля для SSH-пользователя, доступ к Gitea и Docker Hub (alpine:3.20), архитектура x86_64

Только модули ansible.builtin: Python Docker SDK и дополнительные коллекции на валидаторах не нужны. Go на валидаторах не нужен: образ копирует закоммиченный bin/validator-agent (его сумма проверяется по bin/SHA256SUMS).

Подготовка (один раз)

cd deploy/ansible
cp env/validator-agent.env.example env/validator-agent.env
chmod 600 env/validator-agent.env
$EDITOR env/validator-agent.env          # адрес control-api, токен, способы самопроверки, таймауты
ansible validators -m ping               # проверка связи (20 хостов: validator-1 ... validator-20)
  • В inventory/hosts.yml 20 хостов validator-1 … validator-20 с адресами (ansible_host) и переменной validator_id (vkiplab-v1 … vkiplab-v20, как в control-api). Соответствие validator-N → vkiplab-vN задано по порядку номеров; сценарий сверяет validator_id с тем, что записано в работающем контейнере на хосте, и при расхождении останавливается до замены контейнера. VALIDATOR_AGENT_VALIDATOR_ID в env-файл писать не нужно: сценарий подставляет его из inventory.
  • Отпечатки хостов запоминаются при первом подключении (StrictHostKeyChecking=accept-new в ansible.cfg); сменившийся отпечаток известного хоста — ошибка.
  • SSH: пользователь debian и ключ ~/.ssh/vk_cloud_priv.key на jump-хосте (права 0600) — одинаковые на всех хостах; меняются в inventory/group_vars/validators.yml (ansible_user, ansible_ssh_private_key_file). Для docker и записи env-файла сценарий повышает права через sudo.
  • Токен в env-файле можно зашифровать: ansible-vault encrypt env/validator-agent.env, запускать с --ask-vault-pass. Рабочий env/validator-agent.env в git не попадает (.gitignore).

Запуск

cd deploy/ansible
ansible-playbook playbooks/deploy-validator-agent.yml --limit vkiplab-v1     # канарейка: один валидатор
ansible-playbook playbooks/deploy-validator-agent.yml                        # все валидаторы волнами
ansible-playbook playbooks/deploy-validator-agent.yml --check                # только проверки (preflight), без изменений

Волны по умолчанию: 1 хост, затем 4, затем все остальные (deploy_serial: [1, 4, "100%"]). Любой сбой в волне останавливает прогон: следующая волна не начнётся. Все сразу: -e '{"deploy_serial": ["100%"]}'.

Что выкатывается — deploy_ref (по умолчанию main): ветка, тег или коммит. Выкатить нужно запушенный коммит: валидаторы берут код из репозитория, а не с jump-хоста. После правок кода сначала пересоберите bin/validator-agent (см. SETUP.md) и закоммитьте его вместе с bin/SHA256SUMS.

Откат

ansible-playbook playbooks/deploy-validator-agent.yml -e deploy_ref=<предыдущий коммит или тег>

Для тега или коммита клон переходит в detached HEAD; следующий запуск с deploy_ref=main возвращает его на ветку.

Что делает сценарий на каждом хосте

  1. preflight — env-файл на jump-хосте существует и в нём задан VALIDATOR_AGENT_CONTROL_API_URL (адрес-пример example.com не принимается); на валидаторе отвечает Docker, есть git и клон, архитектура x86_64; validator_id из inventory совпадает с validator_id работающего контейнера; на хосте нет другого контейнера этого агента (по имени или образу) — иначе рядом со старым запустился бы второй с тем же validator_id. Показывает текущий контейнер.
  2. git — от имени git_user (на валидаторах root: клон принадлежит ему) fetch, затем клон сбрасывается на deploy_ref (checkout --force). Через git, а не модуль git: учётные данные, уже настроенные в клоне, не трогаются. Локальные правки отслеживаемых файлов в клоне будут сброшены; неотслеживаемые и игнорируемые (.env.*) — нет.
  3. build — проверка bin/validator-agent по bin/SHA256SUMS; docker build --platform linux/amd64 с контекстом в корне репозитория, образ получает метку ревизии (cloud-ip-validator-validator-agent:<хеш>) и latest.
  4. replace — env-файл копируется в /opt/cloud-ip-validator/deploy/docker/.env.validator (0600; путь закрыт .gitignore), затем docker stop → docker rm -f → docker run -d --restart unless-stopped --cap-add NET_RAW --env-file ….
  5. verify — ждёт строку registered в логе агента (регистрация в control-api), проверяет, что контейнер запущен, без перезапусков и на только что собранном образе. При неудаче хост падает с последними строками лога, следующие волны не стартуют. Затем остаются 3 последних образа с метками ревизий (keep_images), остальные и «висячие» удаляются.

Все шаги можно запускать по тегам: --tags preflight|git|build|replace|verify.

Параметры

env/validator-agent.env — запуск агента (читает deploy/docker/validator-agent/docker-entrypoint.sh): VALIDATOR_AGENT_CONTROL_API_URL, CONTROL_API_AGENT_TOKEN, VALIDATOR_AGENT_SELF_CHECK_METHODS, VALIDATOR_AGENT_SELF_CHECK_TIMEOUT_SECONDS, VALIDATOR_AGENT_POLL_INTERVAL_SECONDS, VALIDATOR_AGENT_HTTPS_TIMEOUT_SECONDS, VALIDATOR_AGENT_ICMP_TIMEOUT_SECONDS, VALIDATOR_AGENT_ICMP_COUNT, VALIDATOR_AGENT_SSH_ENABLED, VALIDATOR_AGENT_SSH_TIMEOUT_SECONDS. Смена только env-файла не требует изменения кода: достаточно запустить сценарий (контейнер пересоздаётся с новыми значениями).

inventory/group_vars/validators.yml — доставка: repo_dir, repo_remote, deploy_ref, git_user (пользователь, у которого в клоне настроен доступ к репозиторию; пусто — SSH-пользователь), image_name, container_name, platform, keep_images, restart_policy, capabilities, log_max_size, log_max_file, stop_timeout, verify_retries, verify_delay, local_env_file. Любой параметр переопределяется ключом -e.

Разбор сбоев

  • «Нет env-файла» / «не задан VALIDATOR_AGENT_CONTROL_API_URL» — см. «Подготовка».
  • «в запущенном контейнере validator_id=…, а в inventory …» — соответствие validator-N и validator_id в inventory/hosts.yml неверно для этого хоста: исправьте inventory (контейнер при этом не тронут).
  • Контейнер не прошёл проверку — в сообщении последние строки лога. registration failed — control-api недоступен с валидатора или validator_id не заведён в control-api. Контейнер остаётся на хосте для разбора (docker logs).
  • Permission denied на .git/FETCH_HEAD, dubious ownership, could not read Username — git запущен не от владельца клона или учётные данные есть у другого пользователя: задайте git_user (на валидаторах — root).
  • «найден другой контейнер агента» — на хосте есть контейнер с похожим именем или образом, не совпадающий с container_name: проверьте имя (docker ps -a) и при необходимости удалите лишний контейнер вручную.
  • bin/validator-agent не совпадает с суммой — в репозитории устарел bin/SHA256SUMS: пересоберите бинарник и обновите сумму.
  • Сборка падает на FROM alpine:3.20 / apk add — с валидатора нет доступа к Docker Hub / репозиториям Alpine.
  • Старый образ другого имени остаётся — сценарий чистит только образы image_name; образ с прежним именем удалите вручную (docker rmi).