极狐 GitLab

极狐GitLab 开发中的功能标志

本页面介绍了开发者如何通过功能标志参与极狐GitLab 产品的开发与运营。如果你想在自己的应用中创建自定义功能标志以显示或隐藏特性,请参见创建功能标志。极狐GitLab 中功能标志的完整列表 也可供查阅。

所有新引入的功能标志都应默认禁用与角色一起使用

设计文档:

本文档仍在持续完善中,属于一项改进功能标志内部使用史诗的一部分。如有任何建议,请以新议题形式提出并关联到该史诗。

有关功能标志生命周期概述,或者需要决定是否应使用功能标志,请参阅功能标志生命周期手册页面。

何时使用功能标志#

已移至手册中的“何时使用功能标志”章节。

请勿在外部 API 消费者中使用功能标志#

功能标志是内部实现细节,不属于公开 API 契约的一部分。通过 GraphQL 的 metadata.featureFlags 字段或已废弃的 featureFlagEnabled 字段查询功能标志的外部 API 消费者(如 IDE 扩展、Duo CLI 和 CI 集成)面临特定风险:当功能标志从单体应用中移除时,外部消费者可能尚未更新,这可能导致从小范围的 UI 问题到影响客户的严重事件。

在处理外部 API 消费者的功能标志时,请遵循以下指导:

  1. 优先使用 API 字段或应用程序设置。 尽可能避免从外部 API 消费者查询功能标志。相反,应引入专门的 API 字段或应用程序设置供消费者查询。这些值在功能标志移除后会继续存在。
  2. 实现故障开放行为。 如果必须在外部 API 消费者中使用功能标志,请实现“故障开放”机制:在推出里程碑确定后,消费者应默认将功能标志视为已启用。一旦推出里程碑确认,立即更新消费者。请参阅极狐GitLab Language Server 中的示例
  3. 在删除前考虑用户升级模式。 在删除被外部 API 消费者使用的功能标志之前,请评估用户更新客户端的频率,并确定最安全的删除时机。

请勿将功能标志用于长期设置#

功能标志应为短期使用。如果你打算添加一个功能标志以便按用户/群组/项目长期启用某项功能,请考虑引入级联设置应用程序设置。设置允许客户在 JihuLab.com 或私有化部署实例上自行启用或禁用功能,并可根据需要在代码库中保留任意时长。相比之下,用户在 JihuLab.com 上无法自行启用或禁用功能标志,只有私有化部署管理员才能更改功能标志。

极狐GitLab 开发中的功能标志#

在决定是否应使用功能标志时,需重点考虑以下事项:

  • 功能标志必须默认禁用
  • 功能标志应在代码库中保留尽可能短的时间,以减少对功能标志的跟踪需求。
  • 操作功能标志的人员负责在文档中明确传达功能标志背后功能的状态,并与其他利益相关者沟通。一旦明确需要功能标志,议题描述中应更新功能标志名称及其默认状态(开或关)。
  • 引入功能标志、更新其状态或因功能已稳定而移除现有功能标志的合并请求,必须打上 ~"feature flag" 标签。

当功能实现通过多次合并请求交付时:

  1. 在首个使用该功能标志的合并请求中创建一个新的功能标志,并将其设为默认禁用。不应单独添加功能标志
  2. 通过一个或多个合并请求提交增量更改,确保任何新增代码仅在功能标志启用时才可访问。你可以在本地 GDK 开发期间保持功能标志启用。
  3. 当功能准备好供其他团队成员测试时,请创建初始文档。包括有关功能标志状态的详细信息。
  4. 针对特定群组/项目/用户启用功能标志,并确保实现没有问题。如果没有文档,请勿为 gitlab-cn/gitlab 之类的公开项目启用功能标志。团队成员和贡献者如果在公开项目中看到该功能已启用,可能会搜索有关如何使用该功能的文档。
  5. 当功能准备好投入生产使用(包括私有化部署实例)时,打开一个合并请求以:
    • 更新文档,描述最新的功能标志状态。
    • 添加变更日志条目
    • 移除功能标志以启用新行为,或将功能标志设置为默认启用(仅适用于 opsbeta 类型的功能标志)。

当功能标志的移除通过多次合并请求交付时:

  1. 功能标志值的变更应为合并请求中的唯一更改。只要功能标志在代码库中存在,两种状态(功能开启和关闭)都必须是完全可用的。
  2. 在移除所有对功能标志的引用后,可以移除遗留代码。应遵循功能标志推出议题中的步骤,如果某步骤需要跳过,则应在议题中添加评论说明原因。

你可能会认为功能标志会延迟功能发布至少一个月(即一个版本)。但事实并非如此。功能标志并不需要保留特定的时长(例如至少一个版本),而是应该保留直到功能被视为稳定。稳定意味着它在 JihuLab.com 上运行不会导致任何问题,例如服务中断。

默认分支损坏的风险#

功能标志必须在引入它们的合并请求中使用。否则会导致默认分支损坏的情况,因为 rspec:feature-flags 作业仅在默认分支上运行。

功能标志的类型#

选择符合预期用途的功能标志类型。

gitlab_com_derisk 类型#

gitlab_com_derisk 功能标志是短期功能标志,用于降低 JihuLab.com 部署的风险。极狐GitLab 中使用的大多数功能标志都属于 gitlab_com_derisk 类型。

约束#

  • default_enabled不得设为 true。此类功能标志旨在降低 JihuLab.com 上的风险,因此在 JihuLab.com 上启用后无需在代码库中保留。对于此类型的功能标志,default_enabled: true 不起作用。
  • 最大生命周期:合并入默认分支后 2 个月
  • 文档:鉴于它们是短期且与部署相关的,此类功能标志无需在极狐GitLab 中的所有功能标志页面进行记录
  • 推出议题:必须根据功能标志推出模板创建推出议题

用法#

gitlab_com_derisk 功能标志的格式为 Feature.<state>(:<dev_flag_name>)

要启用和禁用它们,请在极狐GitLab Rails 控制台中运行:

ruby
1# 要为实例启用: 2Feature.enable(:<dev_flag_name>) 3 4# 要为实例禁用: 5Feature.disable(:<dev_flag_name>) 6 7# 要为特定项目启用: 8Feature.enable(:<dev_flag_name>, Project.find(<project id>)) 9 10# 要为特定项目禁用: 11Feature.disable(:<dev_flag_name>, Project.find(<project id>))

要检查 gitlab_com_derisk 功能标志的状态:

ruby
# 检查功能标志是否已启用 Feature.enabled?(:dev_flag_name) # 检查功能标志是否已禁用 Feature.disabled?(:dev_flag_name)

wip 类型#

某些功能比较复杂,需要通过多次合并请求实现。在它们完全实现之前,需要对所有人隐藏。在这种情况下,wip(“进行中工作”)功能标志允许将所有更改合并到主分支,而实际上尚未使用该功能。

功能完成后,功能标志类型可以根据如何向客户呈现/记录该功能,更改为 gitlab_com_deriskbeta 类型。

约束#

  • default_enabled不得设为 true。如有需要,功能完成后可将此类型更改为 beta。
  • 最大生命周期:合并入默认分支后 4 个月
  • 文档:鉴于它们主要用于隐藏未完成的代码,此类功能标志无需在极狐GitLab 中的所有功能标志页面进行记录
  • 推出议题:很可能不需要推出议题,因为 wip 功能标志在启用前应转换为其他类型

用法#

ruby
1# 检查功能标志是否已启用 2Feature.enabled?(:my_wip_flag, project) 3 4# 检查功能标志是否已禁用 5Feature.disabled?(:my_wip_flag, project) 6 7# 将功能标志推送到前端 8push_frontend_feature_flag(:my_wip_flag, project)

beta 类型#

对于某些功能,我们可能不确定能够在当前形式下为所有设计用例进行扩展、支持和维护示例)。此外,有些功能尚不够完善,无法被视为 MVC。在这种情况下提供功能标志,可以让工程师和客户禁用新功能,直到其性能达到可接受的程度。

约束#

  • default_enabled:可以设为 true,以便以 beta 形式向所有人“发布”该功能,并在出现可扩展性问题时保留禁用的可能性(理想情况下,仅应为此原因在特定私有化部署安装中禁用)
  • 最大生命周期:合并入默认分支后 6 个月
  • 文档:此类功能标志必须极狐GitLab 中的所有功能标志页面进行记录
  • 推出议题:必须根据功能标志推出模板创建推出议题

用法#

ruby
1# 检查功能标志是否已启用 2Feature.enabled?(:my_beta_flag, project) 3 4# 检查功能标志是否已禁用 5Feature.disabled?(:my_beta_flag, project) 6 7# 将功能标志推送到前端 8push_frontend_feature_flag(:my_beta_flag, project)

ops 类型#

ops 功能标志是长期功能标志,用于控制极狐GitLab 产品行为的运维方面。例如,禁用可能影响性能的功能的功能标志,如 Sidekiq worker 行为。

请记住,使用这种类型时,应是有意识地决定不引入实例/群组/项目/用户设置。

虽然 ops 类型的功能标志拥有无限生命周期,但每 12 个月必须进行一次评估,以确定它们是否仍然必要。如果是,则必须将 milestone 字段更新为最新的里程碑,以确认该 ops 功能标志仍在使用中。

约束#

  • default_enabled:大多数情况下应设为 false,仅在解决临时可扩展性问题或帮助调试生产问题时启用。
  • 最大生命周期:无限,但每 12 个月必须进行评估
  • 文档:此类功能标志必须极狐GitLab 中的所有功能标志页面进行记录,并关联运维 runbook,说明可以使用它的场景。
  • 推出议题:很可能不需要推出议题,因为很难预测它们何时会被启用或禁用

用法#

ruby
1# 检查功能标志是否已启用 2Feature.enabled?(:my_ops_flag, project) 3 4# 检查功能标志是否已禁用 5Feature.disabled?(:my_ops_flag, project) 6 7# 将功能标志推送到前端 8push_frontend_feature_flag(:my_ops_flag, project)

experiment 类型#

experiment 功能标志用于 JihuLab.com 上的 A/B 测试。

experiment 功能标志应遵循与 beta 功能标志相同的标准,但接口上有些差异。实验功能标志应有一个推出议题,使用实验跟踪模板创建。更多信息请参阅实验指南

约束#

  • default_enabled不得设为 true
  • 最大生命周期:合并入默认分支后 6 个月

worker 类型#

worker 功能标志是特殊的 ops 标志,用于控制 Sidekiq worker 的行为,例如延迟 Sidekiq 作业。

worker 功能标志可能没有任何 YAML 定义,因为其名称可以根据 worker 名称动态生成,例如 run_sidekiq_jobs_AuthorizedProjectsWorker。使用 worker 类型功能标志的一些示例可以在延迟 Sidekiq 作业中找到。

(已弃用)development 类型#

development 类型已被弃用,取而代之的是 gitlab_com_deriskwipbeta 功能标志类型。

功能标志定义与验证#

在开发 (RAILS_ENV=development) 或测试 (RAILS_ENV=test) 环境中,所有功能标志的使用都会受到严格验证。

此过程旨在确保代码库中功能标志使用的一致性。所有功能标志必须

  • 已知。仅使用显式定义的功能标志(experimentworkerundefined 类型的功能标志除外)。
  • 不被重复定义。它们必须在 FOSS 或 EE 中定义,但不能同时定义。
  • 对于没有定义文件的功能标志,在所有调用中使用有效且一致的 type:
  • 拥有负责人。

极狐GitLab 中所有已知的功能标志都以 YAML 文件的形式自我记录,存储在:

每个功能标志在一个单独的 YAML 文件中定义,包含以下字段:

字段必填描述
name功能标志的名称。
description功能标志原因的简短描述。
type功能标志的类型。
default_enabled功能标志的默认状态。
introduced_by_url引入该功能标志的合并请求的 URL。
milestone创建该功能标志的里程碑。
group拥有该功能标志的群组
log_state_changes用于记录功能标志的状态

RAILS_ENV=production 环境中运行时,会跳过所有验证。

创建新功能标志#

极狐GitLab Pages 使用不同的流程处理功能标志。

极狐GitLab 代码库提供了 bin/feature-flag 这一专用工具,用于创建新功能标志定义。该工具会询问有关新功能标志的多个问题,然后在 config/feature_flagsee/config/feature_flags 中创建 YAML 定义。

在开发或测试环境中,只有具有 YAML 定义文件的功能标志才能被使用。

shell
1$ bin/feature-flag my_feature_flag 2>> 指定功能标志类型 3?> beta 4你选择了类型 'beta' 5 6>> 指定功能标志所属的群组标签,从以下列表中选择: 71. group::group1 82. group::group2 9?> 2 10你选择了群组 'group::group2' 11 12>> URL of the MR introducing the feature flag (enter to skip and let Danger provide a suggestion directly in the MR): 13?> https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/141023 14 15>> 功能标志 DRI 的用户名(回车跳过): 16?> bob 17 18>> 这是否仅适用于 EE(回车跳过): 19?> [回车] 20 21>> 按任意键并粘贴我们已复制到剪贴板的议题内容!🚀 22?> [回车自动打开“新建议题”页面,你只需粘贴议题内容] 23 24create config/feature_flags/beta/my_feature_flag.yml 25--- 26name: my_feature_flag 27introduced_by_url: https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/141023 28milestone: '16.9' 29group: group::composition analysis 30type: beta 31default_enabled: false

所有新引入的功能标志都必须默认禁用

在功能标志背后开发并合并的功能不应包含变更日志条目。该条目应在移除功能标志或将功能标志的默认值设为启用的合并请求中添加。如果该功能包含任何数据库迁移,则应该包含针对数据库更改的变更日志条目。

要创建仅在 EE 中使用的功能标志,请添加 --ee 标志:bin/feature-flag --ee

命名新功能标志#

为新功能标志选择名称时,请考虑以下准则:

  • 描述该功能标志所控制的功能

    • 与简短但令人困惑的名称相比,描述性的长名称更好。
  • 避免使用表明功能状态/阶段的名称,如 _mvc_alpha_beta

  • 使用蛇形命名法 (my_cool_feature_flag)。

  • 避免在名称中使用 disable,以避免考虑(或文档)双重否定。考虑以 hide_remove_disallow_ 开头。

    在软件工程中,这个问题被称为“布尔变量的否定命名”。但我们不能完全禁用否定词,以便能够引入默认禁用的标志,通过将功能移到标志后面来删除该功能,或按角色选择性禁用标志

主分支(master)损坏的风险#

功能标志必须在引入它们的合并请求中使用。否则会导致主分支损坏的情况,因为 rspec:feature-flags 作业仅在 master 分支上运行。

可选:添加用于自动移除功能标志的 .patch 文件#

gitlab-housekeeper 能够使用 DeleteOldFeatureFlags keep 自动为你移除功能标志代码。该工具将定期运行,并自动从代码中清理旧的功能标志。

为了让该工具能够自动移除你代码中功能标志的用法,你可以添加一个与功能标志 YAML 文件同名的 .patch 文件(但扩展名为 .patch)。

例如,你可以通过以下步骤为 config/feature_flags/beta/my_feature_flag.yml 创建补丁文件:

  1. 确保你的 Git 工作目录是干净的。
  2. 删除 config/feature_flags/beta/my_feature_flag.yml
  3. 在本地编辑代码,移除所有 my_feature_flag 的使用,就像功能标志已启用且功能正在推进一样。
  4. 运行 git diff > config/feature_flags/beta/my_feature_flag.patch。如果你的功能标志不是 beta 标志,请确保将补丁文件放在与定义功能标志的 YAML 文件相同的目录中。
  5. 撤销对 config/feature_flags/beta/my_feature_flag.yml 的删除。
  6. 撤销你对文件所做的移除功能标志用法的更改。
  7. 将补丁文件提交到你添加功能标志的分支。

将来,gitlab-housekeeper 会通过应用此补丁,自动为你清理功能标志。

列出所有功能标志#

使用 ChatOps 将环境中所有功能标志输出到 Slack,你可以使用 run feature list 命令。例如:

shell
/chatops gitlab run feature list --dev /chatops gitlab run feature list --staging

切换功能标志#

请参阅推出变更 了解更多关于切换功能标志的信息。

删除功能标志#

请参阅清理功能标志了解更多关于删除功能标志的信息。

ops 功能标志迁移到应用程序设置#

回填应用程序设置并在代码中使用这些设置的更改必须在同一里程碑中合并。

要将 ops 功能标志迁移到应用程序设置:

  1. 在应用程序设置中,创建或标识一个现有的 JSONB 列来存储该设置。
  2. 应用程序设置的默认值应与功能标志 YAML 定义中的 default_enabled: 相匹配。
  3. 编写迁移来回填该列。这允许选择退出默认行为的实例保持相同状态。在迁移中避免使用 Feature.enabled?Feature.disabled?。使用 Gitlab::Database::MigrationHelpers::FeatureFlagMigratorHelpers 迁移助手。这些助手只会迁移明确设置为 truefalse 的功能标志。如果功能标志设置为百分比或特定参与方,将使用默认值。
  4. 管理员 区域,创建一个启用或禁用该功能的设置。
  5. 在所有地方用应用程序设置替换功能标志。
  6. 更新所有相关文档页面。如果前端更改在后续里程碑中合并,你应添加关于如何使用 应用程序设置 API 或 Rails 控制台更新设置的文档。

JSONB 列的示例迁移:

ruby
1# default_enabled 从功能标志定义的 YAML 中复制,在删除之前 2DEFAULT_ENABLED = true 3 4def up 5 up_migrate_to_jsonb_setting(feature_flag_name: :my_flag_name, 6 setting_name: :my_setting, 7 jsonb_column_name: :settings, 8 default_enabled: DEFAULT_ENABLED) 9end 10 11def down 12 down_migrate_to_jsonb_setting(setting_name: :my_setting, jsonb_column_name: :settings) 13end

布尔列的示例迁移:

ruby
1# default_enabled 从功能标志定义的 YAML 中复制,在删除之前 2DEFAULT_ENABLED = true 3 4def up 5 up_migrate_to_setting(feature_flag_name: :my_flag_name, 6 setting_name: :my_setting, 7 default_enabled: DEFAULT_ENABLED) 8end 9 10def down 11 down_migrate_to_setting(setting_name: :my_setting, default_enabled: DEFAULT_ENABLED) 12end

使用功能标志进行开发#

在 极狐GitLab 代码库中,有两种使用功能标志的主要方式:

后端#

功能标志接口定义在 lib/feature.rb 中。 此接口提供了一组方法来检查功能标志是否启用或禁用:

ruby
1if Feature.enabled?(:my_feature_flag, project) 2 # 如果功能标志启用,则执行代码 3else 4 # 如果功能标志禁用,则执行代码 5end 6 7if Feature.disabled?(:my_feature_flag, project) 8 # 如果功能标志禁用,则执行代码 9end

未配置的功能标志的默认行为由 YAML 定义中的 default_enabled: 控制。

如果功能标志没有 YAML 定义,在开发或测试环境中会引发错误,而在生产环境中会返回 false

对于没有定义文件的功能标志(仅允许 experimentworkerundefined 类型),在调用 Feature.enabled?Feature.disabled? 时需要传递它们的 type:

ruby
1if Feature.enabled?(:experiment_feature_flag, project, type: :experiment) 2 # 如果功能标志启用,则执行代码 3end 4 5if Feature.disabled?(:worker_feature_flag, project, type: :worker) 6 # 如果功能标志禁用,则执行代码 7end

不要在应用程序加载时使用功能标志。例如,在 config/initializers/* 中或在类级别使用 Feature 类可能会导致意外错误。发生此错误是因为功能标志适配器可能依赖的数据库在加载时不存在(尤其是对于全新安装)。不建议在调用方检查数据库是否存在,因为某些适配器根本不依赖数据库(例如,HTTP 适配器)。功能标志设置检查必须在 Feature 命名空间中进行抽象。这种方法在功能标志更改时还需要应用程序重载。因此,你必须请求 SRE 在生产环境中重载 Web/API/Sidekiq 集群,这需要时间才能完全推出/回滚更改。出于这些原因,请改用环境变量(例如 ENV['YOUR_FEATURE_NAME'])或 gitlab.yml

以下是一个应避免的模式示例:

ruby
1class MyClass 2 if Feature.enabled?(:...) 3 new_process 4 else 5 legacy_process 6 end 7end

递归检测#

当功能标志很多时,并不总清楚它们在何处被调用。要避免出现循环,其中一个功能标志的评估需要评估其他功能标志。如果这导致循环,它将被打破并返回默认值。

为了使递归检测正常工作,始终通过 Feature::enabled? 访问功能值,并避免低级别使用 Feature::get。当发生这种情况时,我们会跟踪 Feature::RecursionError 异常到错误跟踪器。

前端#

当为 UI 元素使用功能标志时,确保也为底层后端代码使用功能标志(如果存在)。这确保在启用之前,绝对无法使用该功能。

使用 push_frontend_feature_flag 方法,该方法对所有继承自 ApplicationController 的控制器可用。你可以使用此方法暴露功能标志的状态,例如:

ruby
1before_action do 2 # 更倾向于按项目或用户限定作用域,例如 3 push_frontend_feature_flag(:vim_bindings, project) 4end 5 6def index 7 # ... 8end 9 10def edit 11 # ... 12end

然后你可以在 JavaScript 中检查功能标志的状态如下:

javascript
if ( gon.features.vimBindings ) { // ... }

JavaScript 中功能标志的名称始终是驼峰式, 因此检查 gon.features.vim_bindings 将无效。

有关如何访问 Vue 组件中的功能标志的详细信息,请参阅 Vue 指南

对于没有定义文件的功能标志(仅允许 experimentworkerundefined 类型),在调用 push_frontend_feature_flag 时需要传递它们的 type:

ruby
before_action do push_frontend_feature_flag(:vim_bindings, project, type: :experiment) end

功能参与方#

强烈建议为功能标志使用参与方。 参与方提供了一种简单的方式,仅为给定项目、群组或用户启用功能标志。这使得调试更加容易,例如你可以根据参与方过滤日志和错误。这也使得可以首先在 gitlab-orggitlab-com 群组上启用该功能,而其他用户不受影响。

参与方还提供了一种简单的方法,以粘性方式进行功能百分比推出。如果 1% 的推出为特定参与方启用了功能,该参与方将在 10%、50% 和 100% 下继续保持该功能启用。

极狐GitLab 支持以下功能标志参与方:

  • User 模型
  • Project 模型
  • Group 模型
  • Ci::Runner 模型
  • 当前请求

参与方是 Feature.enabled? 调用的第二个参数。例如:

ruby
Feature.enabled?(:feature_flag, project)

包含 FeatureGate 的模型具有 .actor_from_id 类方法。 如果你拥有模型 ID,并且除了检查功能标志状态外不需要该模型执行其他操作,则可以使用 .actor_from_id 来检查功能标志状态,而无需执行数据库查询来检索模型。

ruby
1# 糟糕 —— 执行了不必要的查询 2Feature.enabled?(:feature_flag, Project.find(project_id)) 3 4# 良好 —— 不对项目执行查询 5Feature.enabled?(:feature_flag, Project.actor_from_id(project_id)) 6 7# 良好 —— 功能标志检查后使用项目模型 8project = Project.find(project_id) 9return unless Feature.enabled?(:feature_flag, project) 10project.update!(column: value)

有关如何使用 ChatOps 在 极狐GitLab 提供的环境(如 staging 和 production)中选择性地启用或禁用功能标志的详细信息,请参阅 使用 ChatOps 启用和禁用功能标志

标志状态不会从群组继承到其子群组或项目。如果你需要标志状态在整个群组层次结构中保持一致,请考虑使用顶级群组作为参与方。可以通过在任何群组或项目上调用 #root_ancestor 来找到此群组。

ruby
Feature.enabled?(:feature_flag, group.root_ancestor)

混合参与方类型#

通常,对于特定功能标志,在 Feature.enabled? 的所有调用中应仅使用一种类型的参与方,不要混合使用不同的参与方类型。

混合参与方类型可能导致功能以不一致的方式启用或禁用,从而引发错误。例如,如果在控制器级别使用群组参与方检查标志,而在服务级别使用用户参与方,则该功能可能在同一请求中的不同点上既被启用又被禁用。

在某些情况下,如果你知道混合参与方类型不会导致不一致的结果,这样做是安全的。例如,Webhook 可以与群组或项目关联,因此用于 Webhook 的功能标志可能利用这一点,使用相同的功能标志来为群组 Webhook 和项目 Webhook 推出功能。

如果你需要使用不同的参与方类型,并且无法在你的情况下安全地混合使用,则应改为为每种参与方类型使用单独的标志。例如:

ruby
Feature.enabled?(:feature_flag_group, group) Feature.enabled?(:feature_flag_user, user)

实例参与方#

仅当功能与整个实例绑定时才应使用实例级功能标志。始终优先考虑其他参与方。

在某些情况下,你可能希望功能标志针对整个实例启用,而不是基于某个参与方。一个很好的例子是管理员设置,在这种情况下,无法基于群组或项目启用功能标志,因为它们都是 undefined

用户参与方会造成混淆,因为功能标志可能对非管理员用户启用,但对管理员用户禁用。

相反,可以使用 :instance 符号作为 Feature.enabled? 的第二个参数,它将被视为 极狐GitLab 实例。

ruby
Feature.enabled?(:feature_flag, :instance)

当前请求参与方#

版本历史

不建议使用按时间百分比推出,因为每次调用可能返回不一致的结果。

相反,建议使用当前请求作为参与方。

ruby
1# 糟糕 2Feature.enable_percentage_of_time(:feature_flag, 40) 3Feature.enabled?(:feature_flag) 4 5# 良好 6Feature.enable_percentage_of_actors(:feature_flag, 40) 7Feature.enabled?(:feature_flag, Feature.current_request)

当使用当前请求作为参与方时,功能标志应在请求的上下文中返回相同的值。 由于当前请求参与方是使用 SafeRequestStore 实现的,我们应在以下范围内获得一致的功能标志值:

  • 一个 Rack 请求
  • 一个 Sidekiq 工作器执行
  • 一个 ActionCable 工作器执行

要将现有功能从按时间百分比迁移到当前请求参与方,建议你创建一个新的功能标志。 这是因为很难控制现有 percentage_of_time 值、代码变更部署以及切换到使用 percentage_of_actors 之间的时机。

使用参与方在生产环境中验证#

不建议将生产环境用作测试环境。使用我们的测试环境来测试未达到生产就绪状态的功能。

虽然 staging 环境提供了一种在类似生产环境的环境中测试功能的方法,但它不允许你比较生产环境特定的前后性能指标。在生产环境中拥有一个启用了开发功能标志的项目可能很有用,使诸如 Sitespeed 报告之类的工具能够揭示功能标志下新代码的指标。

如果你已经在 Sitespeed 中跟踪旧代码,这种方法会更加有用,使你能够在功能标志推出前后准确比较性能。

启用其他对象作为参与方#

要基于参与方使用功能门控,模型需要响应 flipper_id。例如,为 Foo 模型启用:

ruby
class Foo < ActiveRecord::Base include FeatureGate end

只有包含 FeatureGate 或暴露 flipper_id 方法的模型才能用作 Feature.enabled? 的参与方。

许可功能的功能标志#

你不能使用与许可功能名称相同的功能标志名称,因为这会导致命名冲突。此问题已被广泛讨论并移除,因为它会造成混淆。

要检查许可功能,请添加一个不同名称的专用功能标志,并显式检查它,例如:

ruby
Feature.enabled?(:licensed_feature_feature_flag, project) && project.feature_available?(:licensed_feature)

功能组#

功能组必须在 lib/feature.rb(在 .register_feature_groups 方法中)中静态定义,但其实现可以是动态的(例如,查询数据库)。

lib/feature.rb 中定义后,你可以通过 features API 的 feature_group 参数 为给定功能组激活一项功能。

可用的功能组包括:

组名作用域于描述
gitlab_team_members用户gitlab-com 的成员用户启用功能。

可以通过组名启用功能组:

ruby
Feature.enable(:feature_flag_name, :gitlab_team_members)

本地控制功能标志#

在 Rails 控制台中#

在 Rails 控制台 (rails c) 中,输入以下命令启用功能标志:

ruby
Feature.enable(:feature_flag_name)

类似地,以下命令禁用功能标志:

ruby
Feature.disable(:feature_flag_name)

你也可以为给定的门控启用功能标志:

ruby
Feature.enable(:feature_flag_name, Project.find_by_full_path("root/my-project"))

当从 Rails 控制台手动启用或禁用功能标志时,其默认值会被覆盖。 在更改标志的 default_enabled 属性时,这可能会造成混淆。

要将功能标志重置回默认状态:

ruby
Feature.remove(:feature_flag_name)

要将所有功能标志重置为来自 YAML 定义的默认状态:

ruby
Feature.all.each(&:remove)

在浏览器中#

访问 http://gdk.test:3000/rails/features 可在本地管理功能标志。

日志记录#

如果满足以下任一条件,则记录功能标志的使用情况和状态:

  • 功能标志定义中 log_state_changes 设置为 true
  • milestone 指向一个大于或等于当前 极狐GitLab 版本的里程碑。

当记录功能标志的状态时,可以使用 "json.feature_flag_states": "feature_flag_name:1""json.feature_flag_states": "feature_flag_name:0" 条件在 Kibana 中识别它。 你可以在此链接 中查看示例。

只有 20% 的请求记录功能标志的状态。这由 feature_flag_state_logs 功能标志控制。

变更日志#

我们希望避免在最终用户无法直接(例如:能够使用该功能)或间接(例如:能够利用后台作业、性能改进或数据库迁移更新)访问功能时引入变更日志。

  • 数据库迁移总是能被最终用户间接访问,因为私有化部署客户在升级前需要了解数据库更改。因此,它们应该有变更日志条目。

  • 任何位于默认禁用的功能标志后面的更改不应有变更日志条目。

  • 任何位于默认启用的功能标志后面的更改应该有变更日志条目。

  • 更改功能标志本身(移除标志、设置默认开启)应该变更日志条目。 使用流程图来确定变更日志条目类型。

    Rendering chart...
  • 功能标志的变更日志应描述该功能,而不是标志本身,除非一个默认开启的功能标志被移除并保留了新代码(上图中的 other)。

  • 功能标志也可用于推出 bug 修复或维护工作。在这种情况下,变更日志必须与之相关,例如;fixedother

测试中的功能标志#

在代码库中引入功能标志会创建一条额外的代码路径,应对其进行测试。 强烈建议为所有受功能标志影响的代码包含自动化测试,在启用禁用两种状态下都要测试, 以确保功能正常工作。如果未包含两种状态的自动化测试,则应在部署到生产环境之前手动测试与未测试代码路径相关的功能。

使用测试环境时,所有功能标志默认启用。 可以在 spec/spec_helper.rb 文件 中通过默认禁用标志。 添加行内注释以解释为什么需要禁用该标志。如果可以,也可以附上议题 URL 以供参考。

这不适用于端到端 (QA) 测试,端到端测试默认不启用功能标志。在端到端测试中使用功能标志有不同的流程

要在测试中禁用功能标志,使用 stub_feature_flags 助手。 例如,要在测试中全局禁用 ci_live_trace 功能标志:

ruby
stub_feature_flags(ci_live_trace: false) Feature.enabled?(:ci_live_trace) # => false

测试两种路径的常见模式如下:

ruby
1it 'ci_live_trace 有效' do 2 # 假设测试中 ci_live_trace 默认启用 3 Feature.enabled?(:ci_live_trace) # => true 4end 5 6context '当 ci_live_trace 被禁用时' do 7 before do 8 stub_feature_flags(ci_live_trace: false) 9 end 10 11 it 'ci_live_trace 不工作' do 12 Feature.enabled?(:ci_live_trace) # => false 13 end 14end

如果你希望设置一个测试,其中功能标志仅对某些参与方启用而对其他参与方不启用,你可以在传递给助手的选项中指定这一点。例如,为特定项目启用 ci_live_trace 功能标志:

ruby
1project1, project2 = build_list(:project, 2) 2 3# 功能将仅对 project1 启用 4stub_feature_flags(ci_live_trace: project1) 5 6Feature.enabled?(:ci_live_trace) # => false 7Feature.enabled?(:ci_live_trace, project1) # => true 8Feature.enabled?(:ci_live_trace, project2) # => false

FlipperGate 的行为如下:

  1. 你可以为指定参与方启用覆盖。
  2. 你可以为指定参与方禁用(移除)覆盖,回退到默认状态。
  3. 无法显式地禁用一个指定参与方。
ruby
1Feature.enable(:my_feature) 2Feature.disable(:my_feature, project1) 3Feature.enabled?(:my_feature) # => true 4Feature.enabled?(:my_feature, project1) # => true 5 6Feature.disable(:my_feature2) 7Feature.enable(:my_feature2, project1) 8Feature.enabled?(:my_feature2) # => false 9Feature.enabled?(:my_feature2, project1) # => true

have_pushed_frontend_feature_flags#

使用 have_pushed_frontend_feature_flags 来测试 push_frontend_feature_flag 是否已将功能标志添加到 HTML 中。

例如,

ruby
stub_feature_flags(value_stream_analytics_path_navigation: false) visit group_analytics_cycle_analytics_path(group) expect(page).to have_pushed_frontend_feature_flags(valueStreamAnalyticsPathNavigation: false)

stub_feature_flags 对比 Feature.enable*#

在测试环境中,优先使用 stub_feature_flags 来启用功能标志。此方法为简单用例提供了简单且描述清晰的接口。

然而,在某些情况下需要测试更复杂的行为,例如功能标志的百分比推出。这可以通过使用 .enable_percentage_of_time.enable_percentage_of_actors 完成:

ruby
1# 良好:功能需要显式禁用,因为如果没有定义,它默认是启用的 2stub_feature_flags(my_feature: false) 3stub_feature_flags(my_feature: true) 4stub_feature_flags(my_feature: project) 5stub_feature_flags(my_feature: [project, project2]) 6 7# 不良 8Feature.enable(:my_feature_2) 9 10# 良好:为 my_feature 启用 50% 的时间 11Feature.enable_percentage_of_time(:my_feature_3, 50) 12 13# 良好:为 my_feature 启用 50% 的参与方/门控/事物 14Feature.enable_percentage_of_actors(:my_feature_4, 50)

在测试执行期间,每个具有定义状态的功能标志都会被持久化:

ruby
Feature.persisted_names.include?('my_feature') => true Feature.persisted_names.include?('my_feature_2') => true Feature.persisted_names.include?('my_feature_3') => true Feature.persisted_names.include?('my_feature_4') => true

桩参与方#

当你只想为特定参与方启用功能标志时,你可以桩化其表示。传递给 Feature.enabled?Feature.disabled? 作为参数的门控必须是一个包含 FeatureGate 的对象。

在 specs 中,你可以使用 stub_feature_flag_gate 方法,该方法允许你快速创建一个自定义参与方:

ruby
1gate = stub_feature_flag_gate('CustomActor') 2 3stub_feature_flags(ci_live_trace: gate) 4 5Feature.enabled?(:ci_live_trace) # => false 6Feature.enabled?(:ci_live_trace, gate) # => true

在测试中控制功能标志引擎#

我们的 Flipper 引擎在测试环境中以内存模式 Flipper::Adapters::Memory 工作。 productiondevelopment 模式使用 Flipper::Adapters::ActiveRecord

你可以控制使用的是 Flipper::Adapters::Memory 还是 ActiveRecord 模式。

stub_feature_flags: true(默认且首选的模式)#

在此模式下,Flipper 被配置为使用 Flipper::Adapters::Memory,并将所有功能标志标记为默认开启,并在首次使用时持久化。

确保功能标志下的行为不会在某些非特定上下文中未经测试。

stub_feature_flags: false#

这将禁用内存桩的 flipper,并使用 Flipper::Adapters::ActiveRecord,这是 productiondevelopment 使用的模式。

你应该仅在确实需要测试 Flipper 与 ActiveRecord 交互的方面时使用此模式。

端到端 (QA) 测试#

在端到端 (QA) 测试中,切换功能标志的方式有所不同。端到端测试框架无法直接访问 Rails 或数据库,因此不能使用 Flipper。相反,它使用公共 API。每个端到端测试可以在测试期间启用或禁用功能标志。或者,你可以在从一个或多个测试之前启用或禁用功能标志,当你从你的 极狐GitLab 仓库的 qa 目录运行它们时,或者如果你通过 GitLab QA 运行测试

如上所述,功能标志在端到端测试中默认未启用。 这意味着,除非测试显式编写为启用/禁用功能标志,否则端到端测试将在源代码中实现的默认状态的功能标志下运行,或者在被测极狐GitLab 实例上功能标志的当前状态下运行。

当在 Staging 或 JihuLab.com 上更改功能标志时,Slack 消息将发布到 #e2e-run-staging#e2e-run-production 频道,以通知流水线问题处理负责人,以便他们更容易确定是否有任何故障与功能标志更改有关。但是,如果您正在进行更改,可以通过确认在启用功能标志的情况下端到端测试通过来帮助避免意外故障。

使用功能标志控制 Sidekiq 工作器行为#

具有 worker 类型的功能标志可用于控制 Sidekiq 工作器的行为。

延迟 Sidekiq 作业#

当禁用时,格式为 run_sidekiq_jobs_{WorkerName} 的功能标志通过将作业安排在稍后时间执行来延迟工作器的执行。此功能标志默认对所有工作器启用。在事件期间,当工作器实例引发资源争用的行为耗尽基础设施资源(例如数据库和数据库连接池)时,延迟作业可能很有用。实现可以在 SkipJobs Sidekiq 服务器中间件 中找到。

只要功能标志被禁用,作业就会无限期延迟。在认为工作器可以安全继续处理后,移除功能标志非常重要。

当设置为 false 时,100% 的作业被延迟。当您希望恢复处理时,可以使用时间百分比的逐步推出。例如:

shell
1# 不运行任何作业,延迟所有 100% 的作业 2/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker false 3 4# 仅运行 10% 的作业,延迟 90% 的作业 5/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 10 6 7# 运行 50% 的作业,延迟 50% 的作业 8/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 50 9 10# 恢复正常运行所有作业 11/chatops gitlab run feature delete run_sidekiq_jobs_SlowRunningWorker

丢弃 Sidekiq 作业#

与延迟作业不同,可以通过启用功能标志 drop_sidekiq_jobs_{WorkerName} 来完全丢弃作业。当您确定这些作业将来不需要处理,因此可以安全丢弃时,使用此功能标志。

shell
# 丢弃所有作业 /chatops gitlab run feature set drop_sidekiq_jobs_SlowRunningWorker true # 正常处理作业 /chatops gitlab run feature delete drop_sidekiq_jobs_SlowRunningWorker

丢弃功能标志(drop_sidekiq_jobs_{WorkerName})优先于延迟功能标志(run_sidekiq_jobs_{WorkerName})。当启用 drop_sidekiq_jobs 且禁用 run_sidekiq_jobs 时,作业将被完全丢弃。