1. deploy/check-prod-readiness.sh — pre-deploy gating: backup<60min, disk≥5GB на /opt+/var/lib/docker, /health/ready=Healthy, .env required-keys без placeholder'ов. --ssh-host для удалённой проверки. 2. deploy/prod-deploy.sh <api-tag> <web-tag> — blue-green release: pull → green-контейнер на :8088 → migrations (auto) → smoke (/health/ready + /api/me с тест-токеном) → nginx upstream switch → swap → docker compose up -d с обновлённым тэгом. Failure → удаление green, blue остаётся. --skip-web флаг. 3. deploy/prod-rollback.sh <to-tag> — docker pull (если нужно) → docker compose up -d --force-recreate с указанным tag'ом → wait /health/ready до 120с. --dry-run + --skip-web. 4. deploy/post-deploy-smoke.sh — 10 шагов (signup → login → /api/me → list products/counterparties/stores/stock → create+delete product → logout-via-session). JSON парсится через python3 (не grep — споткнулись на пробеле перед `:` в access_token). Telegram-alert через FM_TG_TOKEN/CHAT при провале. Stage-тест: 10/10 ✓. 5. deploy/db-schema-diff.sh — pg_dump --schema-only с обоих хостов через ssh+docker exec, нормализация (sed), diff -u. Exit: 0=идентичны, 1=разница, 2=ошибка. 6. deploy/generate-release-notes.sh <from-tag> <to-tag> — git log group by prefix через awk: feat→✨, fix→🐛, perf→⚡, docs→📚, test/refactor/chore→<details>. Сохраняет docs/release-notes/<tag>.md. 7. .forgejo/workflows/auto-tag.yml — на push в main: если HEAD не помечен → создаёт v<YYYYMMDD>.<N> annotated tag, push в origin, генерирует release-notes для будущего деплоя. Все скрипты идемпотентные, поддерживают --dry-run, не трогают прод. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
103 lines
6.7 KiB
Markdown
103 lines
6.7 KiB
Markdown
# Sprint 21 — stage→prod migration toolchain
|
||
|
||
Цель: набор скриптов и workflow'ов чтобы первый прод-деплой был не
|
||
импровизацией, а одной командой с понятным rollback'ом и автоматическими
|
||
проверками.
|
||
|
||
Старт: 2026-06-07 (после Sprint 20). Исполнитель: Claude Opus 4.7.
|
||
|
||
## Принципы
|
||
|
||
- Все mutating-скрипты поддерживают `--dry-run` — печатают что бы сделали.
|
||
- Прод НЕ трогается из этого спринта (только инструментарий).
|
||
- Скрипты — идемпотентные: повторный запуск не ломает уже сделанное.
|
||
- Failure-режим: оставляем старый контейнер, не каскадим ошибку.
|
||
- НЕ трогать: `global.json`, prod admin.food-market.kz, POS WPF.
|
||
|
||
## Чек-лист
|
||
|
||
- [x] **1. Pre-deploy check** — `deploy/check-prod-readiness.sh`:
|
||
backup<60min, disk ≥5GB на `/opt` + `/var/lib/docker`, `/health/ready`
|
||
Healthy, .env содержит required keys без placeholder'ов (CHANGEME/
|
||
REPLACE_ME/TODO/dev). Поддерживает `--ssh-host` для удалённой
|
||
проверки. Опциональная CI-проверка через `FM_CHECK_CI=1`.
|
||
- [x] **2. Blue-green deploy** — `deploy/prod-deploy.sh <api-tag> <web-tag>`:
|
||
pull → green-контейнер `food-market-api-next` на :8088 → migrations
|
||
(auto через `Database.Migrate()` в Program.cs) → smoke
|
||
(`/health/ready` + `/api/me`) → nginx upstream switch → swap green→blue.
|
||
Failure → удаляем green, оставляем blue. `--skip-web` для api-only.
|
||
- [x] **3. Rollback** — `deploy/prod-rollback.sh <to-tag>`:
|
||
`docker image inspect` + fallback `docker pull` → `docker compose up
|
||
-d --force-recreate api web` с указанным tag'ом → wait /health/ready
|
||
до 120с. `--skip-web` поддерживается.
|
||
- [x] **4. Post-deploy smoke** — `deploy/post-deploy-smoke.sh`:
|
||
10 сценариев (signup → login → /api/me → list 5 endpoints → create
|
||
product → delete → logout-via-session). Парсит JSON через `python3`,
|
||
не grep/cut (после первого фейла на токене с пробелом перед `:`).
|
||
Telegram-alert через `FM_TG_TOKEN+FM_TG_CHAT` при провале.
|
||
**На stage прогнал 10/10 ✓** (тестовая прогонка).
|
||
- [x] **5. Stage-prod schema diff** — `deploy/db-schema-diff.sh`:
|
||
`pg_dump --schema-only --no-owner --no-privileges --no-comments`
|
||
с обоих хостов через `ssh + docker exec`, нормализация (sed убирает
|
||
SET/SELECT pg_catalog.set_config/пустые строки/комментарии), `diff -u`.
|
||
Exit 0 = идентичны, 1 = разница, 2 = ошибка получения.
|
||
- [x] **6. Release notes generator** — `deploy/generate-release-notes.sh
|
||
<from-tag> <to-tag>`: `git log` группирует по prefix через `awk`:
|
||
feat → ✨ Новые возможности, fix → 🐛 Исправления, perf → ⚡, docs → 📚,
|
||
test/refactor/chore → `<details>`-свёрнутые. Сохраняет в
|
||
`docs/release-notes/<to-tag>.md`. **Прогнал на HEAD~3..HEAD** — markdown
|
||
сгенерирован корректно.
|
||
- [x] **7. Auto-tag workflow** — `.forgejo/workflows/auto-tag.yml`:
|
||
на push в main → если HEAD ещё не помечен → создаёт `v<YYYYMMDD>.<N>`
|
||
где N — порядковый счётчик в дне. Annotated tag (`git tag -a`),
|
||
push в origin, дополнительно генерирует release-notes для будущего
|
||
деплоя (через `deploy/generate-release-notes.sh`).
|
||
|
||
## Журнал
|
||
|
||
### 2026-06-07 старт
|
||
Sprint 20 закрыт (7/7 ✓). Поехали по prod-toolchain.
|
||
|
||
### 2026-06-07 итог
|
||
Все 7 пунктов ✓. Прод не трогали (по правилам спринта).
|
||
|
||
**Тестовые прогоны на stage** (тест.admin.food-market.kz):
|
||
- `post-deploy-smoke.sh`: **10/10 ✓** (signup, login, /api/me, 5×list,
|
||
create+delete product, logout-via-session). Первая попытка упала
|
||
на парсинге access_token (пробел перед `:` в JSON), починил через
|
||
`python3 -c 'json.load(...)'`. Вторая — на отсутствующем
|
||
`/connect/revocation` (OpenIddict не сконфигурирован для revoke),
|
||
заменил step 10 на DELETE сессии через `/api/me/sessions/{id}` с
|
||
fallback'ом на token-valid sanity-check.
|
||
- `check-prod-readiness.sh`: на stage backup-dir `/opt/food-market-data/
|
||
backups` не существует (это нормально для stage'а) — FAIL, как и
|
||
ожидалось. Скрипт фиксирует это с конкретной причиной.
|
||
- `generate-release-notes.sh HEAD~3..HEAD`: markdown с группировкой
|
||
по prefix сгенерирован, файл сохранён в `docs/release-notes/`.
|
||
- `db-schema-diff.sh` и `prod-deploy.sh` / `prod-rollback.sh` запускать
|
||
на dev-vm против prod невозможно без SSH-настройки — синтаксис
|
||
проверен `bash -n`. Реальный прогон только когда user разрешит.
|
||
|
||
## Итог
|
||
|
||
Все 7 пунктов ✓. Инструментарий для прод-деплоя готов:
|
||
|
||
| Скрипт | Назначение | --dry-run | Прогон |
|
||
|---|---|---|---|
|
||
| `check-prod-readiness.sh` | Pre-deploy gating | ✓ | partial (на stage) |
|
||
| `prod-deploy.sh` | Blue-green release | ✓ | синтаксис |
|
||
| `prod-rollback.sh` | Fast rollback | ✓ | синтаксис |
|
||
| `post-deploy-smoke.sh` | Smoke-suite 10 шагов | ✓ | 10/10 ✓ (stage) |
|
||
| `db-schema-diff.sh` | Stage↔prod schema | ✓ | синтаксис |
|
||
| `generate-release-notes.sh` | Markdown release | ✓ | ✓ (HEAD~3..HEAD) |
|
||
| `.forgejo/workflows/auto-tag.yml` | Auto-tag CI | — | синтаксис (yaml) |
|
||
|
||
Что нужно от пользователя для реального прода:
|
||
- `/etc/nginx/conf.d/food-market-upstream.conf` на prod-vm с
|
||
`upstream food_market_api { server 127.0.0.1:8080; }` (создать пустым,
|
||
скрипт `prod-deploy.sh` его перепишет).
|
||
- SSH-ключ от dev-vm на prod-vm + alias `prod` в `~/.ssh/config`.
|
||
- `FM_TG_TOKEN` / `FM_TG_CHAT` env-vars для notify (опц.).
|
||
- Решение поднять prod-БД и сделать первый backup (`food-market-backup.sh`).
|
||
- Решение о dns/sertbot для admin.food-market.kz.
|