Help us learn about your current experience with the documentation. Take the survey.

Ruby 升级指南

我们致力于使用最新的 Ruby MRI 版本运行 GitLab,以获取性能和安全更新以及新的 Ruby API。在升级 GitLab 的 Ruby 版本时,我们应确保:

  • 对贡献者的影响最小。
  • 优先保障 GitLab SaaS 的可用性。
  • 保持 GitLab 所有组件的 Ruby 版本一致性。

在修改 Ruby 版本前,请仔细通读本文档以全面了解可能需要的变更。每次 Ruby 升级可能各有不同,请评估文档中步骤的顺序和必要性。

Ruby 升级范围

升级 Ruby 时首先需要考虑的是范围。通常,我们需关注以下可能涉及 Ruby 更新的领域:

  • 主 GitLab Rails 仓库。
  • 任何辅助性 Ruby 系统仓库。
  • 这些仓库中系统使用的任何第三方库。
  • 这些仓库中系统使用的任何 GitLab 库。

我们不一定需要修改所有这些。例如,补丁级别的 Ruby 更新通常不需要更新第三方 gem。

补丁、次要和主要升级

评估范围时,Ruby 版本级别很重要。例如,将 GitLab 从 Ruby 2.x 升级到 3.x 比从 Ruby 2.7.2 升级到 2.7.4 更困难且风险更高,因为补丁版本通常仅限于安全修复或错误修复。 准备升级时请注意这一点,并相应规划。

为帮助您估算未来升级的工作量,请参考以下升级所需的工作:

受影响的受众和目标

任何升级前,请考虑所有受众和目标,按受 Ruby 升级影响的紧急程度排序:

  1. 开发者。 GitLab 及相关项目有许多公司内外贡献者。修改 .ruby-version 等文件会影响所有使用解释这些文件的工具的开发者。 开发者在从包含已合并更改的仓库拉取代码时即受影响。
  2. GitLab CI/CD。 我们严重依赖 CI/CD 进行代码集成和测试。CI/CD 作业不解释 .ruby-version 等文件。 相反,它们使用执行所在 Docker 容器中安装的 Ruby,该版本在 .gitlab-ci.yml 中定义。 这些作业使用的容器镜像维护在 gitlab-build-images 仓库中。 当我们合并镜像更新时,CI/CD 作业在镜像构建完成后即受影响。
  3. GitLab SaaS。 GitLab.com 使用定制的 Helm 图表部署,该图表使用来自 Cloud Native GitLab (CNG) 的 Docker 镜像。 与 CI/CD 类似,.ruby-version 在此环境中无意义。相反,必须修补这些 Docker 镜像以升级 Ruby。 GitLab SaaS 在下次部署时受影响。
  4. GitLab 自托管版。 通过 Omnibus 安装 GitLab 的客户不使用上述任何方式。 相反,其 Ruby 版本由 Omnibus 中 Ruby 软件包 定义。 GitLab 自托管版客户在升级到包含此更改的版本后即受影响。

Ruby 升级方法

正确规划 Ruby 升级中的每个步骤至关重要。一般准则如下:

  • 对于生产环境行为不太可能发生变化的较小升级,应尽量保持仓库与生产环境的版本差距最小。与利益相关者协调,在短时间内(一两天内)合并所有更改,避免版本差异。此场景下可能的顺序是:先升级开发者工具和环境,再升级生产环境。
  • 对于较大变更,使用新 Ruby 上生产环境风险显著。此时应先解决所有已知与新 Ruby 版本的不兼容问题,然后与生产工程师合作,将新 Ruby 部署到 GitLab 生产环境的一部分。此场景下可能的顺序是:先更新生产环境,再升级开发者工具和环境。这样在发生关键生产环境回归时更容易回滚。

无论哪种方式,根据过往经验,我们发现以下方法效果良好(部分步骤可能仅对次要和主要升级必要)。注意某些步骤可并行执行或顺序可按上述描述调整。

创建史诗

在史诗中跟踪此项工作有助于了解进度。对于较大升级,请在史诗描述中包含时间线,以便利益相关者了解最终切换的预计上线时间。 包含指定的性能测试模板,以确保升级满足性能标准。

将各仓库的变更拆分为此史诗下的独立问题。

传达升级意图

特别是对于引入或弃用功能的升级,应尽早传达升级计划,最好附带时间线。提供重要或显著变更的链接,以便开发者提前熟悉变更。

GitLab 团队成员应在相关 Slack 频道(至少 #backend#development)和工程周报(EWIR)中宣布意图。在您的沟通中包含升级史诗的链接。

将新 Ruby 添加到 CI/CD 和开发环境

要使用新 Ruby 构建 Ruby gem 和运行 GitLab Rails 应用,您必须首先准备 CI/CD 和开发者环境以包含新 Ruby 版本。 此时,您切勿将其设为默认 Ruby,而是使其成为可选版本。这允许在一段时间内同时支持旧版和新版 Ruby,实现更平滑的过渡。

需要修改两个地方:

  1. GitLab 构建镜像 这些是我们用于 Runner 和其他基于 Docker 的预生产环境的 Docker 镜像。所需变更取决于范围。
    • 对于补丁级别更新,只需增加 RUBY_VERSION 的补丁级别即可。 所有针对同一次要版本构建的项目将自动下载新的补丁版本。
    • 对于主要和次要更新,创建一套新的 Docker 镜像,可在升级过程中与现有镜像并行使用。重要:确保将 /patches 目录中的所有 Ruby 补丁文件复制到与您升级到的 Ruby 版本匹配的新文件夹中,否则补丁将不会应用。
  2. GitLab 开发工具包 (GDK) 更新 GDK 以添加新 Ruby 作为开发者可选择的附加选项。这通常只需将其追加到 .tool-versions,这样 asdf 用户即可受益。其他用户需手动安装 (示例)。

对于较大版本升级,建议与质量工程团队合作,识别并建立测试计划。

更新第三方 gem

补丁版本升级通常不需要此步骤,但对于次要和主要版本,gem 可能存在破坏性变更或 Bundler 依赖问题(当 gem 锁定特定 Ruby 版本时)。创建 gitlab-org/gitlab 的合并请求并观察哪些内容报错是很好的方法。

更新 GitLab gem 和相关系统

这通常是必要的,因为我们自己维护的 gem 或 Ruby 应用包含构建配置文件(如 .ruby-version.tool-versions.gitlab-ci.yml)。虽然 GitLab Rails 应用要与新 Ruby 配合工作并不总是需要更新这些仓库,但保持所有仓库的 Ruby 版本一致是良好实践。对于次要和主要升级,使用新 Ruby 为这些仓库添加新的 CI/CD 作业。 构建矩阵定义 可高效实现此操作。

决定更新哪些仓库

升级 Ruby 时,考虑更新 ruby/gems 中的仓库。以下是一些过往项目更新 Ruby 的合并请求示例:

要评估哪些仓库需要与主 GitLab 应用一起更新,请考虑:

  • Ruby 版本范围。
  • 服务或库在 GitLab 整体运行中的作用。

完整受影响仓库列表请参考GitLab 项目列表。 对于较小版本升级,可以延迟更新非核心库或我们确定主应用测试套件能在新 Ruby 版本下捕获回归问题的库。

咨询相关代码所有者,在更新 GitLab 应用之前合并这些更改是否可接受。最好先获得必要批准,但等到所有内容就绪后再合并更改。

准备 GitLab 应用合并请求

在依赖项更新完毕且新 gem 版本发布后,您可以像更新 gem 和相关系统一样,对主 Rails 应用进行必要的更改。 此外,更新文档以反映安装和更新说明中的版本变更(示例)。

特别注意规划此合并请求的时机,因为一旦合并,所有 GitLab 贡献者都将受影响且变更将部署。您必须确保此合并请求在所有其他内容就绪前保持开放状态,但尽早获取批准可缩短前置时间。

给开发者留出升级时间(宽限期)

在新 Ruby 可选且所有合并请求准备就绪或已合并后,应设置一个宽限期(至少1周),供开发者在本地安装新 Ruby。对于 GDK 和 asdf 用户,这应通过 gdk update 自动完成。

此暂停期是评估此升级对 GitLab SaaS 风险的好时机。对于高风险 Ruby 升级(如主要版本升级),建议通过变更管理请求协调变更。尽早创建此问题,以便所有人有足够时间安排和准备变更。

将新 Ruby 设为默认版本

如果已知版本兼容性问题已全部解决,且宽限期已过,所有受影响的仓库和开发者工具都应更新以将新 Ruby 设为默认版本。

此时,更新 GitLab Compose Kit (GCK)。这是偏好使用 docker-compose 运行 GitLab 的用户的替代开发环境。此项目依赖与我们 Runner 相同的 Docker 镜像,因此应保持与该仓库变更的一致性。仅当次要或主要版本变更时才需要此更改(示例)。

如前所述,如果 Ruby 升级对 SaaS 可用性的影响不确定,建议跳过此步骤,直到通过分阶段部署验证其在生产环境中运行稳定。此时,先执行下一步,验证期过后再将新 Ruby 推广为新的默认版本。

更新 CNG、Omnibus 和自编译版本并合并 GitLab 合并请求

最后一步是在生产环境中使用新 Ruby。这需要更新 Omnibus 和生产 Docker 镜像以使用新版本。

要在生产环境中使用新 Ruby,请更新以下项目:

如果图表(如 GitLab Helm Chart)以某种方式使用 Ruby(例如运行测试),也应更新(示例),尽管这可能不是严格必需的。

如果您提交变更管理请求,请与基础设施工程师协调部署计划。处理较大升级时,让发布经理参与部署计划。

为安全补丁创建补丁版本和向后移植

如果升级是补丁版本且包含重要安全修复,应将其作为 GitLab 补丁版本发布给自托管客户。咨询我们的发布经理了解后续步骤。

Ruby 升级工具

有多个工具可简化升级过程。

弃用工具包

Ruby 升级的常见问题是弃用警告会变成错误。这意味着在切换前必须解决每个弃用警告。为防止新警告进入主应用分支,我们使用 DeprecationToolkitEnv。此模块观察规范运行中发出的弃用警告并将其转为测试失败。这可防止开发者提交在新 Ruby 下会失败的新代码。

有时不可避免地会引入新警告,例如我们使用的 Ruby gem 发出警告而我们无法控制。在这种情况下,添加静默规则,如此合并请求 所示。

弃用日志记录器

我们还将 Ruby 和 Rails 的弃用警告记录到专用日志文件 log/deprecation_json.log(GitLab 日志文件位置请参阅GitLab 开发者日志指南),当存在测试未充分覆盖的代码时,这能提供线索(这些代码可能绕过 DeprecationToolkitEnv)。

对于 GitLab SaaS,GitLab 团队成员可在 Kibana 中检查这些日志事件(https://log.gprd.gitlab.net/goto/f7cebf1ff05038d901ba2c45925c7e01)。

建议

在升级过程中,请考虑以下建议:

  • 尽可能前置变更。 特别是次要和主要版本,应用代码很可能出现中断或变更。任何向后兼容的变更都应合并到主分支,并在 Ruby 版本升级前独立发布。这确保我们以小步推进,并尽早获取生产环境反馈。
  • 为较大更新创建实验分支。 我们通常避免长时间运行的主题分支,但为了获取反馈和实验,拥有这样的分支有助于定期从 CI/CD 获取使用新 Ruby 的反馈。这在首次评估可能遇到的问题时很有帮助,如此合并请求 所示。这些实验分支不打算合并;一旦所有必需变更被拆分并独立合并,即可关闭。
  • 为里程碑发布预留足够时间修复问题。 GitLab 发展迅速。Ruby 升级需要提交和审查多个合并请求,请确保所有变更至少在发布前一周合并。这为我们提供了额外时间应对突发问题。如有疑问,最好将升级推迟至下个月,因为我们优先保障可用性而非速度