petstore-docs/生产发布与回滚Runbook-2026-08-01.md

226 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 宠小它生产发布与回滚 Runbook
> 版本2026-08-01
>
> 适用范围35 家独立宠物洗护单店试点backend、admin、H5、微信小程序。
>
> 当前结论:仓库交付到 `Ready for Release`,未取得真实生产域名、凭据、备份和线上验收证据前,不得标记 `Released`。
## 1. 发布原则
1. 发布单元必须冻结为四元组:`backend commit + admin commit + frontend commit + docs/RC manifest commit`,同时记录 jar/静态包校验和。
2. 生产数据库只通过已审核 SQL 迁移;应用固定 `ddl-auto=validate`,禁止启动期改表或修复数据。
3. 后端预检和自动 smoke 均为只读;创建预约、开始服务、提交报告等写路径只在明确指定的试点门店和人工验收窗口执行。
4. 应用、静态站点采用版本目录加 `current` 软链接切换;数据库迁移为向前兼容的追加式变更,不做自动向下迁移。
5. Petstore 使用独立域名、Nginx `server` 块、systemd unit 和发布目录。不得覆盖、重启或复用同机 Gitea/GitLab 的配置与服务。
6. 任何命令输出都不得包含数据库密码、微信 AppSecret、session secret、完整报告 token、手机号或私密媒体 URL。
## 2. 本次发布任务卡
| 字段 | 值 |
|---|---|
| Task Name | 建立可重复的生产发布与回滚闭环 |
| Role | Backend Ops / Frontend Release Ops |
| Owner Agent | Backend Ops后端与 Nginx/ Frontend Release Opsadmin、H5、小程序 |
| Paired QA | Core Flow QA / Report Share QA |
| Workstream | Release |
| Track | Ops |
| Status | Ready for Release生产证据待补 |
| Priority | P0 |
| Repo/Path | `backend/deploy/**`、前端发布配置、`docs/**` |
| Service Lock | Petstore backend unit + Petstore 专用 Nginx server blocks + Petstore static roots |
| Depends On | 冻结 RC、真实域名/TLS、备份、五个迁移、微信合法域名 |
| Acceptance | 预检、构建、readiness、只读 smoke、三角色人工 smoke、回滚演练均有证据 |
| RC Included | Yes |
| Due Date | TBD |
| Output Link | 当前 Runbook + RC manifest |
| Blocker Owner | 生产环境负责人(域名/凭据/变更窗口) |
| Commit / RC | Not Frozen提交后写入 RC manifest |
| Architecture Review Required | Yes |
| Data Model Review Required | Yes迁移顺序与回滚边界 |
| Access / Privacy Review Required | Yes |
## 3. 立即停止条件
出现任一情况立即停止,不继续切流:
- 四仓 commit、构建产物或校验和与 RC manifest 不一致;
- 生产域名仍为 localhost、`.invalid`、`api.s-good.com` 或 `www.s-good.com` 等非本项目域名;
- 数据库备份未完成、校验和缺失或未在隔离库验证可恢复;
- 任一迁移末尾验证查询非 0或预检的 18 项只读不变量非 0
- `nginx -t` 失败,或 diff 显示将修改 Gitea/GitLab 的 server block
- readiness 非 `UP`,上传目录/FFmpeg/数据库任一依赖失败;
- 发现凭据、完整 token、手机号或私密媒体地址进入日志/工单/截图;
- 无明确值班人、回滚执行人或生产变更授权。
## 4. 发布前冻结
RC Steward 在 `docs/qa-reports` 创建当次 manifest至少记录
- 四仓 commit、分支、`git status --short`(必须为空);
- Java/Node/npm/Maven 版本;
- jar、admin `dist`、H5 `dist`、小程序构建目录的 SHA-256
- 目标环境、变更窗口、操作者、审核人;
- 本次包含/排除项、已知风险、旧版本回滚点;
- 域名只记 host不记 query/token配置只记变量名和“已配置”不记值。
冻结后重新运行本地门禁:
```bash
cd /Users/apple/_src/petstore/backend
mvn test
mvn -DskipTests package
bash -n deploy/release-preflight.sh deploy/production-smoke.sh
cd /Users/apple/_src/petstore/admin
npm run build
cd /Users/apple/_src/petstore/frontend
npm run build:h5
npm run build:mp-weixin
cd /Users/apple/_src/petstore
python3 docs/graph/validate_ontology.py docs
python3 docs/graph/audit_drift.py
```
## 5. 数据库备份与迁移
### 5.1 备份证据
使用权限受控的 MySQL option file避免把密码写在命令行。备份必须包含表结构、数据、触发器、事件和存储过程并记录文件大小、SHA-256、开始/结束时间。示例:
```bash
mysqldump --defaults-extra-file=/secure/path/petstore-mysql.cnf \
--single-transaction --routines --triggers --events --hex-blob \
petstore > /secure/backup/petstore-before-<rc>-<timestamp>.sql
shasum -a 256 /secure/backup/petstore-before-<rc>-<timestamp>.sql
```
上线前必须把该备份恢复到隔离数据库并完成基础计数核对;“命令退出 0”不能替代恢复演练。备份保留周期由数据负责人决定本流程不自动删除任何备份。
### 5.2 固定迁移顺序
在维护窗口中按以下顺序人工执行,每一步都保存执行时间和脚本 SHA-256并检查脚本末尾验证查询
1. `backend/db/migrations/20260801_split_service_identity.sql`
2. `backend/db/migrations/20260801_create_store_customer.sql`
3. `backend/db/migrations/20260801_create_business_event.sql`
4. `backend/db/migrations/20260801_create_booking_capacity.sql`
5. `backend/db/migrations/20260801_create_report_send_status.sql`
禁止跳步、交换顺序或让应用自动补表。任一验证项非 0停止发布并由 Data Model/业务 owner 判断修复方案;不得猜测性回填客户身份、报告作者或事件。
### 5.3 生产只读预检
`backend/deploy/petstore-backend.env.example` 复制到服务器权限受控的 `/etc/petstore/backend.env` 并填入真实值;文件权限建议 `0600`,所有者为服务账号。不要在交互 shell 中 `source` 该文件,也不要用 `env $(cat ...)`,避免特殊字符被 shell 解释或泄露。通过一次性 systemd unit 以与正式服务相同的 `EnvironmentFile` 和服务账号执行:
```bash
systemd-run --wait --pipe --collect \
--unit=petstore-preflight-<rc> \
--property=Type=oneshot \
--property=User=petstore \
--property=Group=petstore \
--property=WorkingDirectory=/opt/petstore/backend/releases/<rc> \
--property=EnvironmentFile=/etc/petstore/backend.env \
/usr/bin/env BUILD_FIRST=0 \
JAR_PATH=/opt/petstore/backend/releases/<rc>/petstore-backend.jar \
./deploy/release-preflight.sh
```
通过条件静态配置检查通过、JPA `validate` 通过、18 项数据不变量全部为 0并以退出码 0 结束。预检不会执行迁移或修复数据。
## 6. 构建发布产物
### 6.1 后端
将已冻结 jar 放入 `/opt/petstore/backend/releases/<rc>/petstore-backend.jar`,同时归档 commit、SHA-256 和 `deploy/` 模板。systemd unit 以 `backend/deploy/petstore-backend.service.example` 为基线,由服务器管理员安装为 Petstore 独立 unit。
### 6.2 Admin 与 H5
真实域名配置保存在权限受控文件,不提交仓库。门禁和构建必须使用同一份内容:
```bash
cd /opt/build/petstore/admin
PETSTORE_ADMIN_ENV_FILE=/secure/path/petstore-admin.env npm run preflight:release
cp /secure/path/petstore-admin.env .env.production.local
npm run build
cd /opt/build/petstore/frontend
PETSTORE_FRONTEND_ENV_FILE=/secure/path/petstore-h5.env npm run preflight:release
cp /secure/path/petstore-h5.env .env.production.local
npm run build:h5
```
产物进入版本目录后再切换 `current` 软链接。构建结束应清除工作区内临时 `.env.*.local`,但保留受控配置源和构建证据。
### 6.3 微信小程序
```bash
cd /opt/build/petstore/frontend
PETSTORE_FRONTEND_ENV_FILE=/secure/path/petstore-mp.env npm run preflight:release
cp /secure/path/petstore-mp.env .env.mp-weixin.local
npm run build:mp-weixin
```
微信公众平台的 `request` / `downloadFile` 合法域名必须与门禁通过的实际域名一致。先上传体验版并完成真机三角色验收,再决定提审/发布;后端或 H5 发布不会自动更新用户手机里的小程序。
## 7. 切换应用与 Nginx
1. 将后端 `current` 指向新版本目录,执行 `systemctl daemon-reload`,只重启 Petstore 独立 unit。
2. 确认进程稳定并通过本机 readiness再切 admin/H5 静态目录 `current`
3.`backend/deploy/nginx-petstore.conf.example` 新建 Petstore 专用配置文件;替换域名、证书路径和静态根目录。
4. 执行 `nginx -t`。检查配置 diff 仅增加/修改 Petstore 的三个 server block不改 Gitea/GitLab upstream、端口、证书和 location。
5. 只执行 Nginx reload不停止 Nginxreload 后同时检查 Petstore、Gitea、GitLab 的既有健康入口。
证书续签必须沿用服务器现有 ACME/证书管理方式,为 Petstore 新域名单独申请/续签;不要替换其他产品证书。续签后先 `nginx -t` 再 reload并记录证书到期时间。
## 8. 上线验证
### 8.1 自动只读 smoke
```bash
cd /opt/petstore/backend/current
API_ORIGIN=https://<api-domain> ./deploy/production-smoke.sh
```
脚本验证 liveness、readiness、公开门店、服务时长和可约容量契约提供受控的 `SMOKE_BEARER_TOKEN` 时才增加两个 protected GET。脚本不会创建或修改业务数据。
### 8.2 三角色人工 smoke
仅在预先标记的试点门店和测试宠主上执行,记录匿名化预约 ID/报告 ID不记录完整 token/手机号:
1. **boss**:登录 admin核对门店营业时段、并发容量、服务时长创建员工查看工作台、客户、日程。
2. **staff**:代客预约,开始服务,上传最小合规素材,提交一约一报告,复制报告话术;确认重复提交被拒绝。
3. **customer**:小程序自助预约并验证长服务跨时段占号;打开匿名报告页;提交提醒留资;再次预约。
4. **权限反例**customer 不可进入后台A 店账号不可读取 B 店预约/报告/线索;公开报告响应不含内部身份字段。
5. **异常反例**:无容量、非法状态迁移、缺前/后照片、错误 token、FFmpeg 失败均有可理解提示。
## 9. 回滚
### 9.1 应用与静态资源回滚
- 保留前一版本目录和软链接目标。若新版本 readiness、错误率或主链路失败先停止切流把 backend/admin/H5 的 `current` 恢复到 RC manifest 中的前一版本,再只重启 Petstore unit、`nginx -t` 并 reload。
- 微信小程序不能靠服务器软链接回滚。应在公众平台保留上一稳定版本,按微信流程回退;同时确认其 API 契约仍受当前后端兼容。
- 回滚后重新执行只读 smoke并记录故障窗口、影响门店、回滚时间和 owner。
### 9.2 数据库回滚边界
本批五个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration也不把已产生的新业务数据覆盖回旧备份。
只有确认发生灾难性数据破坏、已冻结所有写入、明确接受备份时间点之后的数据丢失,并取得用户/业务负责人、DBA 和隐私责任人的书面授权后,才可执行整库恢复。恢复必须先在隔离库验证,再制定专门变更单;本 Runbook 不授权该操作。
## 10. 发布完成定义
只有以下证据全部归档RC 才能由 `Ready for Release` 改为 `Released`
- 四仓冻结 commit 与产物校验和;
- 备份及隔离恢复验证;
- 五个迁移执行和末尾验证结果;
- 最终配置门禁与后端只读预检;
- `nginx -t`、readiness、自动只读 smoke
- boss/staff/customer 三角色人工 smoke
- Gitea/GitLab 回归未受影响;
- 监控、告警联系人、回滚点和值班窗口;
- 未解决问题清单及 Go/No-Go 决策人。