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 小时的复制延迟。

工程师开始排查。在排查过程中,他想要清理一个临时目录,释放磁盘空间。操作命令是:

# 本意:删除临时目录
rm -rf /var/tmp/gitlab-backup

但问题在于:他搞混了目录路径。他实际执行的命令是:

# 错误:删除了数据库目录
rm -rf /var/opt/gitlab/postgresql/data

这条命令执行后,生产数据库被删除。由于数据库所在的磁盘分区也一并受影响,数据恢复变得更加困难。

发现与升级(23:00 - 23:30)

23:11,值班监控系统检测到数据库连接失败,触发了告警。

23:13,值班工程师确认了问题:数据库目录被删除。

23:18,多名工程师收到通知并加入响应。

23:30,确认影响范围:整个生产数据库丢失。

恢复尝试(23:30 - 02:00)

团队立即开始尝试恢复:

  1. LVM 快照恢复:尝试从 LVM 快照恢复,但发现快照创建有问题,恢复失败
  2. Azure 备份恢复:尝试从 Azure 存储恢复,但备份文件不可用
  3. 其他备份:尝试从其他备份源恢复,都失败了

最终,团队确认:只有 6 小时前的备份是完整的

服务恢复(02:00 - 07:00)

UTC 时间 2 月 2 日凌晨 02:00,团队决定使用 6 小时前的备份恢复数据库。

恢复过程中,团队做了几件事来加快进度:

  1. 分步恢复:先恢复 Web 服务,让用户可以看到页面
  2. 持续恢复:后台继续恢复剩余数据
  3. 透明沟通:在 GitLab 官网持续更新恢复进度

UTC 时间 07:00(北京时间约 15:00),服务基本恢复正常。

根因分析

直接原因:命令执行错误

故障的直接原因非常明确——工程师执行了错误的 rm -rf 命令。

但「人犯了错」只是起点,不是终点。我们要问的是:为什么一个命令执行错误会造成如此严重的后果?

深层原因一:权限管控缺失

PostgreSQL 的数据目录是 /var/opt/gitlab/postgresql/data。在大多数公司的生产环境中,这个目录的所有者应该是 postgres 用户,而不是普通工程师。

如果权限配置正确,工程师用自己的账号登录后,即使执行了 rm -rf /var/opt/gitlab/postgresql/data,也会因为权限不足而被拒绝。

# 正确的权限配置示例
drwx------ postgres postgres /var/opt/gitlab/postgresql/data

# 普通工程师执行时会被拒绝
$ rm -rf /var/opt/gitlab/postgresql/data
rm: cannot remove '/var/opt/gitlab/postgresql/data': Permission denied

但 GitLab 的实际情况是:这个目录可以被普通工程师删除。这说明权限配置存在严重问题。

深层原因二:备份机制失效

这是更深层的问题。GitLab 当时有多个备份机制,但它们都失效了:

|| 备份类型 | 配置 | 实际状态 | || --- | --- | --- | | LVM 快照 | 每天 17:00 | 快照创建有问题,恢复失败 | | Azure 备份 | 持续同步 | 备份不可用 | | Wal-E | 持续归档 | 归档进程失效 | | Azure 存储 | 每日备份 | 备份过期被清理 |

GitLab 事后分析发现,至少有 5 种备份机制同时失效

  1. LVM 快照/var/opt/gitlab/ 目录没有包含在 LVM 快照中
  2. pg_dump:PostgreSQL 常规备份功能处于半启用状态
  3. Azure Blob Storage:备份脚本配置正确,但备份失败时没有告警
  4. S3 转储:备份脚本配置正确,但失败时没有告警
  5. 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. 坦诚承认问题:没有试图掩盖或淡化问题
  2. 完整的故障时间线:精确到每分钟
  3. 详细的根因分析:不只是「操作失误」,而是分析「为什么操作会失误」
  4. 具体的改进措施:每项措施都有负责人和完成时间
  5. 透明的用户沟通:在官网实时更新恢复进度
GitLab 在事故报告中写道:

"We will be working on improving our tooling and
  documentation to make sure this doesn't happen again."

"我们致力于改进工具和文档,确保此类事故不再发生。"

后续改进

1. 多重备份验证

GitLab 建立了一套完整的备份验证机制:

# 备份验证清单
backup_verification:
  - name: "LVM 快照恢复测试"
    frequency: "每周一次"
    team: "@infrastructure"

  - name: "pg_dump 恢复测试"
    frequency: "每天一次"
    alert_on_failure: true

  - name: "S3 备份完整性检查"
    frequency: "每小时一次"
    alert_on_failure: true

  - name: "Wal-E 归档验证"
    frequency: "持续监控"
    alert_on_delay: "超过 5 分钟"

2. 权限加固

改进了服务器权限配置:

# 数据库目录权限
chown -R postgres:postgres /var/opt/gitlab/postgresql
chmod 700 /var/opt/gitlab/postgresql/data

# 禁止非管理员用户访问
usermod -aG postgres工程师用户名
# 只有 DBA 和 SRE 才有 postgres 组权限

3. 危险命令拦截

引入了 rm -rf 的保护机制:

# 在 /etc/bashrc 中添加别名
alias rm='rm -i'                    # 交互式确认
alias rmrf='echo "rm -rf disabled, use gitlab-ctl"'  # 禁用直接 rm -rf

# 或者使用 safe-rm 等工具

4. 变更管理流程

建立了配置变更后的验证流程:

配置变更 → 自动化测试 → 备份验证 → 监控告警
    ↓          ↓            ↓          ↓
 如果失败    如果失败     如果失败    如果失败
 立即回滚    立即回滚     立即告警    立即升级

数据安全的最佳实践

GitLab 的这次故障,给整个技术行业上了一课。从这次故障中,可以总结出数据安全的最佳实践:

1. 备份的 3-2-1 原则

  • 3 份副本:原始数据 + 2 份备份
  • 2 种介质:比如磁盘 + 云存储
  • 1 份异地:至少一份备份存放在异地

2. 定期恢复演练

「有备份」和「备份能用」是两回事。必须定期做恢复演练,验证备份的可用性。

# 每月恢复演练清单
recovery_drill:
  - 目的数据库: "staging-server"
  - 恢复源: "生产库 24 小时前的备份"
  - 验证内容:
    - 数据完整性检查
    - 应用功能验证
    - 性能基线对比
  - 完成标准: "100% 数据恢复,功能测试通过"

3. 权限最小化原则

不要给工程师 root 权限,除非必要。数据库操作应该通过专门的工具进行,而不是直接用 shell 命令。

# 错误做法
ssh admin-server
sudo -i
rm -rf /var/opt/postgresql/data

# 正确做法
# 使用专门的数据库管理工具
gitlab-rails dbconsole -p
# 所有操作通过审计日志记录

4. 危险操作二次确认

对于高危操作(如删除数据、删除表),应该要求二次确认:

-- 数据库层面的保护
-- 创建删除保护触发器
CREATE OR REPLACE FUNCTION prevent_drop()
RETURNS event_trigger AS $$
BEGIN
    RAISE EXCEPTION 'Dropping tables is not allowed';
END;
$$ LANGUAGE plpgsql;

CREATE EVENT TRIGGER prevent_drop_table
    ON sql_drop
    EXECUTE FUNCTION prevent_drop();

思考题

问题 1:GitLab 的备份机制「全部失效」听起来不可思议,但为什么在实际项目中很常见?

参考答案

几个原因:1)备份是「保险」而不是「日常」——备份配置好后,没有人每天去检查它是否正常工作;2)备份失败通常不会主动告警——除非特意配置了监控;3)变更后不验证——比如迁移服务器后,备份脚本的配置路径可能失效,但没有人重新验证;4)「看起来有备份」给了团队虚假的信心——GitLab 配置了 5 种备份机制,但没有一种真正生效,但他们以为有保障。正确的做法是:把备份当作生产服务来维护,配置告警,定期演练。

问题 2:如果你是 GitLab 的工程师,在发现数据库被删除后,会采取什么顺序来恢复服务?

参考答案

恢复顺序取决于「恢复的目标」和「可用的备份」。一个合理的恢复策略:1)首先确认哪些备份是真正可用的——逐个尝试,记录每个备份的恢复时间和数据完整性;2)选择「最新可用」的备份——不一定是最新的(可能有 bug),但应该是最完整且可恢复的;3)优先恢复「最关键」的数据——用户代码 > Issue > 评论 > 其他;4)分阶段恢复——先恢复只读服务让用户可以看到数据,再逐步恢复写入功能;5)持续同步最新状态——恢复后,需要有机制同步 6 小时内的增量数据(如果有其他存储的话)。

问题 3:为什么 GitLab 的事后报告被认为是「最诚实的事后分析」?这对故障复盘有什么启示?

参考答案

几个原因:1)没有试图掩盖问题——承认了备份全部失效,而不是「部分备份受损」;2)提供了详细的时间线和根因分析——不只是说「操作失误」,而是分析「为什么操作会失误、系统哪里给了失误空间」;3)每项改进措施都有具体的负责团队和时间——而不是泛泛说「我们会加强备份」;4)对受影响用户坦诚——在官网实时更新进度,没有试图隐瞒。这给我们的启示是:诚实的复盘才能带来真正的改进。如果复盘变成了「甩锅大会」或「走过场」,团队就失去了学习的机会,同样的错误会再次发生。