169 lines
8.2 KiB
Markdown
169 lines
8.2 KiB
Markdown
# 测试 → 生产 增量部署规范
|
||
|
||
> 原则:生产上线永远是增量,没有全量。每次上线 = 一批「增量单元」(代码 + 数据库变更 + 配置),
|
||
> 每个单元必须可幂等重放、可验证、可回退。本规范固化四步:备份 → 操作 → 验证 → 故障回退。
|
||
|
||
## 0. 目录约定(在 pipeline-app 仓库)
|
||
|
||
```
|
||
deploy/
|
||
├── METHODOLOGY.md # 本文档
|
||
├── dmig.py # 迁移引擎:台账表 + up/down + 幂等
|
||
├── ddiff.py # 环境差异比对:代码模块版本 / schema / 参数
|
||
├── dbackup.py # 增量表备份(mysqldump)+ 恢复命令生成
|
||
└── migrations/
|
||
├── m0001_xxx.json # 迁移单元(编号递增,字典序 = 执行序)
|
||
└── ...
|
||
```
|
||
|
||
## 1. 增量单元(一次上线携带什么)
|
||
|
||
每次从测试推向生产,变更分成三类,各走各的通道,**不允许混**:
|
||
|
||
| 类型 | 载体 | 通道 |
|
||
|---|---|---|
|
||
| 代码 | 各模块仓库提交 | 生产机 `git pull --ff-only` + `pip install`(非 editable 模块)+ 清 `__pycache__` + 重启 |
|
||
| 数据库变更 | `deploy/migrations/mNNNN_*.json` | `dmig.py`(测试先跑 → 生产复核后执行) |
|
||
| 系统配置 | `params` 表 / 环境变量 | 走 `dmig.py` 的 `params_set`/`env` 类型,或记录在迁移说明里人工核对 |
|
||
|
||
**铁律**:
|
||
1. 测试环境执行过的库变更,必须在提交测试代码的同时登记成迁移文件——**没有登记就没有测试通过**。
|
||
2. 生产执行迁移前必须先跑 `ddiff.py`,拿差异清单与迁移批次对照,**清单里没有对应迁移的差异 = 遗漏**。
|
||
3. 破坏性操作(DROP/DELETE/TRUNCATE)永远生成命令由人工在生产执行,自动化只做到「打印 [NEEDS_APPROVAL]」。
|
||
|
||
## 2. 迁移单元格式(migrations/mNNNN_名称.json)
|
||
|
||
```json
|
||
{
|
||
"id": "m0001",
|
||
"title": "说明(一句话:这次改了什么、为什么)",
|
||
"backup_tables": ["sd_projects"],
|
||
"up": [
|
||
{"op": "sql", "sql": "ALTER TABLE ..."},
|
||
{"op": "create_table", "name": "xxx", "columns": "...完整 CREATE TABLE 语句..."},
|
||
{"op": "params_set", "key": "tender_api_base", "value": "http://..."}
|
||
],
|
||
"down": [
|
||
{"op": "sql", "sql": "DROP TABLE xxx"},
|
||
{"op": "params_del", "key": "tender_api_base"}
|
||
]
|
||
}
|
||
```
|
||
|
||
规则:
|
||
- `id` 唯一且递增;执行顺序 = 文件名字典序。
|
||
- `up` 的每一步都必须**幂等**:`CREATE TABLE IF NOT EXISTS`、`ALTER TABLE ... ADD COLUMN IF NOT EXISTS`(MySQL 8 用先查 information_schema 的方式,dmig 会自动做存在性检查)、`INSERT ... ON DUPLICATE KEY UPDATE`。
|
||
- `down` 是 `up` 的精确逆操作;无逆操作的步骤(如纯数据修复)`down` 写空数组并在 title 注明「不可逆」。
|
||
- `backup_tables` 列出本次会改动的表——`dmig.py apply` 前自动备份这些表(生产备份由人工确认后执行)。
|
||
|
||
## 3. 四步流程
|
||
|
||
### 第 1 步:数据备份(上线前必做,不可跳)
|
||
|
||
```bash
|
||
# 生产机执行。dmig 会自动列出本次批次涉及的表;也可手工指定。
|
||
cd /d/doit/pipeline-app
|
||
./py3/bin/python deploy/dbackup.py backup --batch # 备份本批次所有 backup_tables
|
||
./py3/bin/python deploy/dbackup.py backup --tables sd_projects,pipeline_tasks # 手工补充
|
||
# 备份文件:/d/doit/pipeline-app/backups/<db>_<table>_<日期时间>.sql,同目录生成 RESTORE 命令文件
|
||
```
|
||
|
||
检查点:
|
||
- [ ] 备份文件存在且非空(`ls -lh backups/`)
|
||
- [ ] 备份文件头部有 `CREATE TABLE` 且行数与线上一致(`dbackup.py verify <file>`)
|
||
|
||
### 第 2 步:操作(按序执行增量)
|
||
|
||
**顺序铁律:先库后码再重启**。新代码可能引用新列/新表,代码先上会在重启后
|
||
`Unknown column` 崩溃(2026-09-01 生产实录)。
|
||
|
||
```bash
|
||
# 2.1 差异核对(必做):打印生产与测试的差异清单,与本次批次对照
|
||
./py3/bin/python deploy/ddiff.py --checklist
|
||
|
||
# 2.2 数据库增量先行(迁移引擎)
|
||
./py3/bin/python deploy/dmig.py status # 看台账:哪些已执行、哪些待执行
|
||
./py3/bin/python deploy/dmig.py plan # 预演:打印本批次将执行的 SQL(不落库)
|
||
./py3/bin/python deploy/dmig.py apply # 执行(自动先备份本批次表,逐条落库,逐条记账)
|
||
|
||
# 2.3 代码增量(库就绪后才上代码)
|
||
cd /d/doit/pipeline-app/pkgs/<module> && git pull --ff-only origin main
|
||
cd /d/doit/pipeline-app && ./py3/bin/pip install pkgs/<module>
|
||
find py3/lib/python3.10/site-packages/<module_pkg> -name __pycache__ -exec rm -rf {} +
|
||
|
||
# 2.4 最后重启(生产四进程:逐 pidfile kill 再 start,见 pipeline-prod-operations 技能;
|
||
# 禁止 pkill -f "pipeline-app" ——会误杀自己的远程 shell)
|
||
nohup bash restart-pipeline.sh </dev/null > logs/restart.log 2>&1 &
|
||
sleep 15; curl -s -o /dev/null -w "health:%{http_code}\n" http://127.0.0.1:9090/
|
||
```
|
||
|
||
`dmig.py apply` 的行为:
|
||
- 每条语句执行前查存在性(表/列/索引已有 → 跳过,保证幂等);
|
||
- 执行成功才写台账 `pipeline_deploy_ledger`(批次、步骤、状态、时间);
|
||
- 任一步失败 → 停止后续步骤,打印已完成步骤清单和对应 `down` 操作(供回退),**不自动回滚**。
|
||
|
||
### 第 3 步:验证(不验证 = 没上线)
|
||
|
||
```bash
|
||
./py3/bin/python deploy/ddiff.py --checklist # 差异清零(或只剩本批次明确豁免项)
|
||
./py3/bin/python deploy/dmig.py status # 台账:本批次全部 applied
|
||
curl -s -o /dev/null -w "health:%{http_code}\n" http://127.0.0.1:9090/ # 服务 200
|
||
grep -a "poller started" logs/pipeline.log | tail -5 # 各 poller 正常
|
||
# + 功能验收:真实浏览器走一遍本次变更影响的功能(铁律:功能验收必须真浏览器执行)
|
||
```
|
||
|
||
检查点:
|
||
- [ ] ddiff 差异清零
|
||
- [ ] dmig status 全 applied、无 failed
|
||
- [ ] 服务健康 + poller 正常
|
||
- [ ] 功能浏览器实测通过(记录证据:页面/请求/数据三选一)
|
||
|
||
### 第 4 步:故障回退(任一步失败时)
|
||
|
||
**判断**:回退的是「这次增量」,不是整个系统。定位到失败的批次号。
|
||
|
||
```bash
|
||
# 4.1 库回退:用迁移自带的 down
|
||
./py3/bin/python deploy/dmig.py rollback m0003 # 执行 m0003 的 down + 台账置 rolled_back
|
||
|
||
# 4.2 数据回退:down 不足以恢复时,用第 1 步的备份
|
||
./py3/bin/python deploy/dbackup.py restore --file backups/pipeline_sd_projects_20260901_120000.sql --confirm
|
||
# 注意:恢复会先 DROP 再导入该表——执行前人工确认没有比备份点更新的写入(或先导出增量)
|
||
|
||
# 4.3 代码回退
|
||
cd /d/doit/pipeline-app/pkgs/<module> && git log --oneline -5 # 找到上线前的提交
|
||
git checkout <上线前提交> && cd ../.. && ./py3/bin/pip install pkgs/<module> && 重启
|
||
```
|
||
|
||
回退后重新走第 3 步验证,确认系统回到上线前状态。
|
||
|
||
## 4. 台账表(自动创建)
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS pipeline_deploy_ledger (
|
||
id VARCHAR(32) NOT NULL,
|
||
batch VARCHAR(16) NOT NULL, -- 迁移 id:m0001
|
||
step INT NOT NULL, -- up 内步骤序号
|
||
status VARCHAR(16) NOT NULL, -- applied / rolled_back / skipped
|
||
backup_file VARCHAR(255),
|
||
detail VARCHAR(4000),
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (id),
|
||
UNIQUE KEY uk_ledger_batch_step (batch, step)
|
||
);
|
||
```
|
||
|
||
## 5. 测试环境的角色
|
||
|
||
测试环境是**迁移的演练场**:每个迁移文件必须在测试环境 `dmig.py apply` 跑通(含幂等重跑验证)
|
||
才允许进入生产批次。测试环境的台账独立(同库不同环境实例),生产台账只反映生产执行记录。
|
||
|
||
## 6. 常见坑(历史教训)
|
||
|
||
1. **改库没登记** → 生产是老库。8/31-9/1 连续发现:`pipeline_agent_instances` 缺表、
|
||
`sd_projects.directory_name` 缺列、`pipeline_deliverables.created_by` 还是 32 宽。
|
||
根治就是本规范:改库必须落成迁移文件。
|
||
2. **只在测试跑 DDL 口头说"生产一样"** → 必然漂移。用 `ddiff.py` 强制对账。
|
||
3. **回退没有 down** → 不可逆变更(DROP 列、删数据)必须有备份兜底,备份在操作前。
|
||
4. **破坏性语句进自动化** → 永远 `[NEEDS_APPROVAL]`,人工执行。
|