
运维最怕听到的一句话是“这台Gitlab服务器是从你入职之前就在跑的。”我这次接手的就是这么一台系统信息里赫然写着Gitlab 10.0.0社区版跑在一台CentOS 7机器上距今已经有六七年历史。版本旧不是原罪问题在于新需求扛不动安全扫描报告一页一页全是高危漏洞CI/CD新版功能也用不上领导一句话升到17.x。刚开始我也想过直接装一台新服务器然后把项目迁过去不就行了但服务器上有几十个用户、几百个Project、一堆CI变量、Webhook还有各种细碎权限配置重装的成本比想象中高太多而且迁移过程容易丢东西。最后我选择了在现有服务器上一步一步升级从10.0.0一路升到17.x全程历时两个维护窗口中间被数据库迁移和Gitaly折腾得不轻。这篇就记录这次升级的全过程包括升级路线设计、备份、每一条命令、配置改动、踩坑记录和最后的验证清单。如果你也有一台老Gitlab要升照着我这个流程走至少不会在半夜盯着红的看不清的控制台发呆。1. 不按路径升级的下场升级规则先搞明白先说结论Gitlab 10.0.0不能直接装17.x的包覆盖升级。这一点很多人不理解觉得Nginx、MySQL升级高版本直接覆盖就行但Gitlab这种数据状态复杂、又高度自管理的应用跨大版本有严格限制。我在动手前查了不少文档最终确定了一条合规路径也理解了为什么官方不允许跳级。Gitlab的数据库migration脚本是线性累积的。从10.0.0到17.x之间有几百个迁移脚本跳级升级时数据库schema和代码版本不匹配会出现各种诡异错误登录页500、Sidekiq进程死循环、仓库页面白屏、MR打不开。逐级升级的意义在于每一个大版本的migration都能完整跑一遍后台任务逐步清理数据格式平稳过渡。官方升级路径里还有一个关键规则跨大版本之前必须先把当前大版本升级到最后一个patch版本。比如要离开10.x必须先升到10.8.7要离开11.x必须先升到11.11.8。当前版本处于最终schema状态下一个大版本的migration才能从正确基线开始执行。我这次走的完整路径如下步骤起始版本目标版本说明110.0.010.8.710.x收尾210.8.711.11.8进入11并收尾311.11.812.10.14进入12并收尾412.10.1413.12.15进入13并收尾513.12.1514.10.5进入14并收尾614.10.515.11.13进入15并收尾715.11.1316.0.10进入16816.0.1016.11.816.x收尾916.11.817.0.2进入171017.0.217.7.1最终目标版这条路径看着有10跳实际操作起来每跳的套路高度重复熟练之后一跳也就15到20分钟大头时间花在数据库迁移和前端资源编译上真正让时间不可控的是过程中的排错。如果你问能不能少几跳比如从13.12.15直接到15.11.13在官方升级路径里这种连续跨两个大版本的情况有专门说明有些确实被允许但我不建议在生产环境赌这个。一次只跨一个主版本出问题时定位范围最小回滚也最容易判断。反正维护窗口都申请了多两步无所谓的。2. 备份做不好后面全是泪升级前必做的四件事升级前准备工作我认真做了一下午事实证明每一项都值回票价。如果只做一件事那也是做备份如果时间允许下面四项都建议做完整。第一件事完整的备份包括数据库、仓库和配置文件。Gitlab官方备份命令非常简单在Omnibus环境下就一条sudo gitlab-rake gitlab:backup:create默认备份会打包到/var/opt/gitlab/backups/目录生成一个类似1699999999_2024_11_15_10.8.7_gitlab_backup.tar的文件。这里我特别提醒一点备份文件大小不等于可以高枕无忧。我见过有人备份完不看日志结果备份过程里因为磁盘满了直接报错tar包只写了一半。跑完备份命令后一定要去确认tar包存在而且体积和物理仓库总大小在一个量级。比如项目仓库总共20GB备份出来只有200MB那肯定有问题。第二件事单独备份 /etc/gitlab 目录。这是很多人会漏掉的关键一步。gitlab.rb和gitlab-secrets.json在这个目录里gitlab-secrets.json保存了数据库加密密钥、OTP密钥、CI/CD密钥等敏感信息。一旦丢失即使你有数据库备份升级后所有密码、双因素认证、Webhook密钥都是不可逆的损坏状态。我的做法是sudo cp -r /etc/gitlab /etc/gitlab.bak.20241115 sudo tar zcf gitlab-etc-20241115.tar.gz /etc/gitlab注意gitlab.rb在升级过程中通常不会被自动改写但 Gitlab 官方文档明确要求备份这份文件因为在排错时需要对比新旧配置差异。第三件事确认磁盘空间足够。升级需要的空间远比想象中多。Omnibus安装包本身大约600MB到1GB数据库迁移过程中会产生大量临时表和中继数据前端资源编译也要占用额外空间。再加上备份文件我建议/opt、/var和/var/opt/gitlab所在分区至少保留20GB以上空余空间。检查命令df -h如果不够升级中报 “No space left on device” 就很被动了。我在升级前清了yum缓存、journal日志和旧的备份文件腾出了空间这一步不要省。第四件事评估内存和CPU。Gitlab 10.0.0时代官方推荐的最低配置是2GB内存那仅限于单人开发使用。10.x升到17.x这个跨度数据库迁移阶段的内存峰值比平时高很多Web服务和Sidekiq重启时还要同时开多个进程。实测下来4GB内存的机器在跑第6跳到第8跳的时候系统负载经常冲到接近CPU核数Free内存掉到100MB以内。如果你手里是2GB内存的小机器升级前建议先扩容到4GB以上否则OOM Killer会在migration跑到一半的时候把postgresql进程杀掉那种状态特别难恢复。SWAP至少保证有2GB虽然慢但关键时刻能救命。如果你用的是Docker部署备份姿势会略有不同但原理一致docker exec -t gitlab gitlab-rake gitlab:backup:create # 备份保存在容器内的 /var/opt/gitlab/backups把容器整个停掉后做volume快照更稳妥3. 逐步升级实操记录Omnibus每一跳该敲什么命令我是Omnibus安装方式下面以rpm为例apt命令结构基本相同。每一跳的标准动作可以总结成一串命令这里把我的执行流程整理出来。3.1 第一跳10.0.0到10.8.7第一跳是最紧张的因为老版本的工具链和系统版本都旧。CentOS 7上先下载10.8.7的rpm包然后执行# 建议先进入维护状态防止用户在操作期间提交数据 sudo gitlab-ctl stop unicorn sudo gitlab-ctl stop sidekiq sudo gitlab-ctl stop nginx # 确认服务状态 sudo gitlab-ctl status # 安装新版包这里以rpm包名为例 sudo rpm -Uvh gitlab-ce-10.8.7-ce.0.el7.x86_64.rpm安装脚本会自动运行gitlab-ctl reconfigure并触发数据库迁移。这一步的输出会很长最后几行会提示gitlab Reconfigured!之类的信息。装完后不要急着走人先做三件事# 1. 确认服务全部起来 sudo gitlab-ctl status # 2. 查看数据库迁移状态确保没有down的迁移 sudo gitlab-rake db:migrate:status | grep -i down # 3. 简单验证Web页面正常 curl -I -k https://127.0.0.1/users/sign_indb:migrate:status | grep down正常返回为空页面返回200这一跳才算结束。不要在这一步偷懒后续每一跳都要重复这个验证动作。3.2 中间几跳的关键差异从10.8.7到11.11.8再到12.10.14这几次的操作套路几乎一样下载对应版本的包rpm -Uvh等待reconfigure和migration完成跑验证三件套。区别在于以下几点从12.x开始unicorn已经不是唯一选项但停服务命令里unicorn还能用。到了14.x、15.x时代新生效的Web服务是Puma停服务命令要改成sudo gitlab-ctl stop puma。从11.x开始Gitlab开始更多依赖Gitaly安装包自带gitaly进程服务列表里会多出这个进程。它是仓库读写的新通道升级后如果仓库页面500大概率是Gitaly有问题。升级到13版本以后安装包自带的系统依赖发生了变化比如OpenSSL、Ruby版本都更高了。如果系统里缺少某些共享库安装时rpm会提示依赖缺失需要先用yum补依赖。我的建议是每一跳之间隔30到60分钟观察Sidekiq队列是否被消费完。Sidekiq是Gitlab后台任务处理器升级后旧版本遗留的Background Job会积压在新版本队列里如果一直堆积说明新老版本数据格式不兼容这时候继续下一跳会把问题放大。观察命令sudo gitlab-rails runner puts Sidekiq::Queue.new.size数值持续降低并在几分钟内归零是比较理想的下一跳时机。如果只增不减先查/var/log/gitlab/gitlab-rails/production.log和/var/log/gitlab/sidekiq/current不要盲目继续。3.3 PostgreSQL升级时机这是整个升级过程里最容易被忽视、也最容易翻车的环节。Gitlab 10.0.0默认自带PostgreSQL 9.6而Gitlab 17.x要求PostgreSQL 14以上。Omnibus安装包在升级到某一个阶段时会提示当前数据目录版本太旧。我在从13.x升到14.x之后遇到了Gitlab页面直接打不开、日志里报数据库连接错误的情况排查下来是因为PostgreSQL主版本还停留在旧版本。处理方式是手动升级数据库sudo gitlab-ctl pg-upgrade这个命令会把老版本数据目录里的内容迁移到新版本数据目录整个过程比较耗时取决于仓库数据量的大小我这次跑了大约40分钟。过程中不要重启机器不要手动停postgresql。迁移完成后命令会自动把旧的data目录改名标记为data.old。确认Gitlab页面正常之后再把老目录删掉释放空间。这里有个实用经验与其等页面报错再处理不如在每个大版本升级完成后主动检查一次PostgreSQL版本sudo gitlab-ctl status | grep postgresql # 或者查看数据目录版本 sudo cat /var/opt/gitlab/postgresql/data/PG_VERSION如果发现数据目录版本和当前Gitlab期望的版本不一致直接执行sudo gitlab-ctl pg-upgrade。3.4 Docker部署的升级变体如果你用的是docker镜像部署升级套路是换image版本。比如docker stop gitlab docker rm gitlab docker run --detach --name gitlab \ --publish 443:443 \ --publish 80:80 \ --publish 2222:22 \ --volume $GITLAB_HOME/config:/etc/gitlab \ --volume $GITLAB_HOME/logs:/var/log/gitlab \ --volume $GITLAB_HOME/data:/var/opt/gitlab \ gitlab/gitlab-ce:10.8.7-ce.0每次改最后一个image tag即可比如从10.8.7-ce.0换成11.11.8-ce.0。容器启动时会自动执行reconfigure和migration。用Docker方式升级最需要注意的是不要把整个$GITLAB_HOME在不同大版本之间共用太久Gitlab官方曾经针对容器升级给过建议在跨大版本时建议把数据目录用命名volume挂载避免因为镜像内部目录变化导致数据丢失或权限错乱。实际经验是volume方式跨版本升级基本可行但要确保每次升级前都对config和data目录做完整快照。4. 跨了七个大版本这些配置和组件必须跟着改升级过程中软件包自身会完成大部分组件替换但如果你之前对gitlab.rb做过定制有些参数就需要跟着新版本改否则轻则告警日志不断重则服务起不来。4.1 从Unicorn到PumaWeb服务器变迁Gitlab 10.x到12.x使用的Web Server是Unicorn从13.x开始引入Puma到15.0默认值切到Puma。如果你的gitlab.rb里没有自定义过unicorn[port]、unicorn[worker_processes]这些配置项升级基本无感。但如果自定义过到Gitlab 16和17之后unicorn相关配置项会失效或直接报warning。需要手动把这些配置迁移到Puma块# 旧配置10.x时代 unicorn[worker_processes] 4 unicorn[port] 8080 # 新配置15.0之后的写法 puma[worker_processes] 4 puma[port] 8080改完执行sudo gitlab-ctl reconfigure4.2 Gitaly拆分与仓库存储10.x时代仓库存储路径通常直接配置在gitlab-shell或者Gitlab Rails侧比如git_data_dir /var/opt/gitlab/git-data。从12.0开始Gitaly成为仓库访问的默认通道配置方式变成gitaly[storage] { default { path /var/opt/gitlab/git-data } }Omnibus在升级过程中一般会自动做迁移把旧的git_data_dir转成Gitaly的storage配置。但如果你在旧版本里配置过多个仓库存储路径升级后Gitaly可能只能识别其中一个导致部分仓库在Web端显示异常。升级前把gitlab.rb里跟git_data_dir、gitlab_shell[repos_path]相关的配置记录下来升级后对照Gitaly storage配置逐项检查。4.3 其他配置项的变化还有几个常见的配置项在这7个大版本里有变化配置项10.x写法17.x写法说明通过HTTP克隆端口gitlab_rails[gitlab_port] 8080external_url http://gitlab.example.com:8080新版推荐直接统一external_urlSSH用户user[username] gituser[username] git基本无变化双因素强制gitlab_rails[two_factor_authentication]gitlab_rails[two_factor_authentication][enabled] true参数结构从平铺变成了嵌套禁用注册gitlab_rails[gitlab_signup_enabled] falsegitlab_rails[gitlab_signup_enabled] false无变化如果你不确定某些旧参数在新版里是否还有效升级后看gitlab.rb同目录下的gitlab.rb.template 里面有详细注释或者直接看reconfigure输出里的warning。我在第8跳之后发现reconfigure输出里出现多条未知配置项警告逐条对照后发现都是unicorn老参数。4.4 API与安全相关的调整Gitlab 17.x默认启用了许多安全策略这些策略对老客户端和老脚本会造成影响。比如Git HTTP请求要求的最低TLS版本提高了如果用Nginx且只支持TLSv1.1升级后旧的CI客户端可能连不上。API v3接口早就被移除必须走v4。老版本Runner注册用的registration token机制在新版里逐渐废弃推荐改成project-level或group-level的authentication token。这些改动不是升级立即生效但会体现在后续使用中。如果你的团队有大量基于老API写的自动化脚本升级后要找个时间批量改一遍。5. 升级现场实录几次差点翻车整个升级过程不是一路顺畅这里把踩过的坑记录一下每个坑背后的排查思路比坑本身更有参考价值。5.1 Gitaly进程起不来仓库列表500跳到12.x之后有一次访问项目仓库页面直接白屏500nginx日志里报upstream连接失败。刚开始以为是Sidekiq卡了重启了sidekiq无效后来发现是Gitaly进程没有起来。查看Gitaly日志sudo gitlab-ctl tail gitaly日志里提示failed to load config路径指向一个不存在的socket文件。这是因为升级过程中reconfigure阶段没有正确生成Gitaly的socket路径。解决办法是清理旧的socket文件重新reconfiguresudo rm -rf /var/opt/gitlab/gitaly/*.sock sudo gitlab-ctl reconfigure sudo gitlab-ctl restart gitaly这个坑本身不复杂但很容易被面向上层服务的错误信息误导。遇到仓库页面报错优先直接看gitaly和gitlab-rails两个日志而不是先重启一堆服务。5.2 数据库迁移跑到一半卡死在第7跳升级到16版本时gitlab-ctl reconfigure过程中数据库migration卡在了一个点上大概持续了20分钟没有进展。当时第一反应是进程被OOM Killer杀了但dmesg里没有记录。后来发现是因为系统在migration过程中执行了很重的vacuum而磁盘IO已经饱和。排查步骤是# 查看当前数据库正在执行的查询 sudo gitlab-ctl pg_ctl status sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h /var/opt/gitlab/postgresql -d gitlabhq_production -c select pid, state, wait_event_type, wait_event, query from pg_stat_activity where state ! idle;查询结果显示wait_event是DataFileWrite说明就是磁盘IO慢不是死锁。我选择再等一会儿同时用iotop确认确实是磁盘写入密集。等了大约40分钟migration完成。这个过程的教训是数据库迁移卡住不要急着kill进程先确认是死锁还是IO慢盲目kill可能导致数据文件损坏。5.3 Sidekiq队列积压不消费第8跳升级到16.11后队列里出现了大量任务积压而且数量只增不减。我查了sidekiq进程进程在跑但队列不减。最后在sidekiq日志里看到了大量类似ActiveRecord::RecordNotUnique的报错原因是旧版本产生的重复异步任务数据跟新版本唯一索引冲突。处理方式比较粗暴但有效# 进入rails runner环境清理积压任务 sudo gitlab-rails runner Sidekiq::Queue.all.each { |q| q.clear }这里需要注意清理队列意味着旧任务不再执行如果队列里有没跑完的重要流水线任务可能会丢失CI状态。我当时的场景是旧版本遗留的webhook通知任务和后台统计任务不影响核心代码仓库和CI结果所以直接清掉了。如果你的环境里队列里积压的是高价值任务建议先升级到当前大版本最新版再把这个队列挂一天等它消费完而不是急着重启。这个坑也提醒我跨大版本升级前最好提前一天把老版本的Sidekiq队列清干净或者等它消费完。5.4 SSH推送时报错老密钥失效升到17.x之后有同事反馈SSH推送代码失败错误信息是Unable to negotiate with xxx.xxx.xxx.xxx port 2222: no matching key exchange method found. Their offer: diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1这是很典型的SSH算法兼容问题。Gitlab 17内置的OpenSSH版本只支持更强的密钥交换算法而同事本地的Git客户端或者系统SSH版本比较老还在用已经被标记为不安全的sha1系列算法。解决办法是升级客户端本地的ssh版本或者在客户端~/.ssh/config里临时允许老算法Host gitlab.example.com KexAlgorithms diffie-hellman-group14-sha1 HostKeyAlgorithms ssh-rsa这只是临时方案长期还是建议所有用户升级SSH客户端并换成ed25519密钥。这个问题在升级前往往被忽视因为老版本Gitlab一直容忍弱算法升级后一下子暴露出来。建议在升级通知里提前告诉团队“有可能需要升级本地Git和SSH客户端”能省不少工单。5.5 老Runner不兼容新版API升级之后CI Runner一直处于offline状态Runner日志里报WARNING: Authentication failed - verify token is correct这个是因为我用的Runner还是15.x版本的它用的注册token和心跳接口在17.x里因为token过期或者注册机制被弃用而不被接受。解决方法是先升级Runner到最新版本然后重新注册# 旧Runner停掉 sudo gitlab-runner unregister --name old-runner # 新版Runner使用authentication token注册 sudo gitlab-runner register \ --url https://gitlab.example.com \ --token glrt-xxxxxxxxxxxx \ --executor docker \ --docker-image alpine:latest \ --description new-runner在Gitlab 17.x中project/group的Runner注册页面会给一个glrt-开头的token用它替代旧的registration tokenRunner就能成功连接。这个坑并不难只是如果你提前没做Runner兼容性预案升级后CI会整体瘫痪。6. 升级后的体检清单确认没有带病运行版本号显示17.x还不算完成要确认系统真的健康才算把升级这趟车开到终点。我整理了一份三次体检的清单覆盖服务层、数据层和功能层。6.1 第一轮体检服务层快速检查升级完成后先跑一次最基础的服务体检sudo gitlab-ctl status所有进程都应该是run状态尤其是puma、sidekiq、postgresql、gitaly、nginx。如果有进程是down对着第五节的排错思路处理。这一轮体检5分钟内完成主要排除明显故障。接着跑Gitlab自带的检查工具sudo gitlab-rake gitlab:check SANITIZEtrue这个命令会检查Gitlab自检项比如数据库是否可连接、仓库目录是否可写、sidekiq是否正常、配置是否一致等。正常输出末尾会显示类似Checking GitLab ... Finished。如果有FAIL项要逐条看说明。常见的问题是elasticsearch索引失效或者仓库存储路径不对按提示处理即可。6.2 第二轮体检数据层和仓库功能实测服务层过了之后要真刀真枪地测试功能重点还是代码相关的核心链路。我按下面这个顺序逐项操作用管理员账号登录Web界面检查用户数、项目数、Group数是否和升级前一致。这一步能发现权限数据是否在migration中出了问题。随机打开几个仓库页面确认文件列表、提交历史、分支tab都能正常加载。如果出500优先看gitaly日志。实际clone一个仓库到本地做一次push操作确认SSH和HTTP两种协议都正常。如果团队常用SSH这步最重要。创建一个测试MR走一遍提交流程确认合并请求的diff和comments正常。进入CI/CD页面确认Runner已经在线手动触发一个简单pipeline跑通整个构建流程。这里记得确认pipeline页面能正常输出日志很多老版本升级后日志显示有兼容问题。数据层比较关键的一个命令是确认所有数据库迁移都处于up状态sudo gitlab-rake db:migrate:status | grep -i down如果发现还有down状态的迁移说明升级过程并不彻底需要手动执行sudo gitlab-rake db:migrate这通常不会发生但一旦发生处理完之后要继续观察是否产生新的down记录。6.3 第三轮体检后续几天的观察项升级完成当天测试全绿不代表万事大吉Gitlab有些后台任务是按天或按周触发的比如仓库统计、周期清理、邮件通知、备份调度等。建议升级后一周内每天看一眼这几个指标Sidekiq队列数量sudo gitlab-rails runner puts Sidekiq::Queue.new.size应该一直在低位。系统负载uptime14.x之后的Gitlab比老版本更吃内存和CPU如果负载长期超过CPU核数考虑加配置。/var/log/gitlab/gitlab-rails/production.log里是否有持续刷新的WARN或ERROR。磁盘空间升级后如果一直没有删除data.old目录或备份包磁盘会越来越紧张记得及时清理。还有一个容易被忽略的点升级到17.x之后建议顺手把以下安全设置过一遍等于把老版本欠下的安全债一起还掉开启MFA强制策略、关闭开放注册、检查SSH密钥的最小长度要求、清理长期未使用的账号。这么做不是因为升级本身引入了这些问题而是10.x时代的管理宽松在新版本安全基线面前太扎眼正好趁升级做一次治理。最后分享一个小技巧升级过程里我养成了一个习惯——每完成一跳就把gitlab.rb、gitlab-secrets.json和一个最新的备份tar包统一拷贝到一个单独的备份目录目录名按版本号命名。这样每一跳都有独立回滚点哪一步出问题至少能退到上一跳的稳定状态。这个习惯在这次长链路升级里帮了大忙有一次puma配置参数改错了直接回滚到上一个备份目录五分钟恢复了服务而不是花半小时去理解新版配置文件的全部变化。