Выкатка и откат

> Кратко. Веб собирают в отдельном рабочем дереве и подменяют каталог .output целиком, при остановленных службах. API переводят fast-forward на заранее известный коммит и перезапускают службы по одной. На каждом шаге есть проверка и автоматический возврат, а откат — тот же скрипт с ключом -Rollback.

Ниже — порядок, по которому устроены скрипты выкатки этого проекта (Windows, службы NSSM). Пути даны как пример раскладки, у вас они могут быть другими.

Раскладка на сервере

Что Пример
Клон репозитория API (код, venv, .env) H:\deploy\tgid-server\itwin-api
Веб: исходники, node_modules, .env, рабочая сборка .output H:\deploy\tgid-web
Рабочее дерево git для сборок веба H:\deploy\tgid-web-build…
Журналы скриптов и служб H:\deploy\logs
Состояние выкатки для отката (JSON с прежним коммитом) H:\deploy\backups

Службы:

Служба Что Порт
tgid-api-multi, tgid-api-multi-2 API на все города, по одному процессу uvicorn; nginx делит запросы между ними (least_conn) 8010, 8017
tgid-worker-multi-1…N Воркеры Celery, -P solo, очереди всех городов —
tgid-web, tgid-web-<город> Веб, по службе на город, общий каталог .output свой у каждого
tgid-martin Тайлы сети 3005

Два процесса API, а не --workers 2: на Windows второй процесс uvicorn при общем сокете стоит в accept() до следующего соединения и не отвечает — иногда часами.

Веб

Почему не собирать в рабочем каталоге. Сервер Nuxt держит манифест сборки в памяти. Новая сборка в том же .output удаляет старые файлы _nuxt/*.js, а работающие службы продолжают вставлять их в страницы. В итоге карта не грузится, пока службы не перезапустят.

  1. Подготовка. В рабочем дереве сборки — нужный коммит: git -C <дерево> checkout --detach <коммит>. node_modules в дереве — ссылка (junction) на node_modules рабочего веба.
  2. Сборка. node build-tgid-web-with-env.mjs <путь к .env веба> <дерево>. Скрипт читает .env в окружение (уже заданные переменные не перекрывает), значения не печатает и запускает npm run build. Ключи подложек, адрес API и каталог слоёв GeoServer запекаются при сборке.
  3. Проверка сборки. Запустите .output/server/index.mjs из дерева на свободном порту и откройте страницу.
  4. Предпроверки скрипта выкатки (ничего не меняют):
    • службы tgid-web* есть, рабочая и новая сборки на месте;
    • дерево сборки на ожидаемом коммите;
    • ни один JS-файл новой сборки не совпадает по имени с работающей. Имена солятся коммитом сборки, поэтому у вернувшихся пользователей не смешаются две сборки;
    • резервной копии этой выкатки (.output.bak-<дата>) ещё нет, то есть выкатку не запускали.
  5. Подмена. Остановить все службы tgid-web* → рабочий .output переименовать в .output.bak-<дата> → новый .output перенести на его место → запустить службы. Веб недоступен 20–40 с.
  6. Проверка. Для каждого города запрашивается страница /tgid/<город>/, из неё берётся entry-скрипт _nuxt/…. Обе ссылки должны ответить 200. Не поднялся хоть один город — службы снова останавливаются и возвращается прежняя сборка.
  7. Откат (-Rollback): та же подмена в обратную сторону. Неудачная сборка уходит в .output.rolled-back-<время>.

После выкатки пользователям достаточно обновить страницу (Ctrl+F5).

API

  1. Предпроверки (ничего не меняют):
    • git fetch; ожидаемый коммит есть;
    • текущий HEAD — его предок, то есть возможен fast-forward;
    • в клоне нет изменённых файлов;
    • обе службы API отвечают на /health;
    • файла состояния этой выкатки ещё нет.
  2. Состояние. Прежний коммит записывается в JSON в каталоге резервных копий — по нему работает откат.
  3. Код. git merge --ff-only <коммит>. Слияний и правок на сервере нет: только переход на проверенный коммит.
  4. Импорт. venv\Scripts\python.exe -c "import main" в каталоге API. Не импортируется — git reset --keep на прежний коммит, службы не трогались.
  5. Миграции UsersDB, если в выпуске есть новая миграция в migrations/versions: alembic upgrade head до перезапуска. API сам UsersDB не мигрирует, а без новой колонки модель пользователя может уронить вход. Перед этим — резервная копия UsersDB. Схему баз городов API доводит сам при запуске (Несколько городов).
  6. Перезапуск по одной. Сначала tgid-api-multi-2, ждать ответа /health до 3 минут, затем tgid-api-multi. Пока одна служба перезапускается, nginx отправляет запросы во вторую — пользователи перерыва не замечают.
  7. Не поднялась — git reset --keep на прежний коммит и снова перезапуск обеих.
  8. Откат (-Rollback): git reset --keep на коммит из файла состояния, перезапуск, файл состояния переименовывается в ….rolled-back.

Воркеры и движок

  • Изменился worker.py или задачи Celery — воркеры перезапускают мягко. Команда shutdown идёт через брокер, воркер -P solo принимает её между задачами: дорабатывает текущий расчёт и выходит. NSSM поднимает службу с новым кодом. Перезапуск средствами NSSM ждёт полторы секунды и обрывает идущий расчёт.
  • Код heatnet перезапуска не требует: каждый расчёт — новый процесс.
  • Ядро Rust (heatnet/_core.pyd) выкатывается отдельно: прежний файл переименовывается, а не перезаписывается, потому что идущий расчёт держит его открытым. Затем — пробные расчёты на копии с HEATNET_CORE=check.
  • Переменные служб (движок, флаги) хранятся в параметрах службы NSSM (AppEnvironmentExtra в HKLM\SYSTEM\CurrentControlSet\Services\<служба>\Parameters). Меняйте их через реестр: PowerShell 5.1 искажает кавычки в аргументах nssm set.

Остановка и запуск служб

  • Остановка: nssm stop, затем ожидание до 90 с. Не остановилась — процесс службы завершается принудительно, ещё 30 с ожидания.
  • Запуск: nssm start, ожидание до 60 с, одна повторная попытка.
  • Сообщение NSSM «Unexpected status SERVICE_STOP_PENDING» — не ошибка: скрипт ждёт сам.

Журналы выкатки

  • Вывод скрипта — logs\<имя скрипта>.log. При ошибке пришлите его целиком.
  • Службы пишут в logs\<служба>.out.log и logs\<служба>.err.log; NSSM ротирует их по 10 МБ. Ошибки запуска API — в tgid-api-multi*.err.log, веба — в tgid-web*.err.log.

Чего не делать

  • Не собирать веб в каталоге, из которого работают службы.
  • Не править код на сервере: выкатка — только fast-forward на коммит, который уже проверен.
  • Не перезапускать обе службы API одновременно и не перезапускать воркеры средствами NSSM во время расчётов.
  • Не передавать python.exe аргументы с двойными кавычками внутри из PowerShell 5.1: кавычки срезаются, и команда ломается.

Если не получается

  • Предпроверки не пройдены — скрипт ничего не менял. Причина в списке: нет коммита (не сделан push), не fast-forward (на сервере другой коммит), изменённые файлы в клоне.
  • «Уже есть …json — выкатку запускали» — выкатку уже выполняли. Если её надо отменить — откат, повторить — после отката.
  • /health не отвечает после перезапуска — смотрите tgid-api-multi*.err.log. Скрипт к этому моменту уже вернул прежний код.
  • /health отвечает 200, а что-то не работает — проверьте поле status. Скрипты считают ответ 200 успехом, но /health отвечает 200 и при "status": "degraded", то есть без базы или Redis — Состояние и журналы.
Хотите увидеть эту функцию на своих данных?Покажем ТГИД на демонстрации и ответим на вопросы инженеров.
Запросить демо