2. GitLab架构与组件解析

在动手迁移或升级之前,必须先弄清楚一件事:你手里的 GitLab 到底是“怎么装上去的”。因为 Omnibus、源码编译、Helm Chart 三种部署形态,虽然对外都提供同一套 GitLab 功能,但它们的组件边界、配置入口、升级路径和数据落盘位置完全不同。迁移方案选错,往往不是功能不可用,而是升级时找不到配置文件、备份恢复对不上版本、或者 Gitaly 数据目录被容器卷覆盖。

三种部署形态的本质差异

Omnibus 是“打包式”部署。它把 GitLab Rails、Sidekiq、Gitaly、Nginx、Redis、PostgreSQL 等组件连同各自依赖,统一打包进一个安装包,由 gitlab-ctl 统一管理。所有配置集中在 /etc/gitlab/gitlab.rb,执行 gitlab-ctl reconfigure 后由 Chef 生成各组件真实配置。它的最大优势是组件版本被锁定、升级路径清晰,企业内网离线环境也能用 RPM/DEB 包完成升级。缺点是所有服务挤在同一台机器上,单机资源竞争明显。

源码部署是“自组装”部署。Rails 应用、Gitaly、Sidekiq 各自独立安装,数据库和 Redis 通常外置。配置分散在 gitlab.yml、database.yml、resque.yml、gitaly/config.toml 等多个文件里。它的灵活性最高,可以按组件横向扩展,但升级时必须逐个组件处理:先升 Gitaly,再升 Rails,最后升 Sidekiq,顺序错了会出现 API 不兼容。迁移时最容易踩的坑是“只迁了代码目录,忘了迁 Gitaly 的仓库存储路径”。

Helm Chart 是“云原生编排”部署。GitLab 被拆成多个 Kubernetes 工作负载:webservice、sidekiq、gitaly、praefect、redis、postgresql 各自是独立 Deployment 或 StatefulSet。配置不再写在一个文件里,而是通过 values.yaml 加外部 Secret 注入。升级通过 helm upgrade 滚动完成,但数据库迁移由独立的 migration Job 执行,必须确认 Job 成功后才能继续。它的数据持久化依赖 PVC,迁移时真正要搬的是 PVC 背后的存储卷,而不是容器本身。

三种形态的核心判断依据:
• 配置文件入口:Omnibus 看 gitlab.rb;源码看多个 yml/toml;Helm 看 values.yaml + Secret。
• 数据落盘:Omnibus 默认在 /var/opt/gitlab;源码由各组件配置决定;Helm 在 PVC 中。
• 升级控制点:Omnibus 用 gitlab-ctl upgrade;源码需手动按序升级;Helm 用 helm upgrade + migration Job。

关键服务与数据流

无论哪种部署形态,GitLab 的核心数据流是一致的:用户通过 Nginx/Ingress 进入 Rails(Puma),Rails 把耗时任务丢给 Sidekiq,把 Git 操作交给 Gitaly,把元数据写入 PostgreSQL,把缓存和会话放进 Redis。理解这条链路,才能判断迁移时哪些数据必须一致、哪些可以重建。

Gitaly 是 Git 仓库的实际管理者。所有 git clone、git push、仓库检索都通过 Gitaly 完成。它的数据目录(默认 /var/opt/gitlab/git-data/repositories)是迁移中体积最大、最不能出错的部分。Gitaly 本身无状态逻辑,但仓库数据有状态,迁移时必须保证文件权限和属主一致,否则 Rails 能连上 Gitaly,却读不出仓库。

Praefect 是 Gitaly 的高可用路由层。当启用 Gitaly Cluster 时,Praefect 位于 Rails 和多个 Gitaly 节点之间,负责把写操作同步到多个副本,并在某个 Gitaly 故障时切换读取源。迁移时如果启用了 Praefect,不能只迁单个 Gitaly 节点,必须保证 Praefect 的数据库(记录副本状态)和所有 Gitaly 副本一起迁移,否则会出现“仓库存在但副本状态不一致”。

Redis 承担缓存、会话、Sidekiq 队列和分布式锁。它的大部分数据可以重建,但 Sidekiq 队列中的待处理任务如果丢失,会导致部分后台作业不再执行。迁移时建议在切换前暂停 Sidekiq,等待队列排空,或接受少量任务重试。

PostgreSQL 是唯一不能丢的元数据存储:用户、项目、权限、Issue、MR、CI 流水线记录都在这里。迁移时必须使用与源版本匹配的 pg_dump/pg_restore 或物理备份,并确保目标 GitLab 版本不低于源版本,否则数据库迁移脚本无法执行。

Sidekiq 是异步任务执行器,处理邮件、CI 调度、仓库镜像同步等。它本身无持久数据,但依赖 Redis 队列和 PostgreSQL 中的任务记录。迁移后如果 Sidekiq 未启动,用户会看到“操作成功但邮件没发、流水线不触发”的现象。

GitLab 核心组件与数据流(以 Omnibus 单机为例) 用户 / CI Runner HTTPS / SSH Nginx / Ingress 反向代理 + TLS GitLab Rails (Puma) 业务逻辑 / API / Web PostgreSQL 用户/项目/权限/MR CI 流水线元数据 Redis 缓存 / 会话 / 队列 分布式锁 Sidekiq 异步任务 / 邮件 CI 调度 / 镜像同步 Gitaly Git 仓库读写 repositories 存储 SQL 缓存/队列 任务投递 Git 操作 Praefect(可选) Gitaly 路由 / 副本同步 启用 Gitaly Cluster 时 Gitaly-1 主副本 Gitaly-2 从副本 … 迁移关注点: PostgreSQL 必须版本匹配迁移;Gitaly 仓库目录权限属主一致; Redis 队列建议排空;Praefect 启用时需整体迁移副本状态。

迁移前必须确认的组件清单

在实际迁移前,建议先执行一次组件盘点,确认每个服务的真实位置和版本。以下命令适用于 Omnibus 部署,源码和 Helm 部署需要对应调整查询方式:

# 查看 GitLab 版本与各组件版本
gitlab-rake gitlab:env:info

# 查看 Gitaly 仓库存储路径
gitlab-ctl status gitaly
grep -E "dir|path" /var/opt/gitlab/gitaly/config.toml

# 查看 PostgreSQL 数据目录与版本
gitlab-psql -c "SELECT version();"

# 查看 Redis 监听与持久化配置
gitlab-redis-cli info persistence

# 查看 Sidekiq 队列积压情况
gitlab-rake sidekiq:queue:size

这些输出决定了迁移时的数据搬运范围。例如,如果 gitaly/config.toml 中的 dir 指向了非默认路径,那么备份脚本和迁移脚本都必须同步修改,否则新环境启动后 Gitaly 会在默认路径下创建空仓库目录,用户看到的是“项目还在,但代码全没了”。

Helm Chart 部署下,不要直接 kubectl cp 拷贝 Gitaly Pod 内的仓库目录。正确做法是备份 PVC 对应的底层存储卷,或在 Chart 中配置对象存储作为仓库后端。直接拷贝容器文件系统会丢失文件属主和部分扩展属性,导致 Gitaly 校验失败。

数据流视角下的迁移顺序

从数据流依赖关系看,迁移应遵循“先元数据、后仓库、最后队列”的顺序。PostgreSQL 是其他组件的引用基础,先恢复数据库,Rails 才能知道有哪些项目、哪些用户;Gitaly 仓库数据恢复后,Rails 才能通过数据库中的 project_repositories 记录找到对应仓库路径;Redis 和 Sidekiq 最后启动,避免在数据未就绪时消费任务产生错误。

对于启用了 Praefect 的环境,还需要额外确认 Praefect 自身的数据库(通常与 GitLab 主库分离)是否已迁移,以及各 Gitaly 副本的 storage 名称是否与 Praefect 配置中的虚拟存储名一致。名称不一致时,Praefect 会认为副本不可用,表现为仓库只读或推送失败。