这些教训每个开发者都踩过,只是先后的问题。
备份没做,磁盘坏了或者 rm -rf 跑错了目录,数据消失。生产上”快速改一下”,测试没跑,一个小时后服务挂了,改了什么已经记不清楚了。凭证提交进了仓库,刚推完就反应过来,但已经有扫描脚本捕获了。
不是不知道这些事情危险。是当时觉得”就这一次,没关系”。
这篇文章要讲的不是这些坑是什么,而是怎么把避开它们变成不需要思考的自动行为——像刷牙一样,不依赖记忆和意志力。
1. 备份
备份的核心定律
备份只有两种状态:有用的备份,和从未测试过的备份。
从未测试过恢复流程的备份,不能算备份。它只是”也许能救你”的希望,但在你最需要它的时候,可能是坏的、不完整的、需要依赖已经不存在的工具才能恢复的。
3-2-1 规则:3 份数据,2 种不同介质,1 份在异地。
| 原则 | 含义 |
|---|---|
| 3 份数据 | 原始数据 + 2 份备份 |
| 2 种介质 | 比如本地硬盘 + 云存储 |
| 1 份异地 | 防止本地灾难(火灾、盗窃、洪水)同时摧毁所有副本 |
什么需要备份
开发者最容易忽视的备份盲区:
- 数据库——不只是代码,生产数据经常比代码更难重建。数据库备份要自动化、定期运行、存在代码仓库之外
- dotfiles 和配置——花了几年时间调的 shell、编辑器、SSH config、工具链配置。Git + chezmoi 是常见方案
- 密钥和证书——私钥丢了,关联的公钥信任全部失效。加密备份存在密码管理器或离线安全介质
- 本地项目——没有推送到远程的分支和 stash。本地改动推到远程是最简单的备份
验证可恢复性
每个备份系统至少演练一次完整恢复。
演练之前你不知道:备份工具是否真的在运行、备份文件是否完整、恢复流程是否在当前环境下可用、恢复需要多长时间、恢复过程中有哪些依赖。
定个日历提醒,每季度或每半年演练一次。“我有备份”和”我能在一小时内从备份恢复”是两件完全不同的事。
2. 生产操作纪律
生产环境不是实验场。每一次在生产上的操作,代价都比在开发环境大得多——出了问题影响真实用户、恢复时间更长、记录更重要。
先只读,后写入
在任何生产变更之前,先用只读操作确认你看到的是预期的状态:
# 先确认,再操作
kubectl get pods -n production # 看清楚在哪里
kubectl describe pod <name> # 确认是你要操作的那个
kubectl delete pod <name> # 再执行
# 先看,再改
SELECT COUNT(*) FROM users WHERE status = 'inactive'; # 确认影响范围
UPDATE users SET status = 'deleted' WHERE status = 'inactive'; # 再执行dry-run 是最便宜的保险
有 dry-run 选项的命令,第一次永远先跑 dry-run:
rsync -avz --dry-run ./local/ user@host:/remote/ # 看清楚会改什么
kubectl apply --dry-run=client -f deployment.yaml # 先预览
terraform plan # 再 apply
ansible-playbook site.yml --check # 验证 playbookdry-run 让你在没有副作用的情况下看到”如果真的执行,会发生什么”。跳过 dry-run 节省的三十秒,可能要用几小时来修复。
操作前记录现状
在任何破坏性或难以回滚的操作之前,先保存现状:
kubectl get deployment myapp -o yaml > myapp-backup-$(date +%Y%m%d).yaml
mysqldump mydb > backup-$(date +%Y%m%d-%H%M).sql
terraform state pull > terraform-state-backup-$(date +%Y%m%d).tfstate这三十秒的快照,可能在两小时后救你的命。
不在疲惫时做高危操作
这条听起来像心灵鸡汤,但有大量事故报告支撑:深夜、长时间高压会话结束时、催促你”赶紧上线”的压力下,是生产事故最集中的时间段。
疲惫时:注意力下降、确认步骤被跳过、命令参数出错、操作了错误的环境。如果某个操作可以等到明天白天,就等到明天白天。
3. 破坏性命令的护栏
有些命令是单向的——执行之后,没有简单的撤销路径。对这些命令建立固定的减速仪式,而不是依赖”这次我会仔细”。
文件系统
# rm 没有回收站,删了就是删了
rm -rf /path/to/directory
# 几个减速方法:
alias rm='rm -i' # 每次删除前询问
mv unwanted/ ~/.Trash/ # 先移到垃圾桶
ls -la /path/to/directory # 执行前先 ls 确认内容
echo "rm -rf /path/to/directory" # 先打出来读一遍再执行rm -rf /data 和 rm -rf / data(多一个空格)的结果天壤之别。这种错误发生过,不只一次。
Git
git reset --hard HEAD~3 # 丢弃 3 个 commit 的所有更改,无法直接撤销
git push --force # 覆盖远程历史,影响所有协作者
git clean -fd # 删除所有未追踪文件和目录git reset --hard 之前:git status 确认没有未保存的工作,git log 确认你要重置到的位置。git reflog 可以在 30 天内找回被 reset 的 commit,但不能依赖它作为常规工作流。push --force 只在个人分支上用,永远不在共享分支上用。
数据库
DROP TABLE users; -- 不可恢复
DELETE FROM orders; -- 没有 WHERE,删全表
TRUNCATE TABLE sessions; -- 清空表,比 DELETE 更快也更不可逆数据库操作的减速仪式:把高危 SQL 包在事务里,先 SELECT 确认范围,再 BEGIN,执行,看结果,确认无误再 COMMIT:
BEGIN;
SELECT COUNT(*) FROM orders WHERE status = 'cancelled'; -- 先确认影响范围
DELETE FROM orders WHERE status = 'cancelled'; -- 再执行
-- 看输出,确认行数符合预期
COMMIT; -- 或 ROLLBACK;Kubernetes 和基础设施
kubectl delete namespace production # 删除整个命名空间,所有资源随之消失
terraform destroy # 销毁所有管理的资源
kubectl delete pvc <name> # 删除持久卷,数据消失基础设施操作之前:确认当前 context 指向的是哪个集群(kubectl config current-context)、确认 terraform workspace(terraform workspace show)。很多事故是”以为在 staging,其实在 production”。
4. 环境边界
知道自己在哪个环境
“以为在 staging,其实在 production”是高频事故模式。建立环境可见性:
# 在 shell 提示符里显示当前 k8s context 和 git 分支
# 让当前环境始终可见,而不是需要主动去查把当前环境信息打在终端提示符里(k8s context、AWS profile、数据库连接名),让它随时可见而不是需要主动去查。
生产数据不下载到本地
生产数据包含用户隐私,下载到开发机意味着:
- 笔记本可能被盗、丢失、被恶意软件感染
- 本地的日志、调试工具、截图可能意外保存数据
- 违反数据保护法规(GDPR、个人信息保护法)的风险
需要用真实数据测试时,在隔离的生产级环境里操作,或者用脱敏数据集。
不在生产上做实验
“我先在生产上看看这个配置有没有效果”——不行。实验意味着未知的副作用。生产流量、真实用户、数据完整性,都不是实验的代价。先在开发环境或 staging 环境验证,确认行为符合预期,再带着完整测试和回滚方案上生产。
5. 凭证与配置纪律
私钥不离本地
SSH 私钥、代码签名证书、数字证书,不应该出现在:服务器、跳板机、临时容器、CI 环境、任何你不完全控制的机器上。
需要跨机器认证时,用 ProxyJump、服务账号密钥、短期凭证,或 Secrets Manager 注入——而不是复制私钥。
密钥不进仓库
即使是私有仓库。团队成员权限变化、仓库迁移、外部贡献者访问,都可能让”私有”变得不可控。密钥在环境变量或 Secrets Manager,仓库里只放 .env.example 模板。见 安全实践 的”敏感信息不入库”部分。
配置变更必须有记录
不做”快速改一下”。“快速改一下”的真实含义:没有记录、没有测试、没有审查、没有回滚方案——出了问题,连改了什么都不知道。
所有配置变更,无论多小,都用版本控制或变更日志记录:改了什么、为什么改、在什么时间改的。这不是官僚流程,是给未来的自己(或值班同事)留一条追踪路径。
6. 改了什么,要记得清楚
所有改动用 git
“这只是个临时改动,不用提交”——两个月后,你不记得改了什么,也不记得为什么改,更不知道是否还应该保留。
本地实验性改动也提交到私有分支。Git 是最可靠的操作日志,比人类的记忆可靠得多。改动推到远程同时也是一份免费的备份。
操作日志
在生产上执行的高风险操作,留操作日志——不需要复杂工具,一个 Markdown 文件或团队的 incident channel 就够:
2026-06-04 14:30 by zopiya
操作:重启 api-server Pod(连续 OOM)
命令:kubectl rollout restart deployment/api-server -n production
结果:重启成功,Pod 3 分钟后恢复 Running,内存使用正常
关联 Issue:#1234这条记录在事后分析和值班交接时价值巨大。
不依赖”我记得”
“我记得上次是怎么配的”是比 GPT 幻觉更不可信的信息来源——人类记忆会合理化、会失真、会随时间衰减。配置、流程、部署步骤写成文档,重要的判断记在注释或 ADR 里,不要只活在某个人的脑子里。
7. 知道自己不知道什么
不懂不运行
# 收到这条命令,先不要运行
sudo curl -sSL https://example.com/install.sh | bash
# 先做这些:
# 1. 打开 https://example.com/install.sh 看脚本内容
# 2. 搜索这个脚本的来源和评价
# 3. 在隔离环境(虚拟机、容器)里先试
# 4. 理解每个参数的含义不理解的脚本不运行,不理解的参数不加,不理解的命令在沙箱里先试。这不是保守,是工程纪律。
新工具先在沙箱里试
任何新工具、新配置、新流程,在隔离环境里先跑一遍,确认行为符合预期,再带到生产或共享环境。
这包括:新版本的 CI 配置(在 feature 分支上先验证)、新的 Terraform module(在测试账号里先 apply)、新的数据库迁移(在 staging 先跑)、新的 Ansible playbook(用 --check 先 dry-run)。
复制来的代码要读懂
从 Stack Overflow、GitHub、AI 生成的代码,不读懂就粘贴,不运行就提交——这是安全漏洞和 bug 的温床。每一行进入代码库的代码,都是你的责任,不管它来自哪里。
8. 建立个人安全网
习惯的可靠性不来自记忆,来自系统。建立系统,让正确的行为变成阻力最小的路径:
shell 别名和函数:让危险命令自动减速。
alias rm='rm -i' # 每次删除都确认
alias k='kubectl'
alias kctx='kubectl config use-context' # 显式切换 context
# 切换生产 context 时提示
kprod() {
echo "⚠️ 切换到生产环境,确认?(y/n)"
read ans && [ "$ans" = "y" ] && kubectl config use-context production
}git hook:自动拦截常见错误。
# .git/hooks/pre-commit - 提交前扫描密钥
gitleaks detect --source . --verbose --exit-code定期检查清单:不依赖临时记忆,把该做的事写成日历提醒——每季度备份演练、每月权限审查、每周依赖扫描。
双人确认高危操作:在团队里,生产数据库的危险操作、不可逆的基础设施变更,要求另一个人在场或事先审查——不是不信任,是降低单点失误的概率。
9. 原则
“就这一次”是最危险的思维:每一次例外都在训练自己”跳过检查没关系”。习惯靠的是一致性,不是在意志力充足时遵守、疲惫时放松。
出问题不可怕,记录不清楚才可怕:事故发生时,最关键的不是”谁的错”,是”发生了什么、为什么发生、怎么不再发生”——这需要有记录可追溯。
慢一拍是最便宜的保险:在生产上多确认一步、在执行破坏性命令前先读一遍、在不确定时先查再动。多出来的三十秒,平均下来比每一次跳过省出的几秒值钱得多。
未演练的备份等于没有备份:定期恢复演练不是偏执,是对”备份”这个词的最基本诚实。