go-service-release
name: go-service-release
description: Go 后端服务发布检查单与回滚预案。当要上线一个 Go 服务、要确认版本号和迁移是否就位、要做灰度或回滚、要配健康检查时使用。触发词:发版、上线、部署、发布检查、灰度、回滚、健康检查、版本号对不上、迁移没跑、systemd、二进制替换。不负责代码实现和性能调优,也不负责 Kubernetes 编排(走 docker-compose-hardening 或专门的 K8s 技能)。
Go 服务发布检查单
发布事故的分布很稳定:配置错、迁移错、版本不一致、没法回滚,四类占九成。这份检查单就是围着这四类设计的。
一、发布前
版本一致性
后端二进制、前端产物、API 响应头三处版本号必须一致。不一致的后果是排障时看到的版本是假的。
# 编译期注入,不要硬编码在多个地方
go build -ldflags "-X main.version=$(cat VERSION)" -o bin/app ./cmd/app
服务必须通过响应头暴露版本,比如 X-App-Version。上线后第一件事就是 curl -I 确认这个头。
版本号规则:功能新增才动正式版本位并把 rc 重置为 rc1;纯 bugfix 只递增 rc 号。改了版本号必须同步 CHANGELOG,两者不同步等于没有版本管理。
配置与密钥
- 配置和密钥不进构建产物,走环境变量或密钥管理。
- 新增的环境变量,在目标机器上确认已经存在。缺失时服务应当启动失败并明确报错,而不是用零值默认跑起来。
- 检查配置项的默认值在生产是否合理。开发环境的超时 30 秒,生产可能需要 3 秒。
数据库迁移
- 迁移文件 up/down 成对,down 实际执行测试过。
- 服务启动时自动执行一次
migrate up,失败即阻止启动,并输出迁移版本、文件名和错误上下文。 - 多实例部署时迁移必须加锁,否则并发启动会互相踩。
- 高风险迁移不随启动自动执行:大表 DDL、不可逆的数据变更、删字段删表,这三类走人工审批和单独窗口。
- 迁移必须向后兼容一个版本。也就是说新迁移跑完后,旧二进制仍然能正常工作。做不到就说明这次发布无法回滚,需要拆成两次发。
后一条最容易被忽略,也最致命。「加字段」是兼容的,「改字段名」不是。改名要拆成三步:加新字段并双写 → 迁移数据并切读 → 下个版本删旧字段。
构建与产物
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
-ldflags "-s -w -X main.version=$(cat VERSION)" -o dist/app ./cmd/app
CGO_ENABLED=0得到静态二进制,避免目标机 glibc 版本问题。-trimpath去掉本机路径,产物可复现且不泄露目录结构。- 产物打上版本号或时间戳,别叫
app。
测试
go test ./...全绿。go vet ./...和 linter 无新增告警。go build在目标平台交叉编译成功。- 核心链路有集成测试跑过一遍。
二、发布中
备份优先
替换二进制之前,先把旧产物拷到带版本号的目录,至少保留最近三个版本。
install -d /opt/app/releases
cp /opt/app/bin/app /opt/app/releases/app-$(date +%F-%H%M%S)-$(cat /opt/app/VERSION)
ls -t /opt/app/releases | tail -n +4 | xargs -r -I{} rm -f /opt/app/releases/{}
没有这一步,回滚就只能靠重新编译,而故障当下你未必编得动。
部署脚本要幂等
同一个脚本跑十次和跑一次结果一样。不幂等的脚本在重试时会制造脏状态,而故障时你一定会重试。
健康检查后才承载流量
新版本起来后,依次确认:
| 层次 | 检查 |
|---|---|
| 进程 | systemctl is-active <svc> 为 active |
| 端口 | ss -lntp | grep <port> 在监听 |
| 健康接口 | /healthz 返回 200,且真的检查了依赖 |
| 版本接口 | 响应头里的版本号是新版本 |
| 关键业务接口 | 至少一条真实业务路径跑通 |
健康接口要真检查依赖(数据库能连、缓存能通),只返回 ok 的健康检查毫无价值。
优雅退出
Go 服务必须处理 SIGTERM:停止接受新请求,等待在途请求完成(给一个上限,比如 30 秒),关闭数据库连接池,然后退出。systemd 的 TimeoutStopSec 要大于这个上限。
没有优雅退出,每次发布都会给用户丢一批 502。
三、发布后
立刻确认这几项,不要等用户报障:
- 错误率和 P99 延迟与发布前对比。
- 日志里有没有新出现的 ERROR 模式。
- 迁移是否执行成功,
schema_migrations的版本和 dirty 标志。 - 内存曲线,观察 10 分钟看有没有泄漏迹象。
反馈给相关人的信息必须包含五样:版本号、部署结果、迁移结果、备份路径、具体的回滚命令。少一样,故障时就要现查。
四、回滚
回滚预案在发布前就要写好,不是出事了现想。
优先回滚应用:恢复旧产物并重启。
systemctl stop app
cp /opt/app/releases/app-<上一个版本> /opt/app/bin/app
systemctl start app
curl -sI http://127.0.0.1:<port>/healthz | grep -i x-app-version
迁移的 down 排在最后。只有在确认「备份可恢复」且「新迁移与旧版本不兼容」时才手动执行,且要人工确认影响范围。绝大多数情况下,回滚应用就够了,因为迁移本来就该向后兼容。
特别注意:迁移失败会把 schema_migrations 置为 dirty,此时旧二进制也起不来。这时回滚二进制是无效的,必须先清掉 dirty 标志(migrate force <上一个版本>),再决定是修复前进还是执行 down。
五、绝对不做的事
- 直接在 main 或 develop 上 commit,然后手工编译上线。
- 静默失败:部署、迁移、重启、健康检查任何一步失败都必须明确报告原因和下一步建议,不能吞掉。
- 在没有备份确认的前提下执行
migrate down。 - 把
.env、证书、密钥提交进仓库或打进产物。 - 高峰期做大表 DDL。
- 发布后不看监控就下班。