GitLab 删库事件
2017 年 2 月 1 日,对于 GitLab 的工程师来说,是一个噩梦般的夜晚。
这天晚上,一名工程师在处理一个数据库复制延迟问题时,执行了一条命令。本来是想删除一个临时文件,结果因为操作失误,生产环境的数据库被删除了。
更糟糕的是:当团队尝试从备份恢复时,发现备份机制也存在缺陷——部分备份功能已经失效了好几个月,数据只能恢复到 6 小时前的状态。这意味着过去 6 小时的所有用户提交、Issue、评论、Merge Request,全部丢失。
GitLab 在当天深夜发布了事故报告,坦诚地承认了问题,并提供了完整的事后分析。这份报告后来成为了技术圈广泛引用的经典案例,被认为是最诚实的事后分析之一。
事件背景
GitLab 是一个开源的 Git 代码托管平台,提供类似 GitHub 的服务。2017 年时,GitLab 的数据库架构如下:
- 主数据库:PostgreSQL,跑在单台服务器上
- 备份机制:
- 每天 17:00 自动备份(LVM 快照)
- 每周日凌晨 4:00 备份
- Azure 存储上的备份
- 数据库复制:配置了流复制(streaming replication),但复制延迟监控失效了
这听起来是一个标准的「够用」配置。但问题在于:「够用」和「可靠」之间,差了十万八千里。
事件经过
故障发生(UTC 时间 23:00 左右)
2017 年 2 月 1 日,UTC 时间 23:00 左右,GitLab 的工程师正在处理一个数据库复制延迟问题。问题的表现是:主库的复制状态显示正常,但实际存在约 6 小时的复制延迟。
工程师开始排查。在排查过程中,他想要清理一个临时目录,释放磁盘空间。操作命令是:
但问题在于:他搞混了目录路径。他实际执行的命令是:
这条命令执行后,生产数据库被删除。由于数据库所在的磁盘分区也一并受影响,数据恢复变得更加困难。
发现与升级(23:00 - 23:30)
23:11,值班监控系统检测到数据库连接失败,触发了告警。
23:13,值班工程师确认了问题:数据库目录被删除。
23:18,多名工程师收到通知并加入响应。
23:30,确认影响范围:整个生产数据库丢失。
恢复尝试(23:30 - 02:00)
团队立即开始尝试恢复:
- LVM 快照恢复:尝试从 LVM 快照恢复,但发现快照创建有问题,恢复失败
- Azure 备份恢复:尝试从 Azure 存储恢复,但备份文件不可用
- 其他备份:尝试从其他备份源恢复,都失败了
最终,团队确认:只有 6 小时前的备份是完整的。
服务恢复(02:00 - 07:00)
UTC 时间 2 月 2 日凌晨 02:00,团队决定使用 6 小时前的备份恢复数据库。
恢复过程中,团队做了几件事来加快进度:
- 分步恢复:先恢复 Web 服务,让用户可以看到页面
- 持续恢复:后台继续恢复剩余数据
- 透明沟通:在 GitLab 官网持续更新恢复进度
UTC 时间 07:00(北京时间约 15:00),服务基本恢复正常。
根因分析
直接原因:命令执行错误
故障的直接原因非常明确——工程师执行了错误的 rm -rf 命令。
但「人犯了错」只是起点,不是终点。我们要问的是:为什么一个命令执行错误会造成如此严重的后果?
深层原因一:权限管控缺失
PostgreSQL 的数据目录是 /var/opt/gitlab/postgresql/data。在大多数公司的生产环境中,这个目录的所有者应该是 postgres 用户,而不是普通工程师。
如果权限配置正确,工程师用自己的账号登录后,即使执行了 rm -rf /var/opt/gitlab/postgresql/data,也会因为权限不足而被拒绝。
但 GitLab 的实际情况是:这个目录可以被普通工程师删除。这说明权限配置存在严重问题。
深层原因二:备份机制失效
这是更深层的问题。GitLab 当时有多个备份机制,但它们都失效了:
|| 备份类型 | 配置 | 实际状态 | || --- | --- | --- | | LVM 快照 | 每天 17:00 | 快照创建有问题,恢复失败 | | Azure 备份 | 持续同步 | 备份不可用 | | Wal-E | 持续归档 | 归档进程失效 | | Azure 存储 | 每日备份 | 备份过期被清理 |
GitLab 事后分析发现,至少有 5 种备份机制同时失效:
- LVM 快照:
/var/opt/gitlab/目录没有包含在 LVM 快照中 - pg_dump:PostgreSQL 常规备份功能处于半启用状态
- Azure Blob Storage:备份脚本配置正确,但备份失败时没有告警
- S3 转储:备份脚本配置正确,但失败时没有告警
- Wal-E:归档进程失效,但没有告警
这说明 GitLab 的备份系统是一个「假装有备份」的假象——配置了各种备份机制,但没有人验证这些备份是否真的能用。
深层原因三:缺乏变更测试
事故发生前几周,GitLab 迁移了数据库服务器。迁移过程中,某些备份配置可能失效了,但没有人验证。
这种「配置变更后不验证」的问题,是很多故障的根本原因。
影响评估
|| 维度 | 数据 | || --- | --- | | 故障发生时间 | 2017-02-01 23:11 UTC | | 完全恢复时间 | 2017-02-02 07:00 UTC | | 总中断时长 | 约 8 小时 | | 数据丢失 | 约 6 小时的用户数据 | | 丢失内容 | Issue、评论、Merge Request、代码片段等 | | 受影响用户 | 所有使用 GitLab.com 的用户 |
GitLab 后来在事故报告中承认:约有 5% 的数据库数据丢失,涉及约 5,000 个 Issue、5,000 个评论和约 700 个其他记录。
GitLab 公开报告解读
GitLab 在事故发生后的第二天凌晨发布了完整的事后分析报告。这份报告后来被称为「最诚实的事后分析」。
报告的核心亮点包括:
- 坦诚承认问题:没有试图掩盖或淡化问题
- 完整的故障时间线:精确到每分钟
- 详细的根因分析:不只是「操作失误」,而是分析「为什么操作会失误」
- 具体的改进措施:每项措施都有负责人和完成时间
- 透明的用户沟通:在官网实时更新恢复进度
后续改进
1. 多重备份验证
GitLab 建立了一套完整的备份验证机制:
2. 权限加固
改进了服务器权限配置:
3. 危险命令拦截
引入了 rm -rf 的保护机制:
4. 变更管理流程
建立了配置变更后的验证流程:
数据安全的最佳实践
GitLab 的这次故障,给整个技术行业上了一课。从这次故障中,可以总结出数据安全的最佳实践:
1. 备份的 3-2-1 原则
- 3 份副本:原始数据 + 2 份备份
- 2 种介质:比如磁盘 + 云存储
- 1 份异地:至少一份备份存放在异地
2. 定期恢复演练
「有备份」和「备份能用」是两回事。必须定期做恢复演练,验证备份的可用性。
3. 权限最小化原则
不要给工程师 root 权限,除非必要。数据库操作应该通过专门的工具进行,而不是直接用 shell 命令。
4. 危险操作二次确认
对于高危操作(如删除数据、删除表),应该要求二次确认:
思考题
问题 1:GitLab 的备份机制「全部失效」听起来不可思议,但为什么在实际项目中很常见?
参考答案
几个原因:1)备份是「保险」而不是「日常」——备份配置好后,没有人每天去检查它是否正常工作;2)备份失败通常不会主动告警——除非特意配置了监控;3)变更后不验证——比如迁移服务器后,备份脚本的配置路径可能失效,但没有人重新验证;4)「看起来有备份」给了团队虚假的信心——GitLab 配置了 5 种备份机制,但没有一种真正生效,但他们以为有保障。正确的做法是:把备份当作生产服务来维护,配置告警,定期演练。
问题 2:如果你是 GitLab 的工程师,在发现数据库被删除后,会采取什么顺序来恢复服务?
参考答案
恢复顺序取决于「恢复的目标」和「可用的备份」。一个合理的恢复策略:1)首先确认哪些备份是真正可用的——逐个尝试,记录每个备份的恢复时间和数据完整性;2)选择「最新可用」的备份——不一定是最新的(可能有 bug),但应该是最完整且可恢复的;3)优先恢复「最关键」的数据——用户代码 > Issue > 评论 > 其他;4)分阶段恢复——先恢复只读服务让用户可以看到数据,再逐步恢复写入功能;5)持续同步最新状态——恢复后,需要有机制同步 6 小时内的增量数据(如果有其他存储的话)。
问题 3:为什么 GitLab 的事后报告被认为是「最诚实的事后分析」?这对故障复盘有什么启示?
参考答案
几个原因:1)没有试图掩盖问题——承认了备份全部失效,而不是「部分备份受损」;2)提供了详细的时间线和根因分析——不只是说「操作失误」,而是分析「为什么操作会失误、系统哪里给了失误空间」;3)每项改进措施都有具体的负责团队和时间——而不是泛泛说「我们会加强备份」;4)对受影响用户坦诚——在官网实时更新进度,没有试图隐瞒。这给我们的启示是:诚实的复盘才能带来真正的改进。如果复盘变成了「甩锅大会」或「走过场」,团队就失去了学习的机会,同样的错误会再次发生。