Qadam Roadmap
проектdocs/operations/deployment-runbook.md

Runbook эксплуатации и деплоя

Обновлён 14 апр. 2026 г., 17:16 · 0 комментариев

Runbook эксплуатации и деплоя

Паспорт документа

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 runtime qadam-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 Registry git.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-level qadam-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.example
  • deploy/compose/docker-compose.api-runtime.yml
  • deploy/scripts/build-api-image.sh
  • deploy/scripts/publish-api-image.sh
  • deploy/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 pull registry-tag без локальной пересборки;
  • поднимает shadow runtime на 127.0.0.1:3002 через deploy/compose/docker-compose.web-runtime.yml;
  • использует app env из /etc/qadam/qadam-web-runtime.env, где server-side API_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-level qadam-web.service.

Канонические файлы этого слоя:

  • Dockerfile
  • .dockerignore
  • deploy/compose/docker-compose.web-runtime.yml
  • deploy/env/web-runtime.env.example
  • deploy/scripts/build-web-image.sh
  • deploy/scripts/publish-web-image.sh
  • deploy/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

Если после обновления приложение не стартует:

  1. Смотри journalctl по qadam-api-container, qadam-api, qadam-web и qadam-roadmap.
  2. Если проблема в API runtime-cutover, сначала выполни pnpm -C /data/qadam-core docker:rollback:api.
  3. Проверяй корректность /data/qadam-web/apps/web/.next/standalone, /data/qadam-web/apps/roadmap/.next/standalone, image tag в /etc/qadam/qadam-api-runtime.env и миграций Prisma.
  4. Откатывай код или image tag к предыдущему рабочему состоянию.
  5. Повторяй сборку и рестарт только тех сервисов, которые реально затронуты.
  6. Если проблема только в 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.