news 2026/9/11 19:47:13

GitLab迁移实战:从CentOS到Docker Compose的数据零丢失指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GitLab迁移实战:从CentOS到Docker Compose的数据零丢失指南

安全团队又发来漏洞通告,GitLab 又爆高危。换以前,直接yum update一条命令解决,但这次卡住了——那台跑了两三年的 GitLab 还活在 CentOS 原生环境里,系统生命周期已经结束,软件源里根本没有新版 GitLab 的包。硬扛还是迁移?我选了后者,把 GitLab 从 CentOS 原生安装迁到了 Ubuntu 上的 Docker Compose,全程数据零丢失,备份恢复、切换校验、故障排查都完整走了一遍。这篇文章就是那次迁移的复盘,把关键命令、版本对齐原则、数据校验清单,以及用户反馈最多的几个报错一次说清楚。如果你正准备做 GitLab 迁移,或者刚把 GitLab 装进 Docker 正被各种报错折腾,这篇可以直接拿来当操作手册。

1. 迁移前的清醒账本:版本、数据、风险一起摸清

1.1 先说清楚:这趟迁移到底图什么

很多人看到“迁移”两个字就先慌了,其实第一步不是动手,而是把“为什么迁”想明白。我当时的情况很典型:CentOS 7 的生命周期在 2024 年 6 月 30 日正式终止,之后整个系统都没有官方安全更新。GitLab 隔三差五就有高危漏洞通告,而修复方案基本都得靠升级包,但系统源里已经拿不到了。继续留在原地,等于让整个代码仓库暴露在已知漏洞面前,安全审计这一关就过不去。

选择 Ubuntu 的理由很直接:Ubuntu LTS 版本维护周期长、社区活跃、硬件兼容性好。选择 Docker Compose 的理由更实际——部署方式统一,环境和宿主机隔离,升级就是改个镜像 tag 再重启,回滚也快;而且数据目录固定成三个 volume,备份、迁移、灾难恢复都好打理。但这里我得说句实在话:Docker 化不等于数据安全,容器里的数据一样会丢,备份该做还是得做,后面我会给一套能直接用的备份脚本。

1.2 旧环境信息清单:不知道这些别动刀

迁移之前,一定先把旧环境的信息完整记下来。我自己列过一张清单,照着抄就行:

  • GitLab 版本:查看/opt/gitlab/version-manifest.txt,或者执行gitlab-rake gitlab:env:info,输出的 GitLab 版本号要记准,这是后面版本对齐的依据。
  • 数据目录/var/opt/gitlab是主数据目录,里面有 repositories(仓库)、postgresql(数据库数据)、uploads(附件)、backups(备份产物)。
  • 配置与密钥/etc/gitlab/gitlab.rb是配置文件,/etc/gitlab/gitlab-secrets.json是密钥文件。这两个必须单独备份,很多迁移失败都是栽在 secrets 文件上。
  • 定制配置:external_url、SSH 端口、SMTP 发信、LDAP 对接这些,全部在gitlab.rb里,用grep -v '^#' /etc/gitlab/gitlab.rb过滤出有效配置项,逐条记录。
  • 业务规模:管理员后台里看项目数、用户数、组数、Runner 数、CI/CD 变量数量。这些数字迁移后要逐项核对,是“数据零丢失”最直接的证据。

我当时把项目数、用户数、最重要的几个仓库的最新 commit 哈希都截图存档了,后面校验时对得上,心里才踏实。

1.3 风险评估:最怕的不是丢数据,是版本对不上

很多人以为迁移最大的风险是数据损坏,其实更常见的坑是版本不匹配。GitLab 的备份恢复机制对版本有严格要求:备份文件里写死了生成时的 GitLab 版本,恢复时目标实例版本太新或太旧,都会报错,甚至跨大版本强行恢复会导致数据库 schema 错乱。

我定的预案是:新环境先用 Docker 部署一个和旧环境完全相同的版本,恢复备份,验证没问题之后,再按官方升级路径一步步往上升级。这个过程后面会细说,但你可以先把这条原则刻在脑子里——永远不要在全新版本上直接恢复旧备份

另外,迁移时间窗一定要选业务低谷。我选的是周五晚上,预估停机时间 2 小时,实际用了 1 小时 20 分钟,回滚方案也提前写好了:新环境恢复失败、确认无法短时间内修复时,旧机器还保留着原样,直接把 DNS 指回旧 IP 就能切回旧环境。

2. 新环境搭建:Ubuntu Server + Docker Compose 一次到位

2.1 Ubuntu 系统初始化的几个动作

我新装的是 Ubuntu Server 24.04 LTS,安装过程不多说,重点讲几个容易忽略的初始化动作:

  • 系统更新apt update && apt upgrade -y,装完重启一次,确保内核也是最新的。
  • 主机名和 hosts:趁着一开始就把hostname/etc/hosts配好,避免 GitLab 内部依赖主机名的地方出诡异问题。如果你用了域名访问,把域名也写进 hosts 指到本机 IP,后面测试方便。
  • 防火墙:如果启用了 UFW,放行 80、443(HTTP/HTTPS)和 2222(SSH 映射端口)。注意 Ubuntu 默认没开 UFW,但安全要求严格的环境通常会开。
  • 磁盘规划:GitLab 的 data 目录建议独立分区挂载,我当时是把一块 500G 的云硬盘挂到/srv/gitlab下面,避免系统盘满导致整个实例不可用。

2.2 安装 Docker Engine 与 Compose 插件

新版 Docker 已经自带 Compose 插件,不用再单独装老式的 Python 版docker-compose。直接加 Docker 官方 apt 仓库,然后安装:

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker docker compose version

这里有个经验:安装完成后,docker compose version能正常输出版本号才算装好。如果你在别的教程里看到docker-compose(带横杠)的用法,多半是老版本,语法有差异,注意区分。另外,把当前用户加进 docker 组可以免 sudo 执行 docker 命令,但基于安全考虑,生产环境我不太建议这么做,老老实实用 sudo 更稳妥。

2.3 docker-compose.yml 的关键设计

我用的 compose 文件长这样,注释写的是我踩过坑之后总结的原因:

services: gitlab: image: gitlab/gitlab-ce:15.11.13-ce.0 container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url 'http://gitlab.example.com' gitlab_rails['gitlab_shell_ssh_port'] = 2222 ports: - "80:80" - "2222:22" volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/data:/var/opt/gitlab shm_size: 256m

几个关键点拆开讲:

  • 镜像 tag 为什么要写死版本:我特意用了和旧环境一致的15.11.13-ce.0,这是为了保证备份恢复时的版本兼容。如果你图省事直接写latest,后面恢复备份大概率会撞上版本 mismatch 的报错。
  • external_url 要和旧环境保持一致:如果域名不变,就继续用旧域名。这个值影响所有 webhook 回调、克隆地址、OAuth 回调地址,一旦对不上,表面上能登录,但各种联动功能全乱。
  • SSH 端口映射到 2222:容器内部 GitLab SSH 监听的是 22 端口,但宿主机的 22 被系统 SSH 占了,所以我映射成了2222:22。同时必须在gitlab_shell_ssh_port里告诉 GitLab“对外是 2222”,这样 UI 显示的克隆地址才会自动带上端口。
  • 三个 volume 的职责config存配置和密钥、logs存日志、data存仓库和数据库。备份、迁移时主要针对data,而密钥在config里。
  • shm_size: 256m:GitLab 内部组件(尤其是 Prometheus 和 PostgreSQL)对共享内存有要求,默认 64M 在流量上来后容易出奇怪问题,比如数据库无响应。这个参数不加,后期运维会非常头疼。

文件写好后直接启动:

docker compose up -d docker compose logs -f

第一次启动要拉镜像、初始化数据库,日志会一直刷,等到看到"Congratulations! GitLab is running"或者"gitlab is ready"之类的输出,再用浏览器访问。

3. 数据零丢失的核心链路:备份、校验、传输、恢复

3.1 版本对齐是第一原则

GitLab 的备份文件里记录了生成它的版本号。恢复时,目标实例的版本需要与备份版本相同或相近(patch 版本差异通常可以接受,但我不建议赌)。跨大版本直接恢复时报错的典型信息是:GitLab version mismatch: You are trying to restore a backup of GitLab 15.11.13 to a GitLab 17.x.x

我当时在旧环境先查了版本:

cat /opt/gitlab/version-manifest.txt | grep gitlab-ce

拿到15.11.13,新环境的镜像 tag 就直接写15.11.13-ce.0。等恢复验证通过后,再从 15 升 16、16 升 17,每一步升级前都做备份。这条路径虽然慢,但最安全。

3.2 旧环境全量备份:一个 tar 包不代表全部

备份前先确认磁盘空间充足,备份产物通常是仓库、数据库 dump 的集合体,几个 GB 很常见:

df -h /var/opt/gitlab sudo gitlab-rake gitlab:backup:create

执行完,备份文件出现在/var/opt/gitlab/backups/,名字类似1700000000_2024_11_17_15.11.13_gitlab_backup.tar。这个 tar 包里包含数据库、仓库 bundle、上传附件等核心数据。

但这里有个绝大多数教程不会提醒你的点:备份 tar 包不包含gitlab-secrets.json。这个密钥文件掌管着用户 2FA、webhook 加密、CI/CD 变量加密、Runner 通信密钥。恢复后如果没带过来,用户全部要重新绑定 2FA,webhook 和 CI 变量基本全废。所以一定要单独备份配置和密钥:

sudo tar czf gitlab-config-backup.tar.gz /etc/gitlab/gitlab.rb /etc/gitlab/gitlab-secrets.json

把这两个文件打包带走,迁移才谈得上完整。

3.3 传输与哈希校验

备份文件弄好后,我用 rsync 传到新服务器的备份目录:

rsync -avzP gitlab-config-backup.tar.gz 1700000000_2024_11_17_15.11.13_gitlab_backup.tar user@new-server:/srv/gitlab/data/backups/

传输完成不是万事大吉,一定要做哈希校验。tar 包动辄几个 GB,网络传输抖动、磁盘坏道都可能让文件损坏,等到 restore 时报错再排查就晚了。我在旧机器和新机器上分别算:

sha256sum 1700000000_2024_11_17_15.11.13_gitlab_backup.tar

两边哈希一致,才开始动恢复的脑筋。这一步别省,真的是血的教训。

3.4 在 Docker Compose 里执行恢复

恢复的核心思路是让备份文件出现在容器内的/var/opt/gitlab/backups/目录。因为我们把宿主机/srv/gitlab/data映射成了容器的/var/opt/gitlab,所以刚才传到/srv/gitlab/data/backups/的文件,容器内已经能看到。

先修正文件属主,再进入容器:

sudo chown -R git:git /srv/gitlab/data/backups/ sudo docker compose exec gitlab bash

进入容器后,停掉可能写数据库的服务,避免恢复过程中发生数据竞争:

gitlab-ctl stop puma gitlab-ctl stop sidekiq

然后执行恢复。注意BACKUP=后面的参数不带_gitlab_backup.tar后缀:

gitlab-backup restore BACKUP=1700000000_2024_11_17_15.11.13

如果你的 GitLab 版本比较老,命令可能还是gitlab-rake gitlab:backup:restore BACKUP=...,实际以官方文档为准。执行过程中会提示确认,输入yes继续。恢复完成后:

gitlab-ctl reconfigure gitlab-ctl restart

到这里数据库和仓库已经回来了,但还差最后一步:把前面备份的gitlab-secrets.json复制到新环境的/etc/gitlab/目录(也就是宿主机/srv/gitlab/config/gitlab-secrets.json)。这一步最好在容器停止状态下操作,复制完再启动容器,确保容器不会用新生成的密钥文件覆盖掉旧密钥。然后浏览器访问,用管理员账号登录验证,密码还是老的,因为用户数据已经从备份里恢复了。

4. 切换与数据校验:旧机器先别急着删

4.1 DNS 与访问入口切换

如果域名没变,external_url 也保持一致,那么业务侧几乎无感,用户不需要改任何地址。但服务器 IP 变了,DNS 的 A 记录必须更新。我当时的做法是:切换前 24 小时把 DNS TTL 从默认值调低到 300 秒,这样真正切换时全球生效时间能控制在几分钟内。

如果只有一部分需要马上验证的人可以先测,就在自己电脑上改/etc/hosts,把域名提前指到新服务器 IP,完整走一遍浏览器登录、克隆、推送、触发 CI 的流程,确认没问题再切 DNS。

一个容易忽略的细节:如果迁移后域名或端口有变化,用户本地已有的 remote 地址全都要改,得提前通知团队并给一份命令:

git remote set-url origin git@gitlab.example.com:group/project.git

4.2 数据核对清单:用数字说话

“数据零丢失”不是嘴上说说,要用数据验证。我整理了一张核对清单,照着逐项打勾:

核对项方法预期结果
项目仓库总数管理员后台统计页与迁移前记录一致
用户总数管理员后台用户列表与迁移前记录一致
组与子组结构管理员后台组列表层级结构完整
重点仓库最新 commitgit ls-remote抽查哈希与迁移前一致
用户 SSH Key抽查几个用户后台完整保留
2FA 登录实测扫码登录可正常通过
Webhook 回调触发一次测试事件回调正常到达
CI/CD 变量检查变量值可读没有解密报错
Runner 状态管理员后台重新注册后在线

仓库层面的抽查我用的方式很直接:

git ls-remote git@gitlab.example.com:ops/backup-scripts.git HEAD

输出的 commit 哈希和迁移前存档的截图一对比,是不是一致一目了然。

4.3 旧服务器降级与退役

数据核对没问题,不代表第二天就能把旧机器关机。我建议至少保留两周观察期,这两周内旧机器保持只读状态——千万不要再往旧环境里推代码或跑 CI,否则新旧两个环境的仓库会产生分叉,后面的合并就是一场灾难。

观察期内如果发现新环境有解决不了的问题,还能把 DNS 切回旧 IP,回滚成本极低。两周后确认稳定,再做一次旧环境完整备份留档,然后才把旧机器关机退役。

5. 故障排查实录:迁移后最常见的四个报错

5.1 login failed. check api token or gitlab version

这个报错我迁移后的第一周收到了三次,全是用了 GitKraken、VS Code Git 插件这类 GUI 客户端的同事反馈的。报错信息原文是login failed. check api token or gitlab version. log in via git if the version...,字面意思很容易让人去怀疑 token 或者版本填错,排查链路按顺序来:

  1. 先确认客户端里填的 GitLab 地址是不是还指向旧 IP/旧域名。迁移后地址变了,客户端里保存的还是旧地址居然不会主动报连接超时,而是绕到 API 校验这一层才炸,误导性很强。
  2. 再重新生成一个 Personal Access Token。迁移后如果 external_url 变了,旧 token 的绑定关系可能已经失效。让用户在浏览器登录后进入 User Settings → Access Tokens,新建一个有api权限的 token,重新填进客户端。
  3. 最后检查客户端里的 GitLab 版本配置。部分 GUI 客户端允许手动指定 GitLab 版本号用于 API 兼容判断,填得和实际版本跨度太大,也会报这个错。改成和实际接近的版本或者选自动检测。

这里也顺便回答很多人问的“gitlab token 在哪里”:登录后左下角头像 → User Settings → Access Tokens,token 只显示一次,生成后必须立刻复制保存。

5.2 GitLab 422 登录报错

跨环境迁移后,422 是另一个高频报错。典型症状是:浏览器输入账号密码点登录直接跳 422,但是开一个隐身模式窗口却可以正常登录。出现这种情况的第一反应应该是清理浏览器 cookie——老域名下的加密 cookie 还在本地,Rails 做 CSRF 校验时发现来源不一致,直接拒绝。

第二个常见原因是external_url和实际访问地址不一致。比如 external_url 配的是https://gitlab.example.com,但新环境还没挂证书,有人用http://访问,协议对不上就会 422。迁移后第一时间用浏览器地址栏的完整 URL 和 external_url 逐字符比对。

如果你前面挂了一层反向代理(Nginx、Caddy 之类),还要注意在GITLAB_OMNIBUS_CONFIG里加:

nginx['trusted_proxies'] = ['你的代理IP网段']

不配这个,GitLab 拿到的用户来源 IP 全是代理的地址,在某些场景下也会触发安全校验问题,表现就是各种诡异的 422 和登录态丢失。

5.3 SSH 拉取代码失败:Docker 端口映射的坑

迁移到 Docker 版之后,“克隆代码连不上”的反馈是最多的。症状要么是Permission denied (publickey),要么是Connection refused,但实际上密钥和账号都没问题。真正的坑在于 Docker 端口映射。

默认情况下,GitLab 在 UI 里显示git@gitlab.example.com:group/project.git,但容器内 SSH 监听 22,对外映射的却是 2222。如果你没在 compose 里设置gitlab_rails['gitlab_shell_ssh_port'] = 2222,UI 克隆地址不会带端口,用户拿着这个地址去 clone,自然连 22 端口,而宿主机的 22 是系统 SSH 在监听,连上去身份对不上,就报Permission denied

验证方法很简单:

ssh -T git@gitlab.example.com -p 2222

能出现Welcome to GitLab, @username!就说明 SSH 通了。用户侧的长期解决办法是在~/.ssh/config里写一段:

Host gitlab.example.com HostName gitlab.example.com Port 2222 User git IdentityFile ~/.ssh/id_ed25519

这样git clone git@gitlab.example.com:group/project.git就不用手动加-p 2222了。另外提醒一句,~/.ssh目录权限必须是 700,authorized_keys权限是 600,权限过宽 SSH 也会拒绝。

5.4 Runner 失联与 CI/CD 配置迁移

迁完之后去管理员后台一看,Runner 全灰着,这是因为 Runner 注册时绑定的还是旧实例的 URL,迁移后自然失联。解决方式就是重新注册。

到项目或组设置里的 CI/CD → Runners 页面,拿到新的注册 token,然后在 Runner 机器上执行:

sudo gitlab-runner register

按提示输入新实例地址、注册 token、执行器类型。如果是 Docker executor,还要确认 runner 机器能访问到新 GitLab 地址,网络策略别漏放。

还有个隐蔽问题:如果前面提到的gitlab-secrets.json没有正确恢复,CI/CD 变量的加密密钥对不上,界面上能看到变量名但读不出值,流水线里用到这些变量的 job 会全部失败。遇到这种情况先检查 secrets 文件,而不是急着重建变量——重建等于把密码类变量全部手动导一遍,费时费力还容易漏。

6. 迁移后的日常运维:脚本、升级、监控

6.1 一个够用的备份脚本

迁移只是开始,日常备份不能断。我在新环境里写了个简单脚本,放在/usr/local/bin/gitlab-backup.sh

#!/bin/bash BACKUP_BASE=/srv/gitlab/data/backups /usr/bin/docker compose -f /srv/gitlab/docker-compose.yml exec -T gitlab gitlab-backup create find $BACKUP_BASE -name "*_gitlab_backup.tar" -mtime +14 -delete tar czf $BACKUP_BASE/gitlab-config-$(date +%F).tar.gz /srv/gitlab/config/gitlab.rb /srv/gitlab/config/gitlab-secrets.json find $BACKUP_BASE -name "gitlab-config-*.tar.gz" -mtime +14 -delete

配合 crontab 每周执行一次:

0 2 * * 0 /usr/local/bin/gitlab-backup.sh

脚本里保留最近 14 天的备份,配置和密钥每个星期单独打一个包。定期恢复演练比备份本身更重要,建议每季度找一台空闲机器,把备份恢复一遍并跑几个关键仓库的git fsck,确认备份真的能救命。

6.2 升级 GitLab 的正确姿势

Docker Compose 部署的 GitLab,升级流程其实很清爽:先备份,再改镜像 tag,然后docker compose pull && docker compose up -d

但有一条红线必须守住——跨大版本升级必须走官方升级路径。比如从 15.x 升到 17.x,正确路径是 15 → 16 → 17,每一步都是一个稳定大版本,每一步之间都做一次备份。千万别图省事直接从 15 跳到 17,GitLab 内部数据库结构在大版本之间变化很大,跳级升级轻则报错,重则数据损坏。升级完立刻去管理员后台确认版本号,再跑一次gitlab-rake gitlab:check验证系统完整性。

6.3 磁盘和日志:Docker 部署的隐形杀手

Docker 部署 GitLab 最容易被忽视的是容器日志无限增长。默认情况下,容器的 JSON 日志会一直写,配上 GitLab 本身的日志 volume,硬盘很快会被吃掉。我给 compose 文件加了日志轮转限制:

logging: driver: "json-file" options: max-size: "50m" max-file: "3"

另外,/srv/gitlab/logs目录里的应用日志也需要关注,特别是 PostgreSQL 和 Git 相关的日志。运维侧我每周固定看一眼df -hdu -sh /srv/gitlab/*,确认数据目录增长在预期范围内。


最后再分享一个实际体会:迁移这种事,真正考验人的不是技术命令,而是流程是否克制。版本对齐、备份校验、切换观察、回滚预案,这些步骤看着啰嗦,但它们才是“数据零丢失”的真正保障。我这次迁移之所以顺利,很大程度是因为提前把所有核对数字都存档了,每一步都验证完再走下一步。如果你也准备做类似迁移,别被“Docker 很轻松”这种说法带偏,把备份和校验当成第一优先级,稳一点,慢一点,反而是最快的路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 19:46:10

CMSIS-5五层架构解析:嵌入式系统硬件抽象协议栈实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 19:45:24

商城网站开发公司推荐:从零了解凡科商城

很多商家在搜“商城网站开发公司推荐”的时候,其实心里并没有一个明确的标准。面对市面上五花八门的报价和功能清单,往往越看越糊涂。这篇文章不急着推荐谁,先把商城开发的基础知识讲清楚,再以凡科商城为例,说说SaaS模…

作者头像 李华
网站建设 2026/9/11 19:43:52

MySQL日期字符串转换全攻略:STR_TO_DATE函数深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 19:37:56

rocketmq Connect/EventBridge原理源码分析I-架构,服务和组件

1 简介 RocketMQ Connect是RocketMQ数据集成重要组件,可将各种系统中的数据通过高效,可靠,流的方式,流入流出到 RocketMQ,它是独立于 RocketMQ 的一个单独的分布式,可扩展,可容错系统&#xff…

作者头像 李华