88 lines
4.1 KiB
Markdown
88 lines
4.1 KiB
Markdown
---
|
||
id: devops.cicd.release-rollback
|
||
title: 发布与回滚:镜像标签、环境、滚动 / 蓝绿 / 金丝雀、数据库迁移
|
||
type: lesson
|
||
domain: devops
|
||
stack: [cicd]
|
||
tags: [发布策略, 回滚, 蓝绿发布, 金丝雀, 数据库迁移, 特性开关]
|
||
level: intermediate
|
||
prerequisites: [devops.cicd.pipeline]
|
||
est_hours: 3
|
||
related: [devops.cicd.scenario.multi-lang-pipeline, devops.nginx.https-stream-limit, devops.cicd.interview]
|
||
week: [14]
|
||
order: 2
|
||
---
|
||
|
||
# 发布与回滚:镜像标签、环境、滚动 / 蓝绿 / 金丝雀、数据库迁移
|
||
|
||
## 解释说明
|
||
|
||
**不可变产物**:一次构建出镜像(标签 = 提交哈希或版本号),测试、预发、生产用**同一个镜像**,只是配置不同。这样测试过的东西就是上线的东西,回滚就是切回上一个标签。
|
||
|
||
**环境**:开发 → 测试 → 预发(和生产配置尽量一致)→ 生产。配置通过环境变量注入,不打进镜像。
|
||
|
||
**发布策略**
|
||
|
||
| 策略 | 做法 | 优点 | 缺点 |
|
||
| --- | --- | --- | --- |
|
||
| 重建 | 停掉旧版本再启动新版本 | 最简单 | 有停机 |
|
||
| 滚动 | 逐台替换实例 | 无停机、资源占用少 | 新旧版本同时在线一段时间;回滚也要逐台 |
|
||
| 蓝绿 | 准备一整套新环境,验证后切流量 | 切换和回滚都是瞬间 | 需要双倍资源 |
|
||
| 金丝雀 | 先给小部分流量,观察指标后逐步放大 | 风险最小 | 需要流量控制能力和监控 |
|
||
|
||
**回滚**:应用回滚 = 部署上一个镜像标签。难点在**数据库**:新版本已经改了表结构,旧版本可能跑不起来。
|
||
|
||
**数据库迁移的原则**:迁移脚本放在仓库里、有版本号、由工具执行(Go 的 golang-migrate / goose,Python 的 Alembic);**每次迁移都要向后兼容**——新旧代码都能在新表结构上运行。删除或重命名字段要分多次发布(“扩展 → 迁移 → 收缩”):
|
||
|
||
1. 加新字段(旧代码不受影响),新代码同时写新旧字段;
|
||
2. 回填历史数据,新代码改为读新字段;
|
||
3. 确认不再回滚到旧版本后,再删除旧字段。
|
||
|
||
**特性开关**:代码先上线但功能默认关闭,通过配置打开;出问题直接关开关,比回滚代码更快。
|
||
|
||
## 案例
|
||
|
||
一次发布要把 `users.name` 改名为 `display_name`,迁移脚本直接执行了 `RENAME COLUMN`。发布后发现新版本有 bug,回滚到旧版本——旧代码找不到 `name` 字段,全站报错,故障时间翻倍。按“扩展 → 迁移 → 收缩”分三次发布就能避免。
|
||
|
||
## 代码示例
|
||
|
||
单机部署 + 健康检查 + 失败自动回滚(完整脚本 [examples/pipeline/deploy.sh](examples/pipeline/deploy.sh)):
|
||
|
||
```bash
|
||
previous="$(cat "$STATE_FILE" 2>/dev/null || true)" # 上一次成功部署的标签
|
||
deploy "$IMAGE_TAG"
|
||
if healthy; then
|
||
echo "$IMAGE_TAG" > "$STATE_FILE"
|
||
else
|
||
echo "健康检查失败,回滚到 $previous" >&2
|
||
[[ -n "$previous" ]] && deploy "$previous"
|
||
exit 1
|
||
fi
|
||
```
|
||
|
||
“扩展 → 迁移 → 收缩”的迁移脚本(PostgreSQL):
|
||
|
||
```sql
|
||
-- 第 1 次发布:扩展(旧代码不受影响)
|
||
ALTER TABLE users ADD COLUMN display_name TEXT;
|
||
|
||
-- 第 2 次发布前:回填(大表要分批执行)
|
||
UPDATE users SET display_name = name WHERE display_name IS NULL;
|
||
|
||
-- 第 3 次发布:确认不再回滚到读 name 的版本后,收缩
|
||
ALTER TABLE users DROP COLUMN name;
|
||
```
|
||
|
||
## 易错点
|
||
|
||
- **每个环境重新构建镜像**:测试过的和上线的不是同一个东西。
|
||
- **迁移不向后兼容**:应用无法回滚。
|
||
- **应用启动时自动执行破坏性迁移**:多实例同时启动会并发迁移;迁移失败导致所有实例起不来。迁移作为单独的发布步骤执行。
|
||
- **只看“部署成功”不看业务指标**:容器起来了不代表功能正常。发布后观察错误率、延迟、关键业务指标。
|
||
- **回滚流程从来没有演练过**:真出事时脚本跑不通。
|
||
- **特性开关从不清理**:代码里分支越来越多。功能稳定后删除开关。
|
||
|
||
## 面试常问
|
||
|
||
见 [CI/CD 面试题](interview.md):蓝绿和金丝雀的区别、怎么回滚、数据库变更怎么做到可回滚。
|