Files
junhong_cmp_fiber/docs/deployment/production-runbook.md
break 370fd3e67f
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m49s
update
2026-09-03 09:28:28 +08:00

107 lines
9.9 KiB
Markdown
Raw Permalink 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.
# 生产环境运行说明
## 元数据
- 用途:区分生产与测试运行方式,并为人工发布、迁移和回滚提供事实入口。
- 适用范围:`xm-iot.cn` 的生产 API 与 Worker不适用于本地、测试环境或 Docker Compose。
- 事实源:生产维护者提供的主机与目录信息;实际 systemd Unit、线上 `.env.prod``migrate version` 输出优先于本文。
- Owner生产维护者。
- 最后核验2026-08-13基于维护者提供的信息未连接生产环境
- 更新触发Unit、目录、进程数、Worker 角色、发布顺序、迁移方式或回滚方式变化。
- 验证Agent 可通过 dbhub 对生产数据库执行只读诊断查询服务器上的只读核对、生产发布、迁移、回滚及其他写操作由维护者执行并回填结果Agent 不连接生产主机。
## 与测试环境的边界
生产环境不是仓库中的 Docker Compose 测试部署:
| 项目 | 生产环境 | 仓库 Compose/工作流 |
| --- | --- | --- |
| 平台 | Ubuntu 24.04 x86_64 | 测试环境自动部署 |
| 发布 | 本地交叉编译后手工上传二进制 | Gitea 构建、镜像与 Docker Compose |
| 进程管理 | systemd | Docker Compose |
| 运行单元 | 1 个 API、1 个调度 Worker、3 个执行 Worker | 不作为生产拓扑依据 |
| 配置 | 每个运行目录的 `.env.prod` | Compose `environment` |
| 回滚 | 覆盖为部署前备份的二进制,必要时恢复数据库备份 | 不适用 |
不得从 `.gitea/workflows/deploy.yaml``docker-compose.prod.yml` 或其中的测试配置推断生产地址、凭据、发布步骤或运行拓扑。
## 已确认运行布局
- 主机Ubuntu 24.04.4 LTS`x86_64`
- API 服务 `junhong-cmp-api.service`:工作目录与程序分别为 `/opt/junhong_cmp/api``/opt/junhong_cmp/api/api`,通过同目录 `.env.prod` 加载环境变量;旧二进制备份在 `releases/`
- 调度 Worker 服务 `junhong-cmp-worker.service`:工作目录与程序分别为 `/opt/junhong_cmp/worker``/opt/junhong_cmp/worker/worker`,通过根目录 `.env.prod` 加载环境变量。
- 三个执行 Worker 服务 `junhong-cmp-worker-1.service``junhong-cmp-worker-2.service``junhong-cmp-worker-3.service`:共享 `/opt/junhong_cmp/worker/worker` 这一份二进制;工作目录和 `EnvironmentFile` 分别是 `worker-1/``worker-2/``worker-3/` 下的 `.env.prod`。因此发布只替换根目录的 `worker`,不复制三份二进制。
- 五个 Unit 都以 `root``Restart=always``RestartSec=3` 运行,标准输出和错误写入 journald。
- Worker 拓扑:调度实例已确认 `JUNHONG_WORKER_ROLE=leader``JUNHONG_WORKER_INSTANCE_NAME=leader`;三个执行实例均为 `JUNHONG_WORKER_ROLE=consumer`。当前三个执行实例的 `JUNHONG_WORKER_INSTANCE_NAME` 都是 `worker-1`,会混淆日志与 Outbox Relay 实例标识;七月发布前须分别修正为 `worker-1``worker-2``worker-3`
- 域名:`xm-iot.cn`API 域名:`cmp-api.xm-iot.cn`
- 企微回调由自建企微中台 `wecom.xm-iot.cn` 配置;支付和运营商回调由各官方平台配置。
## 构建与上传
维护者当前在本地构建:
```bash
GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./build/api ./cmd/api
GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./build/worker ./cmd/worker
```
本次迭代更新上传 API 与 Worker 二进制;配置变化由维护者直接修改各运行目录的 `.env.prod`。生产迁移文件必须随本次发布上传到 API 目录下的 `migrations/`,并同步上传当前分支的 `scripts/migrate.sh`,供 `migrate` 记录和执行。生产服务器当前尚未安装 `migrate`;安装路径和版本待首次迁移前确认。
## 配置规则
- 二进制只从嵌入默认配置和 `JUNHONG_` 环境变量读取;`.env.prod` 必须由 systemd Unit 显式加载,或由 Unit 启动脚本 `source` 后启动。需要以 Unit 内容核实实际方式。
- API 与 Worker 共享数据库、Redis、日志、JWT、对象存储、Gateway、短信、支付等基础配置只记录键名不将实际凭据写入仓库文档。
- 七月新增的 API/Worker 共用配置:`JUNHONG_APPROVAL_LEGACY_REFUND_MANUAL_ENABLED``JUNHONG_APPROVAL_LEGACY_OFFLINE_RECHARGE_PAY_ENABLED``JUNHONG_WECOM_BASE_URL``JUNHONG_WECOM_TIMEOUT`
- 本次新增的 Worker 配置:`JUNHONG_WORKER_ROLE``JUNHONG_WORKER_INSTANCE_NAME``JUNHONG_WORKER_POLLING_TOTAL_MAX_CONCURRENCY``JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED``JUNHONG_WORKER_AUDIT_ARCHIVE_TASKS_ENABLED`。轮询总并发必须保持在 1-1000初始生产值建议 100归档/留存总开关默认关闭。
- 首发要求:退款人工入口、线下充值人工确认、企微审批、运营商实名回调、微信/支付宝在线充值均按维护者决定启用;审计物理清理保持关闭。
- 企微应用凭据由后台配置写入数据库明文字段;这不是启动环境变量。本文不记录其值。
## 人工发布与迁移
已确认顺序:备份二进制和数据库 → 上传二进制及迁移文件 → 停止 systemd 服务 → 执行迁移 → 启动服务。
生产当前迁移版本已于 2026-08-13 由维护者在 API 目录验证为 `140`(非 dirty。历史迁移已归档生产从 `140` 向后执行根目录 `migrations/``141+` 迁移;七月分支最终目标为 `209`。根目录不存在 `173` 号迁移,这是正常编号空档。执行前先以显式 `DB_*` 参数运行:
```bash
DB_HOST=<生产主机> DB_PORT=<端口> DB_USER=<用户> \
DB_PASSWORD='<密码>' DB_NAME=<库名> DB_SSLMODE=<模式> \
./scripts/migrate.sh version
DB_HOST=<生产主机> DB_PORT=<端口> DB_USER=<用户> \
DB_PASSWORD='<密码>' DB_NAME=<库名> DB_SSLMODE=<模式> \
./scripts/migrate.sh up
```
迁移失败时不启动新二进制;按失败迁移的事务状态决定处理,必要时恢复已确认可用的数据库备份。启动失败时覆盖回部署前备份的二进制,再恢复数据库备份(如迁移已改变数据库)。
### 零金额退款发布后核验
发布本次退款审批变更后,维护者应先等待既有重试处理稳定事件 `approval:26:approved`;若重试已耗尽,按受控运维流程重放同一事件,不得直接修改退款、订单或钱包数据。随后核验:
1. 退款单 `RF20260820170954264700` 已通过,关联订单支付状态为已退款。
2. 该退款没有代理主钱包或资产钱包的零金额回款流水。
### 锁的含义与发布影响
`000171` 会对 `tb_agent_wallet``000178` 会对 `tb_iot_card` 使用 PostgreSQL `ACCESS EXCLUSIVE` 锁。该锁执行期间会阻塞该表的读写及其他 DDL直到迁移事务提交或回滚若有未结束业务查询/事务,它也会等待。因此必须在 API 和全部 Worker 停止后执行,并在迁移前检查没有长事务。锁持续时间取决于表数据量、索引创建和等待中的旧事务;不能从仓库估算具体秒数。
## 待维护者确认的最小信息
1. `migrate` 的安装路径、版本和迁移文件上传命令;确认 `140` 到首个根目录迁移版本之间是否存在待补的迁移文件。
2. 数据库恢复的准确命令、恢复前提,以及发布前新建备份的执行人。
3. 已停止服务后的锁前检查命令/结果(至少确认无长事务),以及可接受维护窗口。
4. 企微中台转发到 API 的最终回调路径;支付与运营商平台配置的回调 URL 清单(可脱敏域名外路径)。
## 已确认数据库备份
每日凌晨 02:00 自动备份 `junhong_cmp_prod`:数据库运行在 Docker 容器 `postgres` 中,备份脚本执行 `pg_dump -Fc -Z 6`,写入 `/data/backups/postgresql/<库名>_<时间>.dump`,同时生成 MD5 文件并以 `pg_restore --list` 校验结构;保留 30 天。发布前仍须人工新建一次备份并确认校验通过,不能只依赖凌晨的最近备份。恢复命令待维护者实际演练或确认后补充。
## 日审计日志留存启用与恢复
归档与日留存由调度 Worker 受控处理 Asia/Shanghai 的已结束自然日;`JUNHONG_WORKER_AUDIT_ARCHIVE_TASKS_ENABLED=false` 时不注册调度,已入队任务也安全跳过、不扫描在线日志表。开启后每次最多推进一个日期。`JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false` 时仅验证归档、对象、manifest、数据库数量和日期连续性不写清理断点、不删除在线数据。Integration 的 `pending` 记录保留在线但不会阻断同日终态日志、Audit 数据或后续日期的清理;若 pending 后续终结Worker 会重建该日 revision 后续跑删除。
1. 发布新 Worker 后保持 `JUNHONG_WORKER_AUDIT_ARCHIVE_TASKS_ENABLED=false` 和物理清理开关关闭;轮询总并发先设为 100。维护者先核对 `tb_log_archive_run` 中 Audit 与 Integration 两个来源从历史最早在线日期起没有缺失账本;缺失日期必须先受控补归档,不能跳过失败日。
2. 以关闭开关的 Worker 完成只读演练,观察 `/opt/junhong_cmp/worker/logs/audit-retention.log`:每个日期应有来源计数和耗时;若出现日期、来源、失败分类或安全错误摘要,先修复该来源的归档或校验问题后再演练。仅有 pending 不需要人工终结才可继续。
3. 低峰期先将调度 Worker 的 `JUNHONG_WORKER_AUDIT_ARCHIVE_TASKS_ENABLED=true`,重启该 Worker确认单日归档稳定后再将 `JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=true`,并持续观察上述独立日志、`tb_log_archive_run.cleanup_started_at` / `cleaned_at` 断点及表大小。DELETE 释放的 PostgreSQL 页面会供后续写入复用,表文件不会立即缩小;维护者按既有运维窗口评估 VACUUMWorker 不执行 VACUUM 或表重写。
4. 异常时立即将开关改回 `false` 并重启调度 Worker。已清理日期按已验证的对象和 manifest 执行归档恢复;尚未清理或阻断的日期仍保留在线,无需数据库恢复。恢复后先重新执行只读演练,再决定是否重新开启清理。