1. 先搞清楚一件事:你备份的到底是什么
很多团队用 GitLab 用了一两年,备份脚本也配了,Cron 定时任务也跑着,但真到服务器硬盘报错、机房断电、或者领导突然说"这台机器要退役,数据迁到新机器"的时候,才发现自己对备份的理解只停留在"跑一下 backup 命令"。GitLab 的备份不是简单地把/var/opt/gitlab目录打个包就能完事的,它至少有四个状态需要你分别对待。
1.1 GitLab 的四个状态组成:代码仓库、数据库、配置文件、密钥文件
GitLab 本质上是一个组合体:底层跑着 PostgreSQL 数据库、Redis 缓存、Gitaly 仓库存储服务、Sidekiq 异步任务,前面还有 Nginx 和 Puma。代码仓库本身存在 Gitaly 管理的 repository 目录里,但仓库里的所有元数据、权限关系、合并请求、Issue、CI/CD 变量、Webhook 配置、用户账号,全都存在 PostgreSQL 数据库里。所以备份至少要做两件事:把数据库完整导出,把仓库目录同步走。
但更关键的是/etc/gitlab/gitlab.rb和/etc/gitlab/gitlab-secrets.json这两个文件。gitlab.rb是你的配置,gitlab-secrets.json是加密密钥文件。我在实际维护中见过不少人恢复完 GitLab 后页面能打开,但用户密码全部失效、CI/CD 变量读不出来、Runner 连接异常,最后排查下来都是因为这个密钥文件没跟着备份。因为 GitLab 里很多敏感数据(比如外部服务 Token、数据库加密字段)都是用这个密钥文件加密的,恢复后的环境必须用回原来的密钥才能解开。丢失它,备份数据库里的密文就是一堆无意义的乱码。
1.2 官方备份命令到底做了什么
Omnibus 安装包自带的gitlab-backup(老版本是gitlab-rake gitlab:backup:create)本质上做了几件事:
- 调用
pg_dump导出 PostgreSQL 数据库中所有 GitLab 相关库; - 压缩并打包
/var/opt/gitlab/git-data/repositories下的仓库裸仓库目录; - 同步
/var/opt/gitlab/uploads里的附件上传文件; - 打包
/var/opt/gitlab/artifacts、/var/opt/gitlab/pages等公共目录; - 最后生成一个带时间戳的 tar 包,默认放在
/var/opt/gitlab/backups。
所以默认命令其实已经覆盖了代码和业务数据,但它没有把/etc/gitlab/gitlab.rb和/etc/gitlab/gitlab-secrets.json打包进去。官方文档也明确说这两个文件需要你手动备份。所以正确做法是把备份脚本写成两步:先备份配置和密钥,再执行全量备份。
1.3 一个完整的备份命令实例
下面是我在实际环境中用的脚本结构,兼顾了两步走和保留策略:
#!/bin/bash # GitLab backup with config and secrets BACKUP_BASE="/var/opt/gitlab/backups" CONFIG_BACKUP="$BACKUP_BASE/config-$(date +%Y%m%d%H%M%S).tar.gz" KEEP_DAYS=7 # 1. 备份配置文件与密钥 tar czf "$CONFIG_BACKUP" -C /etc/gitlab gitlab.rb gitlab-secrets.json # 2. 官方全量备份 /usr/bin/gitlab-backup create BACKUP=latest STRATEGY=copy GZIP_RSYNCABLE=yes # 3. 清理超过保留时间的备份文件 find "$BACKUP_BASE" -maxdepth 1 -type f -name "*.tar" -mtime +$KEEP_DAYS -delete find "$BACKUP_BASE" -maxdepth 1 -type f -name "config-*.tar.gz" -mtime +$KEEP_DAYS -delete这里有两个参数值得单独说:
STRATEGY=copy:默认方式是tar直接读取仓库目录,如果备份期间有人正在 push 代码,可能读到不一致的中间状态。指定copy会先把仓库目录硬链接到临时目录再打包,降低这种风险,但会多占用一份磁盘临时空间。GZIP_RSYNCABLE=yes:让 tar 包内部文件的压缩对齐方式更合理,方便以后做增量同步或者用 rsync 传输。代价是备份耗时稍微变长。
注意:
BACKUP=latest这个参数在部分版本里用来指定备份文件名;如果脚本里不写,默认也会用latest标识,问题不大。但如果你在同一台机器上多实例运行 GitLab,必须显式指定实例名。
2. 备份实操:默认配置能跑通,但生产环境不能直接照搬
好多人都经历过这种情况:gitlab-backup create命令跑完,看到Backup task is done就放心了。问题是备份文件落在系统盘上,系统盘一旦损坏,备份跟着一起没。备份真正要防的恰恰是"服务器整机不可用"这个场景,所以备份文件不能只留在一台机器的本地磁盘。
2.1 备份目录选型和空间估算
Omnibus 默认的备份目录是/var/opt/gitlab/backups,它是 GitLab 安装目录所在分区的一部分。如果/分区不够大,大仓库打包到一半就会报No space left on device。我一般建议单独给备份挂一个分区,或者直接挂到外部存储。
空间怎么估算?一个简单公式:备份包大小大约是仓库磁盘占用量的 60% 到 80%(取决于仓库类型和压缩效率),数据库导出通常不大,一般几 MB 到几 GB。但仓库目录里有大量objects/pack文件,本身已经是压缩格式,再打包收益有限。所以如果你仓库总共占 200GB,备份包可能要 150GB 左右。在出备份方案时,我习惯按仓库占用空间的 1.2 倍预留临时空间,因为备份过程中copy策略还会多一份临时文件。
想精确查看仓库目前占多大,可以用:
du -sh /var/opt/gitlab/git-data/repositories du -sh /var/opt/gitlab/backups2.2 把备份推送到远端:对象存储和 NFS 的取舍
GitLab 官方支持把备份目录直接挂到 NFS 或者 S3 兼容对象存储上,但我更推荐"本地落盘 + 定时同步"的方式。原因是 GitLab 的备份进程对文件系统性能和锁要求比较敏感,直接写到对象存储偶尔会出现上传中断、文件不完整的问题,虽然 GitLab 新版在改进,但没必要在生产环境冒这个险。
实测比较稳妥的方案是:备份先写本地磁盘,再用脚本把备份包rsync或通过s3cmd put推到远端。推送完比对一下文件大小和 md5,确认一致再删本地的旧备份。
# 推送到 S3 兼容存储示例 s3cmd put /var/opt/gitlab/backups/*.tar s3://your-bucket/gitlab-backup/ \ --no-progress \ --multipart-chunk-size-mb=64 # 推送后校验 s3cmd ls s3://your-bucket/gitlab-backup/ | tail -5对象存储的好处是天然异地冗余,NFS 的好处是恢复时可以直接把备份目录指过去,不需要先下载到本地。但如果你的 NFS 本身就在同一机房同一机架,那就没有"异地容灾"的意义了,至少要做到跨机架或者跨机房。
2.3 Cron 定时备份要处理的并发和覆盖问题
Cron 定时备份本身不复杂,但有一个坑很多人踩过:gitlab-backup create默认备份文件名包含时间戳,理论上不会互相覆盖,但如果上一次备份因为仓库太大还没跑完,下一次 Cron 又触发了,两个备份进程会同时抢/var/opt/gitlab/.gitlab-backup目录下的临时锁,导致其中一个进程直接报错退出,留下一个残缺的 tar 包,看起来"成功"了,实际恢复时才发现文件不完整。
解决办法是在脚本开头加个简单的锁判断:
LOCKFILE="/var/opt/gitlab/backups/.backup.lock" if [ -f "$LOCKFILE" ]; then echo "Another backup process is running, skip this run." exit 0 fi touch "$LOCKFILE" trap 'rm -f "$LOCKFILE"' EXIT同时给 Cron 任务加一个合理的执行时间窗口。比如每天凌晨 2 点执行,如果仓库特别大,可以预留一个 4 小时的窗口,不要设置在业务高峰附近。GitLab 备份本身对 IO 的消耗很大,尤其是STRATEGY=copy会额外产生大量硬链接和文件遍历,业务量大时跑备份会拖慢 Gitaly 响应。
2.4 增量备份和仓库级备份的适用场景
很多从数据库领域过来的人会下意识问:GitLab 支持增量备份吗?官方全量备份工具在很长一段时间内都不支持增量。GitLab 17.x 之后开始推"仓库级备份"(Repository Backup)功能,可以只备份某个仓库或者按组备份,但数据库、对象存储、元数据仍然需要整体备份覆盖。
如果你要的是"RPO(恢复点目标)尽可能短",更靠谱的方案是给底层 PostgreSQL 做持续归档(WAL 归档),或者把 GitLab 的数据库实例做成流复制。但这样做复杂度会上一个量级,不太适合中小团队。我的观点是:对于绝大多数团队,每天一次全量备份 + 每天定时把备份推送到异地,RPO 在 24 小时以内,已经完全够用。代码仓库这种数据,哪怕丢一天,通常也就是重推一次提交的事,比数据库业务数据好恢复得多。
3. 恢复的完整链路:从备份文件到可访问服务
恢复这件事,理论上就是一条命令:gitlab-backup restore BACKUP=时间戳。但我在实操中从没见过哪次生产恢复是"一条命令没事"的。恢复过程的坑比备份多得多,核心原因在于 GitLab 对版本、环境、权限的要求非常苛刻。
3.1 恢复前的硬性前置检查
恢复前必须确认的东西先列个清单:
- 版本匹配:备份文件是哪来的,最好就用同一个 GitLab 大版本去恢复。如果你想恢复到更高版本的 GitLab,官方要求先安装与备份时完全相同的版本去恢复,再走升级流程,不能"备份是 15.11 的,直接恢复到 17.4 的实例"。跨大版本直接恢复,数据库迁移脚本会直接把你的数据搞挂。
- 空实例原则:恢复前目标实例必须是一个全新安装、还没创建过任何项目的 GitLab。如果目标实例已经存在项目,恢复时可能会因为仓库路径冲突直接失败。最稳妥的做法是先
gitlab-ctl stop puma sidekiq停掉服务,如果有旧数据,先整体挪走。 - 磁盘空间充足:备份包 + 解压后的数据量 + 数据库导入临时文件,这些加在一起,需要比备份包大出至少一倍的空间。我第一次做恢复演练时就吃过亏:备份包 80GB,解压加导入峰值直接飙到 190GB,系统分区差点塞满。
3.2 恢复操作的分步过程
以 Omnibus 安装的 GitLab 为例,恢复流程大概是这样的:
# 1. 停掉相关服务,避免恢复过程中有写入 sudo gitlab-ctl stop puma sidekiq # 2. 确认识别到备份文件(文件必须在备份目录里) sudo gitlab-backup restore BACKUP=1722233300_2024_07_29_16.9.0 # 3. 恢复完成后,重建相关缓存和状态 sudo gitlab-ctl reconfigure # 4. 重启所有服务 sudo gitlab-ctl restart # 5. 如果恢复的是配置和密钥,还要重启一次让所有进程读取最新配置 sudo gitlab-rake gitlab:checkgitlab-backup restore在执行过程中会做一个检查,先确认数据库和 GitLab 版本是否兼容,然后解包仓库目录到临时位置,再导入数据库,最后移动仓库目录到目标位置。它不会自动恢复/etc/gitlab/gitlab.rb和gitlab-secrets.json,这两个必须先在恢复前手动放回去。如果你忘了放gitlab-secrets.json,恢复后 GitLab 能启动,但所有带加密的字段都用新密钥去解密旧数据,结果就是一堆解密失败报错。
3.3 恢复后至少要验证的 8 个点
验证备份是否真正恢复成功,不是看页面能打开就算数。我给自己定了一个恢复后检查清单,每次恢复演练、真实恢复、迁移落地都要逐项过一遍:
- 用管理员账号登录 Web UI,确认能登录、能切换页面,没有 500 错误。
- 新建一个临时测试项目,clone 下来,提交一个文件再 push 回去,确认仓库读写链路正常。
- 查看仓库的提交历史、分支、Tag 列表,和备份前记录做对比。
- 查看几个关键项目的 CI/CD 变量、Webhook、保护分支规则是否还在。
- 检查
/var/opt/gitlab/git-data/repositories下的仓库目录数量和备份前是否一致。 - 用管理员进入 Admin Area -> Users,抽查几个非管理员用户的权限。
- 查看
gitlab-rake gitlab:check输出,重点看gitlab-shell、GitLab API、Database这几项是否为绿色。 - 确认 Sidekiq 队列没有积压异常任务:
gitlab-rails runner 'puts Sidekiq::Queue.new.size'。
这套检查看着多,实际走一遍也就一两个小时,但能避免"两周后发布版本时才发现 CI 变量不见了"这种灾难。
3.4 单仓库恢复的思路
如果你只是想恢复某一个仓库,而不是整个 GitLab 实例,有些情况下不需要做全量恢复。比如某个仓库被误删了,但全量备份还在,你可以用 GitLab Rails Console 从备份中把仓库单独捞出来。
大体思路是:准备一台临时 Ubuntu 或测试机,安装同版本 GitLab,恢复全量备份,然后单独导出目标仓库的bundle文件,再导入到生产实例:
# 在临时实例上导出单仓库 sudo gitlab-rake gitlab:repo:export \ RAILS_ENV=production \ username=group_name \ project=project_name \ archive_path=/tmp/export # 在生产实例上导入 sudo gitlab-rake gitlab:repo:import \ RAILS_ENV=production \ username=group_name \ project=project_name \ archive_path=/tmp/export但说实话,如果不是仓库量巨大、不能停机,我更推荐直接全量恢复。单仓库恢复要处理权限、群组、CI/CD 变量等各种依赖,坑更多。
4. 迁移方案:换机、换版本、换部署方式的三条路径
备份和恢复是基础,迁移是更复杂的场景。迁移不只是"恢复到一个空实例",通常还夹杂着版本升级、部署方式变化、域名变化、存储路径变化这些东西。我把迁移拆成三种情况,分别说清楚。
4.1 同版本同架构迁移:最快的方式
场景:旧机器要退役,新机器配置更高,两边都是同一个大版本的 Omnibus GitLab,都是 Linux x86_64。
这种迁移其实比"备份恢复"还简单,因为不涉及版本差异。我个人最常用的是"整体目录拷贝 + 重装配置"的方式,而传统备份恢复适合小仓库、讲究流程的场景。整体目录拷贝的步骤如下:
- 新机器安装与旧机器完全相同的 GitLab 版本,完成初始化配置。
- 在旧机器上停掉 GitLab 服务:
sudo gitlab-ctl stop,保证数据一致。 - 把
/etc/gitlab整个目录、/var/opt/gitlab整个目录用rsync同步到新机器。命令大概是:
rsync -avzP --delete \ -e ssh \ /etc/gitlab/ root@newserver:/etc/gitlab/ rsync -avzP --delete \ -e ssh \ /var/opt/gitlab/ root@newserver:/var/opt/gitlab/- 新机器执行
gitlab-ctl reconfigure和gitlab-ctl restart。
这里的关键点是两个版本必须完全一致,大版本一致不够,小版本也要一致,因为/var/opt/gitlab下的数据结构可能因为补丁升级而发生变化。如果不一致,轻则 reconfigure 报错,重则数据库起不来。这个方案的好处是速度快,数 TB 数据也能通过 rsync 增量同步在可接受时间内完成。
4.2 跨版本迁移:必须遵守"逐版本升级"的铁律
GitLab 官方升级路径的规则是:不能跨大版本直接升级,必须按照大版本逐个升级,而且每个大版本还要考虑"最后一个次要版本"作为跳板。典型的升级路径是15.11.x -> 16.0.x -> 16.11.x -> 17.0.x。这样做不是因为官方想折磨人,而是数据库迁移脚本只保证"相邻版本"之间的兼容性。
所以跨版本迁移的正确姿势是:
- 线上备份,停写操作。
- 在原机器上先升级到目标路径上的第一个中间版本,跑一遍启动验证。
- 再升级到下一个中间版本,再验证。
- 最后升级到最终版本。
- 在最终版本上做一次完整备份,再恢复到新机器。
这个过程中最忌讳的就是"备份是旧版本,直接恢复到新版本实例,然后跑reconfigure期望它自动迁移数据库"。我见过有人从 GitLab 13 直接备份恢复到 GitLab 16,结果数据库在db:migrate阶段直接崩了,日志里面全是 SQL 语法错误。
如果你确实是"旧版本备份文件 + 新版本空实例"的处境,唯一的补救路径是先回退安装一个与备份一致的旧版本,在旧版本上恢复,再一级一级升级上来。这个时间成本通常很大,但至少数据不会丢。
4.3 Docker/K8s 和裸机之间的迁移
现在很多团队用的 GitLab 是 Docker 镜像部署的,迁移逻辑又不一样。Docker 部署时,官方推荐的持久化目录主要三个:
/etc/gitlab:配置文件与密钥/var/log/gitlab:日志/var/opt/gitlab:数据
迁移到新 Docker 环境时,最省事的方式是先把这几个目录从容器挂载卷里拷出来,再到新环境起一个同版本容器,把这些目录映射进去。但有个细节很多人忽略:Docker 版的 GitLab 里默认的备份命令是gitlab-backup create,恢复命令一样,但必须先进入容器再执行:
docker exec -it <container_name> gitlab-backup create # 恢复时 docker exec -it <container_name> gitlab-backup restore BACKUP=时间戳K8s 上的 GitLab(无论是官方 Helm Chart 还是 Operator)迁移就更复杂,一般不走gitlab-backup,而是直接用持久卷快照、或者用gitlab-backup恢复到新 PVC 实例。如果是中小团队自建的简化版 K8s 环境,我的建议是别贪复杂,直接按裸机思路处理:先备份,在临时 PVC 实例上恢复,确认无误后再切流量。
4.4 迁移后跑一遍 CI/CD 生态适配检查
很多人以为"数据迁过去了,GitLab 能打开,迁移就成功了"。实际上你的 CI/CD 体系里还有一堆东西绑在旧环境中:
- Runner 重新注册:旧 Runner 的
config.toml里有旧实例的注册 Token 或 URL,迁移后如果不重新注册,CI 任务会一直卡在 pending 状态。 - SSH Host Key:如果客户端的 known_hosts 里缓存了旧机器的指纹,迁移后第一次拉代码会报 Host Key Verification Failed,需要更新。
- Webhook 地址:所有配置了旧域名/旧 IP 的 Webhook,都要批量改成新地址,否则第三方系统调不通。
- 域名和反向代理:如果 GitLab 前端有 Nginx 反代,迁移后要检查反代配置里的 upstream 地址是否指向新实例。
这些内容官方文档不会一次性提醒你,但它们才是"迁移完能不能正常干活"的关键。
5. 我踩过的坑:备份成功但恢复失败的几个根因
备份与恢复,最难受的不是"备份失败",而是"备份日志显示成功,恢复时才失败"。以下五个问题,我都在不同环境里真实遇到过,每个都花了不少时间排查。
5.1 gitlab-secrets.json 缺失,恢复后用户密码全部失效
有一次做容灾演练,我特意模拟"只恢复数据,不恢复配置"的场景。结果恢复完成后,Web 能打开,但所有账号密码都不对,gitlab-rails runner查用户数据也正常,数据库里用户记录都在。最后才想到是gitlab-secrets.json没恢复,GitLab 生成的新密钥和数据库里的密文对不上。
这个问题的本质是:用户密码字段、2FA 相关数据、CI/CD 项目变量、外部集成的 Token 都是加密存储的,加密密钥就存在gitlab-secrets.json里。恢复时必须让新实例读到旧密钥,我的做法是先停服务,把旧密钥放回去,再执行恢复。如果你已经启动了服务,务必将服务全部停止,然后替换密钥,再重新启动。
5.2 备份文件权限对,但 NFS 挂载导致文件损坏
有一段时间我们备份目录直接挂在 NFS 上,gitlab-backup create的日志从头到尾没报错。直到一次真实恢复时,tar 包解压到一半报Unexpected EOF,才发现 NFS 上有几个文件大小不对。检查下来是 NFS 客户端写入缓存和 GitLab 进程的 flush 时序问题,文件虽然显示写完了,但实际没完全落盘。
从那以后我再也没把 GitLab 备份目录直接放在 NFS 上。生产环境的备份方案改成了"本地磁盘 + rsync 远端同步",并且在远端同步校验文件大小。
5.3 备份时磁盘空间满载,备份包"假成功"
还有一种情况更隐蔽:备份过程中磁盘空间不够,tar 打包直接退出,但gitlab-backup create因为某个子任务已经成功,返回了非零退出码,Cron 没感知到(因为很多人只重定向输出,没检查退出码),就认为备份成功了。实际生成的 tar 包是残缺的,文件大小比正常备份小一大截。
后来我在脚本里加了"备份后自动校验大小"的逻辑:
backup_file=$(ls -t /var/opt/gitlab/backups/*_gitlab_backup.tar | head -1) min_size=$((10 * 1024 * 1024 * 1024)) # 10GB,根据实际环境调整 actual_size=$(stat -c%s "$backup_file") if [ "$actual_size" -lt "$min_size" ]; then echo "Backup file too small, maybe failed!" exit 1 fi5.4 跨版本恢复时 PostgreSQL 版本不匹配
GitLab 每次升级都可能伴随 PostgreSQL 大版本升级,比如 13.x 可能带 PostgreSQL 12,到 16.x 已经带 PostgreSQL 14。如果你备份时的 GitLab 内置 PostgreSQL 是 12,恢复目标实例的 PostgreSQL 是 14,gitlab-backup restore一般会拒绝导入。
Omnibus 的gitlab.rb里可以配置 PostgreSQL 数据目录,但更稳妥的是恢复前先用gitlab-ctl status确认版本,或者干脆安装与备份相同版本的 GitLab 再做恢复。如果你确实要在高版本 PostgreSQL 实例上恢复旧数据,必须先用旧版本实例恢复,然后走一次 GitLab 升级流程,让内置的迁移脚本自动处理 PostgreSQL 的升级。
5.5 Cron 任务没有加锁导致的重复备份互相覆盖
这个问题前面提过,但值得再强调一次出现后的现象:备份目录里有多个 tar 包,时间戳只差几小时,文件大小忽大忽小,但你看 Cron 日志,每次执行都"正常完成"。实际上第一次备份进程还没结束,第二次已经开始,两个进程共享同一个临时数据库 dump 文件,导致其中一个 dump 内容被覆盖,生成一个数据不完整的备份。
加锁的思路前面已经给了代码。还可以在 Cron 里指定一个足够长的执行窗口,并且在执行前检查是否有遗留进程:
pgrep -f "gitlab-backup create" && exit 06. 备份恢复演练:怎么确定你的备份真的能救你
"备份有没有用"这件事,只有在真实恢复时才能证明。我见过太多团队,脚本跑了半年,从没验证过恢复,等真出事才发现备份文件是坏的。所以备份方案必须配恢复演练,而且演练要有周期性、有记录、有报告。
6.1 演练频率和演练环境怎么搭
建议至少每季度做一次完整恢复演练。演练环境不一定要和正式环境同等配置,但版本必须一致。如果你用 Docker,那最简单,起一个同版本 GitLab 容器,把备份数据恢复进去,跑一遍检查清单,验证完直接销毁容器。
如果公司有条件,我建议把演练环境固定成一套"恢复演练专用虚拟机",平时关机,需要时开机。这样想验证哪个备份点都可以随时拉起。
6.2 演练脚本化:把恢复动作固化下来
恢复操作如果不写成脚本,每次演练都会有手忙脚乱的问题。下面是一个简单的演练脚本骨架:
#!/bin/bash # Assume fresh GitLab installed at /etc/gitlab and data dirs empty set -e # 1. stop services sudo gitlab-ctl stop puma sidekiq # 2. restore config and secrets sudo tar xzf /backup/config-*.tar.gz -C /etc/gitlab # 3. copy backup file sudo cp /backup/*_gitlab_backup.tar /var/opt/gitlab/backups/ # 4. run restore sudo gitlab-backup restore BACKUP=$(basename /var/opt/gitlab/backups/*_gitlab_backup.tar | sed 's/_gitlab_backup.tar//') # 5. reconfigure and restart sudo gitlab-ctl reconfigure sudo gitlab-ctl restart # 6. basic verification sudo gitlab-rake gitlab:check有了这个脚本,每次演验证就是"新装系统 -> 跑脚本 -> 验证"三步走,时间能压缩到半天以内。
6.3 恢复演练发现的典型问题清单
根据我自己的演练经验,最常发现的问题有这几类:
- 备份目录保留时间设得太短,演练时才发现需要的备份点已经被自动清理了。
- 备份定时任务只配了备份,没配推送远端,演练时发现远端根本没有备份包。
- 数据库备份成功,但仓库目录备份因为文件权限问题被跳过,日志里有一行 WARNING,平时没注意。
- 恢复时才发现
/var/opt/gitlab/backups所在分区空间不足,解压到一半失败。 - 备份文件的用户属主变成了 root,恢复进程是 git 用户,直接报 Permission denied。
这些问题全都可以通过一次定期演练提前暴露。所以别再问"备份有没有必要做演练"这种问题了,真到了数据丢完再后悔,代价远大于演练投入的时间。
最后分享一个小技巧:我习惯在每次重大变更(升级大版本、迁移机器、调整存储路径)前后,都手动触发一次全量备份,并把备份文件名的备注信息写到变更记录里。这样如果变更出了问题需要回滚,我能清楚地知道"哪个备份点是变更前最后一个可靠状态"。别完全依赖 Cron 自动备份,重大操作前后的手动备份,是性价比最高的保险。