事故现场
周五下午4:58,距离下班还有2分钟。我信心满满地执行了一条"简单"的命令:
git push origin main --force
然后整个监控面板变成了红色。
发生了什么
这条命令的本意是把本地的几个配置修复推上去。但我忘了本地仓库的 main 分支已经落后远程 origin/main 30 个 commit。--force 把远程那30个 commit 全部覆盖了,其中包括:
- 数据库迁移脚本
- 网关路由配置
- 上周才上线的支付模块热修复
救火过程
# 第一步:深呼吸
# 第二步:查看 reflog
git reflog
# 找到被覆盖前的 commit hash
# 0a1b2c3 HEAD@{1}: commit: 支付模块热修复
# 第三步:恢复
git push origin 0a1b2c3:main
整个恢复花了 47 分钟。但比这更痛的是之后复盘时发现的三个根本问题:
- 我们没有分支保护规则——任何人都可以直接 force push 到 main
- 没有部署锁——push 触发了自动部署
- 没有回滚预案——出了事才知道该怎么做
后来我们做了什么
# .github/workflows/branch-protection.yml 不是这个
# 我们直接在 GitHub Settings 里配置了:
# ✅ Require pull request reviews before merging
# ✅ Require status checks before merging
# ✅ Do not allow bypassing the above settings
# ✅ Restrict who can push to matching branches(禁止直接 push main)
另外,我们加了一个 deploy lock 机制:
#!/bin/bash
# deploy-lock.sh
LOCK_FILE="/tmp/deploy.lock"
if [ -f "$LOCK_FILE" ]; then
echo "部署锁已存在,当前有人在部署或维护窗口"
echo "如需强制解锁,请联系 SRE 团队"
exit 1
fi
touch "$LOCK_FILE"
echo "部署已锁定,维护窗口开始"
# 部署完成后记得 rm "$LOCK_FILE"
经验总结
永远不要在周五下午 force push。
更准确地说:永远不要在没有 branch protection 的仓库里 force push。
运维的核心不是"出了事能修好",而是"出不了事"。