Published

坏盘上的 GitLab:一次 Bitnami 11.2.3 数据抢救实录

Author

Robin

事情是从一个 Input/output error 开始的。

一台跑了多年的 GitLab 虚机,底层存储的物理盘出现坏扇区:在虚拟化管理界面里对它的虚拟磁盘做复制,直接报错。更麻烦的是两个现实:这台 GitLab 是 2018 年的 Bitnami 打包版 11.2.3,官方下载站早已整体下线,旧版 OVA 在归档站、Wayback Machine、第三方镜像里都找不到;而它的快照链和虚机本体躺在同一块坏盘上——快照这次不是保险,是陪葬品。

这篇文章不打算写成"数据恢复教程大全"。它记录的是这次抢救现场的判断顺序:哪些线绝对不能碰、动手的先后为什么反直觉、以及一份恢复流程——里面的每一步都是这次实操中确认过的,未经验证的操作我不会写进来充数。

一、先说危险线

抢救现场最容易死人的不是技术不够,是手快。动手之前,先把五条线划清楚:

1. secrets.yml 不可丢。 Bitnami 的密钥文件在 /opt/bitnami/gitlab/config/secrets.yml。GitLab 的 2FA 种子、CI/runner token 等一批字段是加密存在数据库里的,解密靠的就是这个文件。它丢了,数据库抢救得再完整,那些字段也全部解不开。更阴险的是:GitLab 自带的 rake 备份并不包含这个文件——备份做得再勤快,漏了它照样是半份备份。恢复时它也不会自己回来,必须手动放回原位。

2. 坏盘上的快照链一律不动。 快照和数据在同一块物理坏盘上时,consolidate、delete、revert 这类操作都会在坏盘上引发大量读写,本来只是部分扇区损坏,折腾一轮可能把链彻底弄断。快照增量盘脱离父盘链单独存在时也毫无意义——它只存差异块。同理,文件系统检查不要在原盘上跑,要跑在镜像上跑。

3. 备份和恢复目标,版本必须完全一致。 GitLab 的备份只能恢复到与备份时完全相同的版本:11.2.3 的备份只能进 11.2.3,不能"顺手升个级一步到位"。想升级,是恢复成功之后另开一条逐级阶梯的事,不能混在抢救里做。

4. 老版本全程内网隔离。 11.2.3 是 2018 年的版本,已知漏洞只多不少。抢救和恢复全程在内网完成,升到受支持版本之前不暴露公网。

5. 先抢数据,再折腾盘。 顺序永远是:开机把数据抢出来 → 拷到健康存储并校验 → 再回头处理盘。拷贝工具也有讲究:普通 cp 和虚拟化层面的磁盘复制遇到 I/O error 就直接失败,硬盘级对拷要用 ddrescue——断点续传、先抢易读区域、再回头啃坏区;不可读的区域它会补零跳过。注意"跳过"的意思是那部分数据认栽,不是变魔术。

二、抢救的顺序为什么反直觉

两个细节值得单独说,它们都和直觉相反:

先导数据库,再停服务。 常规维护是先停服务再备份,抢救不行——PostgreSQL 一旦停掉,pg_dumpall 就没法跑了。所以顺序是趁服务还在,先把全库 dump 导出来,再停服务打包文件:

# 1) 服务运行中,先导全库 dump
sudo /opt/bitnami/postgresql/bin/pg_dumpall -U postgres > /root/gitlab-db.sql

# 2) 停全部服务
sudo /opt/bitnami/ctlscript.sh stop

# 3) 整目录打包:--ignore-failed-read 是关键参数
sudo tar --ignore-failed-read -czf /root/bitnami-gitlab-rescue.tar.gz -C /opt bitnami

--ignore-failed-read 是这条命令的灵魂:没有它,tar 读到第一个坏扇区文件就整体报错退出,前功尽弃;有了它,读不了的文件跳过,其余数据成包。

打包完不等于抢救成功。 拷到健康机器之后要过三关才算数:dump 文件尾部有 PostgreSQL database dump complete 标记且大小合理(空库也有几 MB);源和副本两端 md5 一致;救援包能被 tar -tzf 完整列一遍不报错。三关都过,数据才算真正离盘。

三、恢复之前,先验五项定路线

数据在手,不要急着恢复。先回答五个问题,它决定走哪条路:

  1. secrets.yml 在不在?(不在,直接看 B 路里关于加密字段的部分)
  2. 数据库 dump 完整吗?(尾部标记 + 大小)
  3. 抽 3~5 个核心仓库做 git fsck:出现 dangling 可以忽略,出现 broken / missing / corrupt 说明该仓库已坏;
  4. 上传附件目录的大小对得上吗?(数量级对即可)
  5. 救援 tar 包本身完好吗?

A 路(secrets 在 + dump 完整):整库恢复,见下一节。

B 路(库坏了、但仓库目录大体完好):放弃整库。在干净环境里重建同名空项目,把裸仓库逐个 git push --mirror 推回去。issue 和 MR 历史认栽,只保代码——这很痛,但比在坏库上反复折腾到全灭强。

四、恢复:干净的 11.2.3 + 手动放回 secrets

A 路的第一步是准备一个干净的同版本环境。官方旧包下线的情况下,Docker Hub 上的官方镜像还在,按 digest 固定拉取防投毒:

docker run --detach --hostname gitlab-1123 --publish 8443:443 --publish 8080:80 \
  --name gitlab-1123 --restart always \
  --volume /srv/gitlab-1123/config:/etc/gitlab \
  --volume /srv/gitlab-1123/logs:/var/log/gitlab \
  --volume /srv/gitlab-1123/data:/var/opt/gitlab \
  gitlab/gitlab-ce:11.2.3-ce.0@sha256:b5927ed7ac79524251c3b717195c239023a33d169709c9f1d74f0a898975938f

第二步,在修好的旧实例上用 gitlab:backup:create 打官方备份包,把 tar 拷进新容器的 /var/opt/gitlab/backups/ 再执行恢复。注意 Bitnami 是自研打包(/opt/bitnami + ctlscript.sh),和 Omnibus 的目录结构完全不同,不能文件级拷贝硬搬,跨发行形态迁移只能走官方备份这条路。

第三步是最容易被漏掉的一步:恢复完成后,手动把抢救出的原 secrets 文件放回去(Bitnami 侧是 secrets.yml,Omnibus 侧对应 /etc/gitlab/gitlab-secrets.json)。千万不要让新环境生成一份新的——新密钥解不开旧数据,等于亲手把第一条危险线踩穿。

如果恢复是在 Bitnami 原机上用救援包做,顺序就是 2026-10-18 我实际确认过的那条:ctlscript.sh stop → 解开救援包覆盖文件 → 手动确认 secrets.yml 是抢救出的原文件 → ctlscript.sh start。

五、启动验证:2FA 能过,就是最硬的证据

恢复后不要只看"服务起来了"。验证清单四项,全过才算恢复成功:

  1. 服务全绿(ctlscript.sh status),Web 登录页能打开;
  2. 用原账号登录且 2FA 能通过——这一条同时证明了数据库完整、secrets 是原文件、加密链路通畅,是四项里信息量最大的一项;
  3. 抽查 2~3 个核心仓库能打开、能 clone;
  4. gitlab:check 无红项:
docker exec gitlab-1123 gitlab-rake gitlab:check SANITIZE=true

六、复盘:备份里最容易漏的三样东西

这次抢救还没完全收尾,但教训已经很清楚了:

  • 密钥文件不在备份里。 绝大多数系统的官方备份都不包含解密密钥——GitLab 的 secrets、各种应用的 .env、证书私钥。做备份方案时先问一句:"恢复时解不开的那部分,靠哪个文件?"那个文件要单独进备份清单。
  • 快照不是备份。 同一块物理盘上的快照,只防手滑删除,不防盘坏。备份的底线是另一块介质、最好另一个故障域。
  • 老版本要趁能跑的时候迁。 等盘坏了才想起升级,可选的路只剩"先原样复活再逐级爬阶梯"这一条,成本翻几倍。还在跑老版本系统的,趁它健康,把版本阶梯提前爬完。

最后留一句给同样在维护老系统的人:抢救现场拼的不是命令储备,是顺序和克制——先划危险线、先抢数据、每步留核验点。这三件事做对了,最坏的结果也有下限。


本文整理自 2026 年 10 月的一次真实抢救记录与恢复手册,内部环境细节已脱敏;命令与路径均为 Bitnami / GitLab 官方形态。恢复完成后的逐级升级阶梯不在本文范围。