事故
周五下午,线上服务挂了。
原因:一个 36KB 的旧版 server.js 覆盖了服务器上 76KB 的新版。部署的人没有拉取最新代码,直接把本地的旧文件传了上去。
更惊险的一次:一个只有 1 条测试数据的本地数据库,差点覆盖了生产环境 125 个用户的数据。发现时文件已经在传输队列里了。
根因:本地保存着过时的代码副本,部署时没有和服务器做比对,直接覆盖。
这不是个例。在没有 Git 仓库、没有 CI/CD 的项目里,用 scp / rsync 手动部署,这种事迟早会发生。
我的"完美方案"
我觉得问题出在"没有规范的部署流程"。于是研究了 Capistrano、Shipit、Deployer 这些成熟工具,花了两天写了一套部署系统:
1400 行 Shell,8 个文件。
- Capistrano 风格的
releases/current/shared目录结构 rsync --checksum漂移检测ln -sfn+mv原子切换- PM2 日志 → PM2 状态 → HTTP 请求的 3 层健康检查
- 健康检查失败自动回滚
flock并发锁*.db/uploads/全局排除
然后做了 10 轮代码审查 ,修了 79 个 bug。
没错,一个部署脚本,79 个 bug。
实测打脸
信心满满地上线:
- 第 1 次 :失败。zsh 的
trap EXIT在函数返回时就触发,SSH 连接被中途杀死。 - 第 2 次:失败。符号链接指向了自己,形成循环,数据库丢了。
- 第 3 次:终于成功。之后几次也正常。
我以为搞定了。
然后,就在同一天,另一个同事在处理一个紧急 bug。他的操作:
- 打开本地的
server.js(旧版本) - 改了几行
scp直接传上去- 线上新版本被覆盖
1400 行部署系统,完美绕过。
因为他压根没用。赶时间,直接 scp 了。
醒悟
我回头看了看团队规范里写的:
❗ 必须使用 deploy.sh 部署,禁止手动 scp!
他读过这条规则。但当他需要"快速修一个 bug"的时候,选择了最短路径。
任何依赖操作者遵守规则的方案,都会在某个时刻被绕过。
不是意愿问题,是人性。写在文档里的规则 = 写在路口的"禁止闯红灯",没有红绿灯和摄像头,迟早有人闯。
重新想
覆盖事故需要 3 个条件同时成立:
- 操作者手里有旧代码
- 服务器接受了上传
- 覆盖不可恢复
前两个没法根除------总得有人能部署代码。但第 3 个可以干掉。
让每一次文件变化都被记录。覆盖了不怕,随时恢复。
这不是"防止错误",是让错误变得无害。
最终方案:
bash
#!/bin/bash
# /opt/scripts/auto-version.sh --- cron 每分钟跑一次
for dir in /opt/apps/*/; do
cd "$dir" || continue
if [ ! -d .git ]; then
git init -q
printf 'node_modules/\n*.db\n*.log\nuploads/\ndata/' > .gitignore
git add -A && git commit -m "init" -q
fi
git add -A
git diff --cached --quiet || git commit -m "auto: $(date +%Y%m%d_%H%M)" -q
done
出事了:
bash
cd /opt/apps/my-project
git log --oneline # 看历史
git checkout HEAD~1 -- . # 一键恢复
两个方案的本质区别
1400 行系统的思路:阻止错误发生 。 20 行 cron 的思路:让错误发生了也没事。
前者需要每个人配合,一个人不配合就全白搭。 后者跑在服务器上,不需要任何人配合。
| 1400 行 | 20 行 | |
|---|---|---|
| 思路 | 阻止错误 | 错误无害 |
| 依赖操作者 | ✅ 绕过就废 | ❌ 服务器端自动 |
| Bug | 79 个 | 0 |
| 维护 | 高 | 零 |
| 磁盘 | 无 | ~5 MB/项目 |
四条教训
1. 解决方案比问题大,就是过度工程化。
原始问题用一行 rsync 就能缓解。我搞出了 Capistrano 风格的完整系统,复杂度是问题本身的 100 倍。
2. 追求"事故无害"比"零事故"现实。
零事故要求完美预防------几乎不可能。Netflix 的 Chaos Engineering 就是这个思路:假设一切都会出错,确保出错了能快速恢复。
3. 安全措施放服务器端,别指望客户端。
任何依赖客户端配合的安全机制都是纸老虎。防火墙装在服务器上,不是装在用户电脑上。
4. 10 轮审查不如 1 次实测。
79 个 bug 被审查发现了,但真正上线时 3 个致命 bug 全是审查没覆盖的。真实环境的 1 次反馈 > 代码层面的 10 次检查。
谁需要这个
scp/rsync手动部署的小团队- 外包/实习生直接改线上代码
- AI Agent(Cursor、Claude Code 等)操作服务器
- 任何没有 Git + CI/CD 的项目
已经有完善的 Git 工作流和 CI/CD?那你不需要这篇文章。
完整脚本(含 .gitignore 优化、并发锁、一键安装/卸载):[Gist 链接]