Runbook эксплуатации и деплоя
Обновлён 14 апр. 2026 г., 17:16 · 0 комментариев
Runbook эксплуатации и деплоя
Паспорт документа
- Статус документа: living document
- Актуально на: 2 апреля 2026 года
- Владелец: backend/platform-команда
- Пересмотр: при изменении deploy-контура, инфраструктуры, rollback-модели или runtime topology
- Область применения: эксплуатационный и migration-слой production-инфраструктуры проекта
- Связанные документы:
- Текущее состояние
- Roadmap
- Post-deploy checklist
- Инженерные принципы
- Модель stage delivery и release handoff
1. Текущая production-модель
Важно не путать этот документ с stage release-процессом.
Этот runbook описывает:
- текущий production/runtime-контур данного сервера;
- локальные operational команды;
- rollback и host/container topology именно этой машины.
Этот runbook не утверждает, что stage-сервер должен обновляться вручную с этой машины. Каноническая модель stage release зафиксирована отдельно в stage-delivery-model.md: локально мы готовим change packages, а отдельный stage-контур забирает main автоматически.
На текущем сервере Qadam развёрнут в смешанном runtime-контуре:
- backend checkout находится в
/data/qadam-core; - frontend checkout находится в
/data/qadam-web; apiобслуживает production через container runtimeqadam-api-container.service;- legacy
qadam-api.serviceсохранён как rollback-контур и не должен считаться активным production runtime по умолчанию; - product frontend и roadmap-service пока запускаются через
systemd; - для
qadam-webуже подтверждён registry-backed shadow runtime на127.0.0.1:3002, но он пока не обслуживает production-трафик; - внешний трафик обслуживает
nginx; - PostgreSQL и Redis установлены на хосте;
- TLS обслуживается через Let’s Encrypt.
При этом важно:
- Docker Engine на сервере уже установлен;
- его
data-rootвынесен в/mnt/qadam100gb/docker; - для
qadam-core/apiуже подтверждены shadow smoke, registry delivery и production cutover; - для
qadam-webуже подтверждены registry publish/pull и shadow smoke от registry image, но production всё ещё канонически обслуживается черезsystemd.
Дополнительные важные факты:
- этот сервер уже переведён на deploy из
qadam-coreиqadam-web; - roadmap-портал читает документы из
qadam-core/docsчерезQADAM_PROJECT_ROOT=/data/qadam-core; - host nginx использует named upstream
qadam_api_backendи сейчас проксирует/api/*на127.0.0.1:5002; - для API уже существует source-controlled reversible cutover path между
qadam-api.serviceиqadam-api-container.service; - старый checkout
/data/uzbekбольше не является production source of truth и должен считаться legacy-копией.
Важно: legacy-скрипты вне deploy/ и старые Docker-артефакты в корне репозитория не являются источником истины для этого production-сервера.
План полного перехода на новый container runtime описан отдельно в docs/operations/docker-contour-migration-plan.md. До завершения этого migration именно этот runbook остаётся каноническим для текущего mixed runtime на split-репозиториях.
Смежные обязательные документы:
2. Домены
Публичный домен
https://qadam.2fab.app
Внутренний портал документации
https://qadam-roadmap.2fab.app- защищён
basic auth - пароль хранится на сервере в
/root/qadam-roadmap-basic-auth.txt
3. Ключевые файлы на сервере
/etc/systemd/system/qadam-api.service/etc/systemd/system/qadam-api-container.service/etc/systemd/system/qadam-web.service/etc/systemd/system/qadam-roadmap.service/etc/systemd/system/qadam-core-runner.service/etc/systemd/system/qadam-monitor.service/etc/systemd/system/qadam-monitor.timer/etc/systemd/system/qadam-backup.service/etc/systemd/system/qadam-backup.timer/etc/docker/daemon.json/etc/nginx/sites-available/qadam.2fab.app.conf/etc/nginx/sites-available/qadam-roadmap.2fab.app.conf/etc/nginx/conf.d/qadam-api-upstream.conf/etc/qadam/qadam.env/etc/qadam/qadam-api-runtime.env/etc/qadam/qadam-web-runtime.env/etc/qadam/qadam-monitor.env/etc/qadam/qadam-backup.env/etc/qadam/qadam-roadmap.env/opt/act_runner/qadam-core/.runner
4. Сервисы
Проверка статуса
systemctl status qadam-api-container qadam-web qadam-roadmap nginx
systemctl status qadam-api --no-pager
systemctl status qadam-core-runner
systemctl status qadam-monitor.timer qadam-backup.timer --no-pager
Перезапуск
systemctl restart qadam-api-container
systemctl restart qadam-web
systemctl restart qadam-roadmap
systemctl restart qadam-core-runner
systemctl start qadam-monitor.service
systemctl start qadam-backup.service
systemctl reload nginx
Логи
journalctl -u qadam-api-container -n 200 --no-pager
journalctl -u qadam-api -n 200 --no-pager
journalctl -u qadam-web -n 200 --no-pager
journalctl -u qadam-roadmap -n 200 --no-pager
journalctl -u qadam-core-runner -n 200 --no-pager
journalctl -u qadam-monitor.service -n 200 --no-pager
journalctl -u qadam-backup.service -n 200 --no-pager
journalctl -u nginx -n 200 --no-pager
5. Переменные окружения
Боевые переменные лежат в /etc/qadam/qadam.env.
Для image-based API runtime боевые переменные лежат в /etc/qadam/qadam-api-runtime.env.
Для shadow/container runtime product web боевые runtime env лежат в /etc/qadam/qadam-web-runtime.env.
Для отдельного roadmap-service боевые переменные лежат в /etc/qadam/qadam-roadmap.env.
Для monitoring и backup policy боевые переменные лежат в /etc/qadam/qadam-monitor.env и /etc/qadam/qadam-backup.env.
Ключевые группы переменных:
- runtime:
NODE_ENV,PORT,HOSTNAME - база данных:
DATABASE_URL,DATABASE_REPLICA_URL,POSTGRES_PASSWORD - auth:
JWT_SECRET - инфраструктура:
REDIS_URL,CORS_ORIGIN,NEXT_PUBLIC_API_URL,API_URL - roadmap-портал:
QADAM_PROJECT_ROOT,QADAM_ROADMAP_STORAGE_DIR,PORT=3001,HOSTNAME=127.0.0.1 - API runtime image:
QADAM_API_IMAGE,QADAM_API_PORT,QADAM_API_HOSTNAME,QADAM_API_ENV_FILE,QADAM_API_COMPOSE_PROJECT - web runtime image:
QADAM_WEB_IMAGE,QADAM_WEB_PORT,QADAM_WEB_BIND_HOST,QADAM_WEB_ENV_FILE,QADAM_WEB_COMPOSE_PROJECT - интеграции:
TELEGRAM_BOT_TOKEN,TELEGRAM_ALERT_CHAT_ID,SELLER_TELEGRAM_BOT_TOKEN,SELLER_TELEGRAM_BOT_USERNAME,SELLER_TELEGRAM_WEBHOOK_SECRET,SMTP_HOST,SMTP_PORT,SMTP_SECURE,SMTP_USER,SMTP_PASS,SMTP_FROM,SMTP_REPLY_TO,SELLER_STATUS_CHANGE_NOTIFY_STATUSES,AXIOM_TOKEN,AXIOM_DATASET - backup runtime:
QADAM_BACKUP_ROOT_DIR,QADAM_BACKUP_OFFSITE_PREFIX,QADAM_BACKUP_*RETENTION_DAYS
Секреты не дублируются в репозиторий.
6. Порядок обновления приложения на хосте
Этот раздел относится именно к текущему серверу и нужен как operational/rollback runbook.
Он не должен автоматически трактоваться как обязательный релизный процесс для stage, если stage-контур разворачивается отдельной автоматикой из main.
cd /data/qadam-core
pnpm install --frozen-lockfile
pnpm check-types
pnpm test
pnpm build
pnpm export:openapi
pnpm --filter @repo/prisma migrate:deploy
pnpm docker:build:api
pnpm docker:publish:api
# далее обновить QADAM_API_IMAGE в /etc/qadam/qadam-api-runtime.env на нужный git-<commit-sha> tag
pnpm docker:cutover:api
cd /data/qadam-web
pnpm install --frozen-lockfile
set -a
source /etc/qadam/qadam.env
export API_URL=http://127.0.0.1:5002/api/v1
set +a
pnpm generate:api-contract
pnpm check-types
pnpm build
systemctl restart qadam-web
systemctl restart qadam-roadmap
systemctl reload nginx
Если менялись только отдельные пакеты, допускается более узкая сборка, но канонический путь для production на этом сервере — полная проверка и полная сборка. Для api канонический runtime-update теперь идёт через image tag и pnpm docker:cutover:api, а не через прямой systemctl restart qadam-api.
Repo-side CI для qadam-core теперь обслуживается локальным self-hosted runner:
- сервис:
qadam-core-runner - рабочий каталог:
/opt/act_runner/qadam-core - регистрационное состояние runner:
/opt/act_runner/qadam-core/.runner - workflow source:
/data/qadam-core/.gitea/workflows/quality-gate.yml - runner cache и hostexecutor workspace вынесены в
/tmp/act_runner-cacheи/tmp/act_runner-tmp, чтобы CI не упирался вENOSPCна корневом разделе - runner состоит в группе
docker, поэтому workflow умеет собирать API image и запускать container smoke локально на хосте - для runner уже настроен
docker loginв Gitea Container Registrygit.2fab.app, поэтому workflow может публиковать API image без ручного ввода credentials
Если Quality Gate падает на ENOSPC, канонический порядок проверки такой:
df -h / /tmp
systemctl status qadam-core-runner
readlink -f /opt/act_runner/.cache
journalctl -u qadam-core-runner -n 200 --no-pager
Нормальное состояние:
/opt/act_runner/.cacheуказывает в/tmp/act_runner-cache;qadam-core-runner.serviceвыставляетXDG_CACHE_HOME=/tmp/act_runner-cacheиTMPDIR=/tmp/act_runner-tmp;- root disk не используется как основное место для runner workspace и Prisma cache.
Если релиз меняет API-контракт, перед рестартом должен проходить и контрактный sync-check:
cd /data/qadam-core
pnpm export:openapi
cd /data/qadam-web
pnpm generate:api-contract
pnpm check:api-contract
Если релиз затрагивает канонические документы или delivery-процесс, дополнительно обязателен:
cd /data/qadam-core
pnpm check:docs
6A. Контейнерный smoke для qadam-core/api
Этот сценарий пока не заменяет канонический host deploy, но уже является обязательной инженерной проверкой для CP-301.
Сейчас у него два канонических режима:
cd /data/qadam-core
pnpm smoke:api-container
Этот сценарий поднимает временные postgres и redis контейнеры в отдельной docker network, применяет migrations внутри API image и проверяет health/ready и metrics без обращения к production БД.
Второй сценарий проверяет уже сам runtime-manifest на текущем production-хосте, но без cutover:
cd /data/qadam-core
pnpm docker:build:api
pnpm docker:smoke:api-runtime
Он:
- собирает API image с каноническим тегом
qadam-core-api:git-<commit>; - поднимает
deploy/compose/docker-compose.api-runtime.ymlкак shadow-runtime на127.0.0.1:5002; - использует production env из
/etc/qadam/qadam.env, но не останавливает host-levelqadam-api; - проверяет
GET /api/v1/health/readyиGET /api/v1/metrics; - после проверки удаляет shadow-container.
Ожидаемое поведение:
- image собирается без обращения к
/var/lib/dockerна корневом разделе; - shadow-container поднимается на
127.0.0.1:5002; GET /api/v1/health/readyотвечает200;GET /api/v1/metricsотвечает200на loopback;- host-level
qadam-apiна127.0.0.1:5001продолжает работать штатно.
Если нужно пройти publish/pull путь через registry, канонический сценарий такой:
cd /data/qadam-core
pnpm docker:build:api
pnpm docker:publish:api
QADAM_API_IMAGE=git.2fab.app/eldar/qadam-core-api:git-<commit-sha> pnpm docker:smoke:api-runtime
Ожидаемое поведение:
- image публикуется в Gitea Container Registry
git.2fab.app/eldar/qadam-core-api; - registry-tag можно поднять через тот же shadow smoke без локальной пересборки;
- host-level
qadam-apiостаётся активным и не прерывается.
Канонический template delivery-обвязки для такого запуска лежит в:
deploy/env/api-runtime.env.exampledeploy/compose/docker-compose.api-runtime.ymldeploy/scripts/build-api-image.shdeploy/scripts/publish-api-image.shdeploy/scripts/smoke-api-runtime-compose.sh
6C. Registry-backed shadow smoke для qadam-web
Для product web container delivery на production-хосте пока каноничен не cutover, а shadow smoke от готового registry image:
cd /data/qadam-web
pnpm install --frozen-lockfile
set -a
source /etc/qadam/qadam.env
export API_URL=http://127.0.0.1:5002/api/v1
set +a
pnpm build
pnpm docker:build:web
pnpm docker:publish:web
QADAM_WEB_IMAGE=git.2fab.app/eldar/qadam-web-app:git-<commit-sha> pnpm docker:smoke:web-runtime
Этот сценарий:
- собирает лёгкий runtime image из уже готового
.next/standalone,.next/staticиpublic; - публикует его в Gitea Container Registry
git.2fab.app/eldar/qadam-web-app; - при необходимости делает
docker pullregistry-tag без локальной пересборки; - поднимает shadow runtime на
127.0.0.1:3002черезdeploy/compose/docker-compose.web-runtime.yml; - использует app env из
/etc/qadam/qadam-web-runtime.env, где server-sideAPI_URLдолжен указывать наhttp://host.docker.internal:5002/api/v1.
Ожидаемое поведение:
GET http://127.0.0.1:3002/api/healthотвечает200;- главная страница product web отвечает
200и содержит ожидаемый<title>; - production-трафик на
https://qadam.2fab.appпри этом продолжает обслуживаться host-levelqadam-web.service.
Канонические файлы этого слоя:
Dockerfile.dockerignoredeploy/compose/docker-compose.web-runtime.ymldeploy/env/web-runtime.env.exampledeploy/scripts/build-web-image.shdeploy/scripts/publish-web-image.shdeploy/scripts/smoke-web-runtime-compose.sh
6B. Reversible cutover для api
Для текущего production API канонический переключатель выглядит так:
cd /data/qadam-core
pnpm docker:cutover:api
Этот сценарий:
- читает
/etc/qadam/qadam-api-runtime.env; - поднимает
qadam-api-container.serviceна127.0.0.1:5002; - ждёт локальный
health/ready; - переводит
/etc/nginx/conf.d/qadam-api-upstream.confна новый порт; - перезагружает
nginx; - проверяет публичный
https://qadam.2fab.app/api/v1/health/ready; - останавливает legacy
qadam-api.serviceтолько после успешного public health.
Канонический rollback:
cd /data/qadam-core
pnpm docker:rollback:api
Он:
- поднимает legacy
qadam-api.serviceна127.0.0.1:5001; - ждёт локальный и публичный
health/ready; - переводит
qadam_api_backendобратно на5001; - останавливает
qadam-api-container.service.
7. Smoke-check после деплоя
Полный operational checklist смотри в post-deploy-checklist.md. Ниже остаётся минимальный канонический smoke-set.
curl -I https://qadam.2fab.app
curl https://qadam.2fab.app/api/v1/health
curl https://qadam.2fab.app/api/v1/health/live
curl https://qadam.2fab.app/api/v1/health/ready
curl -I https://qadam.2fab.app/api/docs
curl https://qadam.2fab.app/api/openapi.json
curl http://127.0.0.1:5002/api/v1/metrics | head
curl -I https://qadam.2fab.app/roadmap
curl -I -u <login>:<password> https://qadam-roadmap.2fab.app
curl -u <login>:<password> https://qadam-roadmap.2fab.app/api/health
bash -lc 'set -a; source /etc/qadam/qadam.env; set +a; cd /data/qadam-core && pnpm telegram:alert -- --message "<b>Qadam</b> monitoring smoke test"'
Ожидаемое поведение:
https://qadam.2fab.app/roadmapдолжен возвращать404;https://qadam.2fab.app/api/v1/health/liveиhttps://qadam.2fab.app/api/v1/health/readyдолжны отвечать200;http://127.0.0.1:5002/api/v1/metricsдолжен отдавать Prometheus-compatible text/plain, а внешнийhttps://qadam.2fab.app/api/v1/metrics— резаться на403;pnpm telegram:alertдолжен доставлять тестовое сообщение в operational Telegram-канал, заданный черезTELEGRAM_ALERT_CHAT_ID;https://qadam-roadmap.2fab.appтребуетbasic auth;https://qadam-roadmap.2fab.app/api/healthдолжен отвечать{"status":"ok"}после аутентификации.
Дополнительно полезно проверить, что runtime действительно ушёл на новые checkout-пути:
systemctl show qadam-api -p ExecStart -p WorkingDirectory
systemctl show qadam-api-container -p ExecStart -p WorkingDirectory
systemctl show qadam-web -p ExecStart -p WorkingDirectory
systemctl show qadam-roadmap -p ExecStart -p WorkingDirectory
systemctl show qadam-core-runner -p ExecStart -p WorkingDirectory
systemctl show qadam-monitor -p ExecStart -p WorkingDirectory
systemctl show qadam-backup -p ExecStart -p WorkingDirectory
Дополнительно для monitoring baseline полезно проверить timer:
systemctl status qadam-monitor.timer --no-pager
systemctl status qadam-monitor.service --no-pager
cat /var/lib/qadam-monitor/state.json
Дополнительно для backup baseline полезно проверить:
systemctl status qadam-backup.timer --no-pager
systemctl status qadam-backup.service --no-pager
cat /var/lib/qadam-backup/state.json
find /var/backups/qadam -maxdepth 1 -mindepth 1 | sort
Нормальное состояние для qadam-backup.service после успешного ручного или timer-run запуска — inactive (dead) с status=0/SUCCESS, потому что это Type=oneshot, а не long-running daemon.
8. SSL и renewal
- Сертификаты лежат в
/etc/letsencrypt/live/. - Renewal выполняется через
certbot.timer. - После обновления сертификатов
nginxдолжен быть перезагружен.
Полезные команды:
systemctl status certbot.timer
certbot certificates
certbot renew --dry-run
9. Rollback
Если после обновления приложение не стартует:
- Смотри
journalctlпоqadam-api-container,qadam-api,qadam-webиqadam-roadmap. - Если проблема в API runtime-cutover, сначала выполни
pnpm -C /data/qadam-core docker:rollback:api. - Проверяй корректность
/data/qadam-web/apps/web/.next/standalone,/data/qadam-web/apps/roadmap/.next/standalone, image tag в/etc/qadam/qadam-api-runtime.envи миграций Prisma. - Откатывай код или image tag к предыдущему рабочему состоянию.
- Повторяй сборку и рестарт только тех сервисов, которые реально затронуты.
- Если проблема только в frontend, можно временно откатить web без отката БД и без rollback API runtime.
Rollback не должен использовать разрушительные git-команды без понимания состояния рабочей директории.
Если rollback связан с данными или storage, использовать backup-restore-runbook.md, а не импровизировать вручную.
10. Что ещё нужно улучшить
- Сделать автоматизированный post-deploy smoke script.
- Поверх базового Telegram alerting добавить более зрелый alert routing и dashboards.
- Довести
qadam-webот registry-backed shadow runtime до production cutover и rollback-процедуры. - После стабилизации mixed runtime удалить legacy host-only API deploy steps.