本文是 GitLab 14 到 19 迁移系列 的运行保障补篇。环境名称、存储位置和保留周期均为泛化示例。
GitLab 迁入 Kubernetes 后,常见误解是“已经由 Kubernetes 管了,所以天然高可用”。如果 GitLab 只有一个 StatefulSet 副本,数据仍落在单节点本地盘,它只是换了运行位置,并没有消除单点。
四个概念不能混用
| 做法 | 能解决什么 | 不能解决什么 |
|---|---|---|
| 定时备份 | 误删、数据损坏、历史恢复 | 当前实例立即可用 |
| 冷备恢复 | 主机故障后的重建 | 很短的停机窗口 |
| 主备切换 | 单实例或节点故障 | 双端数据一致性与自动切换 |
| 完整 HA | 多副本组件、数据库、缓存、存储和入口故障 | 设计和运维复杂度 |
备份的目标是 RPO,恢复演练的目标是 RTO;两者都要量化。比如“每天一份备份”不等于能接受最多 24 小时的数据丢失,也不等于能在两小时内恢复服务。
对单节点 GitLab 更务实的三层保护
第一层是频繁、可校验的逻辑备份。备份至少包含仓库、数据库、附件、CI 相关数据以及配置和加密材料。只保存数据库 dump,常会在恢复后发现历史密钥无法解密。
第二层是异机保存。备份目录与 GitLab 落在同一块磁盘,只能防误操作,不能防节点或磁盘故障。至少准备一个独立接收端,并校验文件哈希。
第三层是定期恢复演练。把最近备份恢复到隔离环境,验证以下动作:
登录 → 查看项目 → clone → push 测试分支 → 打开附件 → 触发最小 CI
演练结果应记录实际耗时和失败点,才能反推 RTO 是否可接受。
一次可执行的备份流程
下面以 GitLab Omnibus 容器为例。Helm Chart 或 Operator 部署的备份入口可能不同,但“先识别版本与数据位置、再备份、最后在另一台机器核验”的顺序不变。所有变量均为占位符,执行前替换成自己的命名空间、Pod 与路径。
export GITLAB_NS=<gitlab-namespace>
export GITLAB_POD=<gitlab-pod>
export BACKUP_DIR=/data/gitlab-backup
export BACKUP_HOST=<backup-receiver>
# 先保存恢复卡所需的运行事实。
kubectl -n "$GITLAB_NS" get pod "$GITLAB_POD" -o wide
kubectl -n "$GITLAB_NS" exec "$GITLAB_POD" -- gitlab-rake gitlab:env:info
kubectl -n "$GITLAB_NS" get pvc,pv
在冻结写入窗口中创建 GitLab 逻辑备份。不要在备份途中执行版本升级、修改密钥或允许新的流水线写入。
# Omnibus 镜像的常见入口;若使用 Helm Chart,改用该 Chart 文档提供的任务入口。
kubectl -n "$GITLAB_NS" exec "$GITLAB_POD" -- gitlab-backup create
# 配置与密钥必须单独保存;真实路径随镜像和部署方式而变。
kubectl -n "$GITLAB_NS" exec "$GITLAB_POD" -- \
sh -ec 'tar -C /etc/gitlab -czf /tmp/gitlab-config-and-secrets.tgz gitlab.rb gitlab-secrets.json'
# 把备份文件复制到管理机后生成不可修改的校验清单。
mkdir -p "$BACKUP_DIR"
kubectl -n "$GITLAB_NS" cp "$GITLAB_POD":/var/opt/gitlab/backups "$BACKUP_DIR/backups"
kubectl -n "$GITLAB_NS" cp "$GITLAB_POD":/tmp/gitlab-config-and-secrets.tgz \
"$BACKUP_DIR/gitlab-config-and-secrets.tgz"
(cd "$BACKUP_DIR" && find . -type f -print0 | sort -z | xargs -0 shasum -a 256) \
> "$BACKUP_DIR/SHA256SUMS"
/etc/gitlab 与 /var/opt/gitlab 只是 Omnibus 的常见目录,不是通用路径。执行复制前先用 kubectl exec ... -- find 或部署文档确认;不要因为示例路径不存在而临时跳过密钥备份。
备份必须离开故障域
同节点、同存储池的第二份文件不能算异机备份。传输后应在接收端重新校验,而不只是看 rsync 的退出码。
# --partial 保留中断后的临时文件;--append-verify 适合大文件断点续传。
rsync -aH --partial --append-verify "$BACKUP_DIR/" \
"<backup-user>@$BACKUP_HOST:/srv/gitlab-backup/$(date +%F)/"
# 本地和接收端都校验同一份清单。
(cd "$BACKUP_DIR" && shasum -a 256 -c SHA256SUMS)
ssh "<backup-user>@$BACKUP_HOST" \
'cd /srv/gitlab-backup/<backup-date> && shasum -a 256 -c SHA256SUMS'
验收时至少记录备份文件名、总容量、SHA-256、GitLab 版本、创建时间和接收端位置。恢复卡只保存位置和密钥引用,不保存口令、Token 或私钥正文。
恢复演练要验证“能用”,不是“能启动”
演练环境必须和生产入口、生产数据库隔离。先让旧的演练 StatefulSet 停止,再通过一次性迁移 Job 或受控挂载把已验证的备份放入演练数据卷;随后启动演练 Pod,并在 Pod 内停止 GitLab 应用进程后恢复。恢复命令必须与原部署形态和版本匹配。
export DRILL_NS=<isolated-drill-namespace>
# 防止恢复期间有旧进程继续访问同一块数据卷。
kubectl -n "$DRILL_NS" scale statefulset/<gitlab-statefulset> --replicas=0
kubectl -n "$DRILL_NS" wait --for=delete pod/<gitlab-pod> --timeout=180s
# 此处由迁移 Job 将备份和配置放入演练 PVC;不要挂载生产数据卷。
kubectl -n "$DRILL_NS" apply -f gitlab-restore-data-job.yaml
kubectl -n "$DRILL_NS" wait --for=condition=complete job/gitlab-restore-data --timeout=15m
# 启动隔离的演练实例,再停止应用进程后恢复;不要跨大版本直接套用。
kubectl -n "$DRILL_NS" scale statefulset/<gitlab-statefulset> --replicas=1
kubectl -n "$DRILL_NS" wait --for=condition=Ready pod/<gitlab-pod> --timeout=10m
kubectl -n "$DRILL_NS" exec <gitlab-pod> -- gitlab-ctl stop puma sidekiq
kubectl -n "$DRILL_NS" exec -it <gitlab-pod> -- \
gitlab-backup restore BACKUP=<backup-id>
kubectl -n "$DRILL_NS" get pod,pvc
kubectl -n "$DRILL_NS" logs <gitlab-pod> --tail=200
最后用一个专用测试项目走完整读写路径:网页登录、浏览项目、git clone、推送临时分支、下载附件和触发最小 CI。测试分支与测试制品应在验收后删除。只有这些动作成功,才能把该备份记为“可恢复”。
为什么不建议自制“双活同步”
两套 GitLab 实例之间直接同步数据目录、数据库或 registry 文件,通常不是可靠的 HA。仓库、PostgreSQL、Redis、Sidekiq、上传文件与配置密钥的写入顺序不同,文件层复制无法建立一致性边界。
如果确实需要更短恢复时间,应选择官方支持的架构:受支持的数据库复制、缓存高可用、对象存储或共享存储、冗余入口,以及与版本相匹配的 GitLab HA 方案。它的代价远高于单节点恢复,需要先用 RPO/RTO 和实际停机成本证明必要性。
一张必须长期维护的恢复卡
每次升级、迁移或密钥变更后,更新一张不含明文密码的恢复卡:GitLab 版本与镜像 digest、备份位置与校验值、外部 PostgreSQL 版本、配置和密钥备份位置、恢复顺序、入口切换方法、最近一次演练日期和负责人。
这张卡的价值在于:故障发生时,不用临时猜“应该先恢复数据库还是先启动 GitLab”。
回退原则
升级或迁移失败时,不要在原数据卷上反复尝试不同版本。停止新实例写入,保留失败现场日志;用已校验的同版本镜像、配置和备份在隔离环境复原,核对数据后再切回入口。备份、密钥或版本任一项无法对应时,先恢复到只读调查状态,再决定下一步,而不是继续覆盖数据。
DISCUSSION
讨论与反馈