GitLab 开发中的功能标志
本文档说明开发者如何通过功能标志参与 GitLab 产品的开发和运维。如需在您自己的应用中创建自定义功能标志来显示或隐藏功能,请参阅 创建功能标志。
GitLab 中完整的功能标志列表 也已提供。
所有新引入的功能标志都应 默认禁用。
所有新引入的功能标志都应 与 actor 结合使用。
设计文档:
本文档是 改进功能标志内部使用史诗的持续工作内容。如有建议,请创建新 issue 并关联到该史诗。
如需了解 功能标志生命周期概览,或需要帮助判断 是否应使用功能标志,请参阅功能标志生命周期手册页面。
何时使用功能标志
已移至手册中的 “何时使用功能标志” 章节。
不要将功能标志用于长期存在的设置
功能标志应保持短期存在。如果您计划添加功能标志以便为用户/组/项目长期启用某功能,建议改用 级联设置 或 应用设置。
设置允许客户在 GitLab.com 或自托管环境中自行启用或禁用功能,且可在代码库中长期保留。相比之下,用户无法在 GitLab.com 上自行启用或禁用功能标志,只有自托管管理员才能修改功能标志。
此外,功能标志在 GitLab Dedicated 中不受支持,这也是不应将其作为设置替代品的另一个原因。
GitLab 开发中的功能标志
在决定是否使用功能标志时,应考虑以下要点:
- 功能标志必须 默认禁用。
- 功能标志应尽可能缩短在代码库中的存在时间,以减少功能标志管理需求。
- 操作功能标志的人员需负责在文档和其他利益相关者中明确传达功能标志后功能的当前状态。一旦确定需要功能标志,应立即在 issue 描述中更新功能标志名称及其默认启用/禁用状态。
- 引入功能标志、更新其状态或因功能被判定为稳定而移除现有功能标志的合并请求,必须分配 ~“feature flag” 标签。
当功能实现跨越多个合并请求时:
- 在首次使用该标志的合并请求中 创建新功能标志,默认禁用。
标志 不应单独添加。 - 通过一个或多个合并请求提交增量更改,确保新增代码仅在功能标志 启用 时才能执行。
开发期间可在本地 GDK 中保持功能标志启用状态。 - 当功能准备供其他团队成员测试时,创建初始文档。
包含关于 功能标志 状态的详细信息。 - 为特定组/项目/用户启用功能标志,确保实现无问题。若无文档,请勿为
gitlab-org/gitlab等公共项目启用功能标志。团队成员和贡献者可能在看到公共项目中的功能启用时搜索使用文档。 - 当功能准备投入生产(包括 GitLab 自托管实例),打开一个合并请求以:
- 更新文档描述最新标志状态。
- 添加 变更日志条目。
- 移除功能标志以启用新行为,或将功能标志切换为 默认启用(仅限
ops和beta功能标志)。
当功能标志移除跨越多个合并请求时:
- 功能标志的值变更应是合并请求中的唯一更改。只要功能标志存在于代码库中,两种状态(功能启用和禁用时)都必须完全正常工作。
- 移除所有功能标志引用后,可删除遗留代码。应遵循功能标志发布 issue 中的步骤,如需跳过某步骤,应在 issue 中添加注释说明原因。
有人可能会认为功能标志至少会延迟功能发布一个月(即一个发布周期)。实际情况并非如此。功能标志无需固定存在特定时间(例如至少一个发布周期),而应持续存在直到功能被判定为稳定。稳定意味着在 GitLab.com 上运行不会导致任何问题,例如服务中断。
默认分支损坏风险
功能标志必须在引入它们的合并请求中使用。否则会导致 默认分支损坏,因为 rspec:feature-flags 任务仅在默认分支上运行。
功能标志类型
选择与预期用途匹配的功能标志类型。
gitlab_com_derisk 类型
gitlab_com_derisk 功能标志是短期功能标志,用于降低 GitLab.com 部署风险。GitLab 中使用的大多数功能标志属于此类型。
约束条件
default_enabled:必须 设为false。此类功能标志旨在降低 GitLab.com 风险,因此在 GitLab.com 上启用后无需保留在代码库中。default_enabled: true对此类型功能标志无效。- 最大生命周期:合并到默认分支后 2 个月
- 文档:鉴于其短期性和部署相关性质,此类功能标志无需在 GitLab 中所有功能标志 页面中记录
- 发布 issue:必须 使用 功能标志发布模板 创建发布 issue
使用方法
gitlab_com_derisk 功能标志的格式为 Feature.<state>(:<dev_flag_name>)。
在 GitLab Rails 控制台中启用和禁用:
# 为实例启用:
Feature.enable(:<dev_flag_name>)
# 为实例禁用:
Feature.disable(:<dev_flag_name>)
# 为特定项目启用:
Feature.enable(:<dev_flag_name>, Project.find(<project id>))
# 为特定项目禁用:
Feature.disable(:<dev_flag_name>, Project.find(<project id>))检查 gitlab_com_derisk 功能标志状态:
# 检查功能标志是否启用
Feature.enabled?(:dev_flag_name)
# 检查功能标志是否禁用
Feature.disabled?(:dev_flag_name)wip 类型
某些功能复杂,需通过多个 MR 实现。在完全实现之前,需对所有用户隐藏。此时,wip(“Work In Progress” 工作中)功能标志允许将所有更改合并到主分支,而无需实际使用该功能。
功能完成后,可根据功能向客户展示/文档化的方式,将功能标志类型更改为 gitlab_com_derisk 或 beta。
约束条件
default_enabled:必须 设为false。如需要,功能完成后可更改为 beta 类型。- 最大生命周期:合并到默认分支后 4 个月
- 文档:鉴于主要用于隐藏未完成代码,此类功能标志无需在 GitLab 中所有功能标志 页面中记录
- 发布 issue:可能无需发布 issue,因为
wip功能标志应在启用前转换为其他类型
使用方法
# 检查功能标志是否启用
Feature.enabled?(:my_wip_flag, project)
# 检查功能标志是否禁用
Feature.disabled?(:my_wip_flag, project)
# 将功能标志推送到前端
push_frontend_feature_flag(:my_wip_flag, project)beta 类型
我们可能 无法确信能在当前形式下为每个设计用例扩展、支持和维护功能(示例)。
也存在功能尚未完善到可视为 MVC 的情况。此时提供标志可让工程师和客户在功能性能足够前禁用它。
约束条件
default_enabled:可设为true以便向所有人"发布" beta 功能,并在可扩展性问题发生时禁用(理想情况下仅应在特定自托管安装中为此原因禁用)- 最大生命周期:合并到默认分支后 6 个月
- 文档:此类功能标志 必须 在 GitLab 中所有功能标志 页面中记录
- 发布 issue:必须 使用 功能标志发布模板 创建发布 issue
使用方法
# 检查功能标志是否启用
Feature.enabled?(:my_beta_flag, project)
# 检查功能标志是否禁用
Feature.disabled?(:my_beta_flag, project)
# 将功能标志推送到前端
push_frontend_feature_flag(:my_beta_flag, project)ops 类型
ops 功能标志是长期功能标志,控制 GitLab 产品行为的操作方面。例如,可能影响性能的功能标志,如 Sidekiq 工作进程行为。
请注意,使用此类型应遵循不引入实例/组/项目/用户设置的决定。
尽管 ops 类型标志无生命周期限制,但必须每 12 个月评估其是否仍需保留。
约束条件
default_enabled:大多数情况下应设为false,仅在解决临时可扩展性问题或帮助调试生产问题时启用。- 最大生命周期:无限制,但必须每 12 个月评估一次
- 文档:此类功能标志 必须 在 GitLab 中所有功能标志 页面中记录,并关联描述其使用场景的操作运行手册
- 发布 issue:可能无需发布 issue,因为启用/禁用时间难以预测
使用方法
# 检查功能标志是否启用
Feature.enabled?(:my_ops_flag, project)
# 检查功能标志是否禁用
Feature.disabled?(:my_ops_flag, project)
# 将功能标志推送到前端
push_frontend_feature_flag(:my_ops_flag, project)experiment 类型
experiment 功能标志用于 GitLab.com 上的 A/B 测试。
experiment 功能标志应符合与 beta 功能标志相同的标准,但接口略有不同。实验功能标志应使用 实验跟踪模板 创建发布 issue。更多信息请参阅 实验指南。
约束条件
default_enabled:必须 设为false。- 最大生命周期:合并到默认分支后 6 个月
worker 类型
worker 功能标志是特殊的 ops 标志,允许控制 Sidekiq 工作进程行为,例如延迟 Sidekiq 作业。
worker 功能标志可能没有 YAML 定义,因为名称可使用工作进程名称动态生成,例如 run_sidekiq_jobs_AuthorizedProjectsWorker。使用 worker 类型功能标志的示例可在 延迟 Sidekiq 作业 中找到。
(已弃用)development 类型
development 类型已弃用,推荐使用 gitlab_com_derisk、wip 和 beta 功能标志类型。
功能标志定义和验证
在开发 (RAILS_ENV=development) 或测试 (RAILS_ENV=test) 环境中,所有功能标志使用均被严格验证。
此过程旨在确保代码库中功能标志使用的一致性。所有功能标志 必须:
- 已知:仅使用明确定义的功能标志(
experiment、worker和undefined类型除外)。 - 不重复定义:必须在 FOSS 或 EE 中定义,但不能同时定义。
- 对于无定义文件的功能标志,在所有调用中使用有效的
type:。 - 拥有所有者。
GitLab 已知的功能标志在以下 YAML 文件中自文档化:
每个功能标志在单独的 YAML 文件中定义,包含多个字段:
| 字段 | 必需 | 描述 |
|---|---|---|
name |
是 | 功能标志名称。 |
description |
是 | 功能标志原因的简短描述。 |
type |
是 | 功能标志类型。 |
default_enabled |
是 | 功能标志的默认状态。 |
introduced_by_url |
是 | 引入功能标志的合并请求 URL。 |
milestone |
是 | 创建功能标志的里程碑。 |
group |
是 | 拥有功能标志的 组。 |
feature_issue_url |
否 | 原始功能 issue 的 URL。 |
rollout_issue_url |
否 | 覆盖功能标志发布的 issue URL。 |
log_state_changes |
否 | 用于记录功能标志的状态 |
在 RAILS_ENV=production 环境中运行时,所有验证均被跳过。
创建新功能标志
GitLab Pages 使用 不同流程 处理功能标志。
GitLab 代码库提供 bin/feature-flag 工具,用于创建新功能标志定义。
该工具会询问新功能标志的多个问题,然后在 config/feature_flags 或 ee/config/feature_flags 中创建 YAML 定义。
仅在开发或测试环境中运行时,有 YAML 定义文件的功能标志才能使用。
$ bin/feature-flag my_feature_flag
>> 指定功能标志类型
?> beta
您选择了 'beta' 类型
>> 从以下列表中选择功能标志所属的组标签:
1. group::group1
2. group::group2
?> 2
您选择了 'group::group2'
>> 原始功能 issue 的 URL(留空跳过):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/435435
>> 引入功能标志的 MR URL(留空跳过,让 Danger 在 MR 中直接提供建议):
?> https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023
>> 功能标志 DRI 的用户名(留空跳过):
?> bob
>> 这是仅 EE 功能吗(留空跳过):
?> [Return]
>> 按任意键并粘贴您复制到剪贴板的 issue 内容!🚀
?> [Return 自动打开 "New issue" 页面,您只需粘贴内容]
>> 发布 issue 的 URL(留空跳过):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/437162
create config/feature_flags/beta/my_feature_flag.yml
---
name: my_feature_flag
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435435
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437162
milestone: '16.9'
group: group::composition analysis
type: beta
default_enabled: false所有新引入的功能标志必须 默认禁用。
在功能标志后开发和合并的功能不应包含变更日志条目。条目应在移除功能标志的合并请求中添加,或在功能标志默认值设为启用的合并请求中添加。如果功能包含任何数据库迁移,应 包含数据库变更的变更日志条目。
要创建仅用于 EE 的功能标志,添加 --ee 标志:bin/feature-flag --ee
命名新标志
为新功能标志命名时,考虑以下指南:
-
描述功能标志所控制的功能
- 长而描述性的名称优于简短但令人困惑的名称。
-
避免使用表示功能状态/阶段的名称,如
_mvc、_alpha、_beta等 -
使用蛇形命名法(
my_cool_feature_flag)。 -
避免在名称中使用
disable,以避免需要思考(或 文档化)双重否定。考虑以hide_、remove_或disallow_开头。在软件工程中,这被称为 “布尔变量的负向命名”。
但我们无法完全禁止负向词汇,以便引入 默认禁用 的标志,通过将其移到标志后移除功能,或 按 actor 选择性禁用标志。
主分支(main)损坏风险
功能标志 必须 在引入它们的合并请求中使用。否则会导致 主分支损坏,因为 rspec:feature-flags 任务仅在 master 分支上运行。
可选添加 .patch 文件自动移除功能标志
gitlab-housekeeper 可使用 DeleteOldFeatureFlags keep 自动移除您的功能标志代码。该工具将定期运行并自动清理代码中的旧功能标志。
为使该工具自动移除代码中功能标志的使用,可在功能标志 YAML 文件旁添加 .patch 文件。文件名应完全相同,但扩展名使用 .patch 而非 .yml。
例如,可为 config/feature_flags/beta/my_feature_flag.yml 创建补丁文件:
- 确保您有干净的 Git 工作目录。
- 删除
config/feature_flags/beta/my_feature_flag.yml。 - 本地编辑代码以移除所有
my_feature_flag的使用,仿佛该功能标志已启用且功能正在推进。 - 运行
git diff > config/feature_flags/beta/my_feature_flag.patch。如果您的功能标志不是beta标志,请确保补丁文件与定义功能标志的 YAML 文件在同一目录中。 - 撤销删除
config/feature_flags/beta/my_feature_flag.yml - 撤销您为移除功能标志使用而修改的文件
- 将补丁文件提交到您添加功能标志的分支
将来 gitlab-housekeeper 将通过应用此补丁自动清理您的功能标志。
列出所有功能标志
要使用 ChatOps 将环境中所有功能标志输出到 Slack,可使用 run feature list 命令。例如:
/chatops run feature list --dev
/chatops run feature list --staging切换功能标志
有关切换功能标志的更多信息,请参阅 发布变更。
删除功能标志
有关删除功能标志的更多信息,请参阅 清理功能标志。
将 ops 功能标志迁移到应用设置
回填应用设置并在代码中使用设置的更改必须在同一里程碑中合并。
将 ops 功能标志迁移到应用设置:
- 在应用设置中,创建或识别现有的
JSONB列来存储设置。 - 应用设置默认值应与功能标志 YAML 定义中的
default_enabled:匹配 - 编写迁移来回填该列。这允许选择退出默认行为的实例保持相同状态。避免在迁移中使用
Feature.enabled?或Feature.disabled?。使用Gitlab::Database::MigrationHelpers::FeatureFlagMigratorHelpers迁移助手。这些助手仅迁移明确设为true或false的功能标志。如果功能标志为百分比或特定 actor 设置,将使用默认值。 - 在 管理员 区域创建设置以启用或禁用功能。
- 在各处用应用设置替换功能标志。
- 更新所有相关文档页面。如果前端变更在后续里程碑中合并,您应添加关于如何使用 应用设置 API 或 Rails 控制台更新设置的文档。
JSONB 列的迁移示例:
# 在移除前从功能标志定义 YAML 复制 default_enabled
DEFAULT_ENABLED = true
def up
up_migrate_to_jsonb_setting(feature_flag_name: :my_flag_name,
setting_name: :my_setting,
jsonb_column_name: :settings,
default_enabled: DEFAULT_ENABLED)
end
def down
down_migrate_to_jsonb_setting(setting_name: :my_setting, jsonb_column_name: :settings)
end布尔列的迁移示例:
# 在移除前从功能标志定义 YAML 复制 default_enabled
DEFAULT_ENABLED = true
def up
up_migrate_to_setting(feature_flag_name: :my_flag_name,
setting_name: :my_setting,
default_enabled: DEFAULT_ENABLED)
end
def down
down_migrate_to_setting(setting_name: :my_setting, default_enabled: DEFAULT_ENABLED)
end使用功能标志开发
在 GitLab 代码库中使用功能标志主要有两种方式:
后端
功能标志接口在 lib/feature.rb 中定义。
该接口提供一组方法来检查功能标志是否启用或禁用:
if Feature.enabled?(:my_feature_flag, project)
# 功能标志启用时执行代码
else
# 功能标志禁用时执行代码
end
if Feature.disabled?(:my_feature_flag, project)
# 功能标志禁用时执行代码
end未配置功能标志的默认行为由 YAML 定义中的 default_enabled: 控制。
如果功能标志没有 YAML 定义,在开发或测试环境中将引发错误,在生产环境中返回 false。
对于没有定义文件的功能标志(仅允许 experiment、worker 和 undefined 类型),调用 Feature.enabled? 和 Feature.disabled? 时需传递其 type::
if Feature.enabled?(:experiment_feature_flag, project, type: :experiment)
# 功能标志启用时执行代码
end
if Feature.disabled?(:worker_feature_flag, project, type: :worker)
# 功能标志禁用时执行代码
end不要在应用加载时使用功能标志。例如,在 config/initializers/* 或类级别使用 Feature 类可能导致意外错误。此错误发生是因为功能标志适配器可能依赖的数据库在加载时不存在(尤其是全新安装时)。在调用方检查数据库是否存在并不推荐,因为某些适配器根本不需要数据库(例如 HTTP 适配器)。功能标志设置检查必须在 Feature 命名空间中抽象。此方法还要求功能标志变更时重新加载应用。您必须要求 SRE 在生产环境中重新加载 Web/API/Sidekiq 队列,这需要时间完全发布/回滚变更。因此,请改用环境变量(例如 ENV['YOUR_FEATURE_NAME'])或 gitlab.yml。
应避免的模式示例:
class MyClass
if Feature.enabled?(:...)
new_process
else
legacy_process
end
end递归检测
当存在许多功能标志时,并不总是清楚它们在何处被调用。避免循环,即一个功能标志的评估需要其他功能标志的评估。如果发生循环,将被中断并返回默认值。
为使递归检测正常工作,始终通过 Feature::enabled? 访问功能值,避免低级使用 Feature::get。发生这种情况时,我们会将 Feature::RecursionError 异常跟踪到错误跟踪器。
前端
当使用功能标志控制 UI 元素时,确保对任何底层后端代码也使用功能标志。这确保在启用功能之前绝对无法使用该功能。
使用 push_frontend_feature_flag 方法,该方法对所有继承自 ApplicationController 的控制器可用。您可以使用此方法公开功能标志的状态,例如:
before_action do
# 建议按项目或用户范围限定,例如
push_frontend_feature_flag(:vim_bindings, project)
end
def index
# ...
end
def edit
# ...
end然后可以在 JavaScript 中按如下方式检查功能标志状态:
if ( gon.features.vimBindings ) {
// ...
}JavaScript 中功能标志的名称始终为驼峰式,因此检查 gon.features.vim_bindings 将无效。
有关如何在 Vue 组件中访问功能标志的详细信息,请参阅 Vue 指南。
对于没有定义文件的功能标志(仅允许 experiment、worker 和 undefined 类型),调用 push_frontend_feature_flag 时需传递其 type::
before_action do
push_frontend_feature_flag(:vim_bindings, project, type: :experiment)
end功能标志 Actor
强烈建议将 actor 与功能标志结合使用。 Actor 提供了一种简单的方法,仅为给定项目、组或用户启用功能标志。这使得调试更容易,例如,您可以根据 actor 过滤日志和错误。这也使得可以先在 gitlab-org 或 gitlab-com 组上启用功能,而其他用户不受影响。
Actor 还提供了一种简单的方法以粘性方式按百分比发布功能。如果 1% 的发布为特定 actor 启用了功能,该 actor 将在 10%、50% 和 100% 发布时继续拥有该功能。
GitLab 支持以下功能标志 actor:
User模型Project模型Group模型- 当前请求
actor 是 Feature.enabled? 调用的第二个参数。例如:
Feature.enabled?(:feature_flag, project)包含 FeatureGate 的模型具有 .actor_from_id 类方法。
如果您拥有模型的 ID 且除了检查功能标志状态外不需要该模型,可以使用 .actor_from_id 检查功能标志状态,而无需执行数据库查询检索模型。
# 不良 -- 执行了不必要的查询
Feature.enabled?(:feature_flag, Project.find(project_id))
# 良好 -- 无项目查询
Feature.enabled?(:feature_flag, Project.actor_from_id(project_id))
# 良好 -- 功能标志检查后使用项目模型
project = Project.find(project_id)
return unless Feature.enabled?(:feature_flag, project)
project.update!(column: value)有关如何使用 ChatOps 选择性启用或禁用 GitLab 提供的环境(如暂存和生产)中的功能标志,请参阅 使用 ChatOps 启用和禁用功能标志。
标志状态不会从组继承到其子组或项目。
如果需要标志状态在整个组层次结构中保持一致,建议使用顶级组作为 actor。
可通过在任何组或项目上调用 #root_ancestor 找到该组。
Feature.enabled?(:feature_flag, group.root_ancestor)混合 Actor 类型
通常,对于特定功能标志的所有 Feature.enabled? 调用,应仅使用一种 actor 类型,不要混合不同 actor 类型。
混合 actor 类型可能导致功能以不一致的方式启用或禁用,从而引发错误。例如,如果在控制器级别使用组 actor 检查标志,在服务级别使用用户 actor 检查标志,则功能可能在同一请求的不同点同时启用和禁用。
在某些情况下,如果您知道不会导致不一致结果,可以安全地混合 actor 类型。例如, webhook 可关联到组或项目,因此 webhook 的功能标志可利用此点使用同一功能标志为组 webhook 和项目 webhook 发布功能。
如果您需要使用不同的 actor 类型且无法在您的情况下安全混合,应为每种 actor 类型使用单独的标志。例如:
Feature.enabled?(:feature_flag_group, group)
Feature.enabled?(:feature_flag_user, user)实例 Actor
实例级功能标志仅应用于与整个实例绑定的功能。始终优先使用其他 actor。
在某些情况下,您可能希望为整个实例启用功能标志,而不基于 actor。一个很好的例子是管理员设置,因为基于组或项目启用功能标志是不可能的(两者均为 undefined)。
用户 actor 会引起混淆,因为功能标志可能为非管理员用户启用,却为管理员用户禁用。
相反,可以使用 :instance 符号作为 Feature.enabled? 的第二个参数,它将被清理为 GitLab 实例。
Feature.enabled?(:feature_flag, :instance)当前请求 Actor
不建议使用时间百分比发布,因为每次调用可能返回不一致的结果。
建议使用当前请求作为 actor。
# 不良
Feature.enable_percentage_of_time(:feature_flag, 40)
Feature.enabled?(:feature_flag)
# 良好
Feature.enable_percentage_of_actors(:feature_flag, 40)
Feature.enabled?(:feature_flag, Feature.current_request)当使用当前请求作为 actor 时,功能标志应在请求上下文中返回相同值。
由于当前请求 actor 使用 SafeRequestStore 实现,我们应在以下内容中获得一致的功能标志值:
- Rack 请求
- Sidekiq 工作进程执行
- ActionCable 工作进程执行
要将现有功能从时间百分比迁移到当前请求 actor,建议创建新功能标志。
这是因为很难控制现有 percentage_of_time 值、代码变更部署以及切换到使用 percentage_of_actors 之间的时间。
使用 actor 验证生产环境
不建议将生产环境用作测试环境。使用我们的测试环境测试尚未投入生产的功能。
虽然暂存环境提供了在类似生产环境中测试功能的方式,但它不允许比较特定于生产环境的发布前后性能指标。在生产环境中拥有一个启用开发功能标志的项目可能很有用,允许像 Sitespeed 这样的工具揭示新代码在功能标志下的指标。
如果您已经在 Sitespeed 中跟踪旧代码库,此方法尤其有用,使您能够准确比较功能标志发布前后的性能。
将其他对象启用为 Actor
要使用基于 actor 的功能门控,模型需要响应 flipper_id。例如,为 Foo 模型启用:
class Foo < ActiveRecord::Base
include FeatureGate
end只有包含 FeatureGate 或暴露 flipper_id 方法的模型才能用作 Feature.enabled? 的 actor。
许可功能的功能标志
您不能使用与许可功能名称相同的功能标志,因为这会导致命名冲突。这已 广泛讨论并移除,因为它令人困惑。
要检查许可功能,请在不同名称下添加专用功能标志并显式检查,例如:
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 成员启用功能 |
可通过组名启用功能组:
Feature.enable(:feature_flag_name, :gitlab_team_members)本地控制功能标志
在 Rails 控制台中
在 Rails 控制台(rails c)中,输入以下命令启用功能标志:
Feature.enable(:feature_flag_name)类似地,以下命令禁用功能标志:
Feature.disable(:feature_flag_name)您还可以为特定 gate 启用功能标志:
Feature.enable(:feature_flag_name, Project.find_by_full_path("root/my-project"))从 Rails 控制台手动启用或禁用功能标志时,其默认值会被覆盖。
当更改标志的 default_enabled 属性时,这可能会引起混淆。
要将功能标志重置为默认状态:
Feature.remove(:feature_flag_name)在浏览器中
访问 http://gdk.test:3000/rails/features 可在本地管理功能标志。
日志记录
如果满足以下任一条件,功能标志的使用和状态将被记录:
- 功能标志定义中
log_state_changes设为true。 milestone指定的里程碑大于或等于当前 GitLab 版本。
当功能标志状态被记录时,可在 Kibana 中使用 "json.feature_flag_states": "feature_flag_name:1" 或 "json.feature_flag_states": "feature_flag_name:0" 条件识别。
您可以在 此链接 中查看示例。
仅 20% 的请求记录功能标志状态。这由 feature_flag_state_logs 功能标志控制。
变更日志
我们希望避免在功能最终用户无法直接访问(例如:使用功能的能力)或间接访问(例如:利用后台作业的能力、性能改进或数据库迁移更新)时引入变更日志。
-
数据库迁移始终由最终用户间接访问,因为自托管客户需要在升级前了解数据库变更。因此,它们 应 有变更日志条目。
-
任何在 默认禁用 功能标志后的变更 不应 有变更日志条目。
-
任何在 默认启用 功能标志后的变更 应 有变更日志条目。
-
更改功能标志本身(标志移除、默认启用设置)应 有 变更日志条目。
使用流程图确定变更日志条目类型。flowchart LR FDOFF(Flag is currently<br>'default: off') FDON(Flag is currently<br>'default: on') CDO{Change to<br>'default: on'} ACF(added / changed / fixed / '...') RF{Remove flag} RF2{Remove flag} RC(removed / changed) OTHER(other) FDOFF -->CDO-->ACF FDOFF -->RF RF-->|Keep new code?| ACF RF-->|Keep old code?| OTHER FDON -->RF2 RF2-->|Keep old code?| RC RF2-->|Keep new code?| OTHER -
功能标志的变更日志应描述功能而非标志,除非默认启用功能标志被移除且保留新代码(上述流程图中的
other)。 -
功能标志也可用于发布错误修复或维护工作。在此场景中,变更日志必须与之相关,例如;
fixed或other。
测试中的功能标志
在代码库中引入功能标志会创建应测试的额外代码路径。
强烈建议为受功能标志影响的所有代码(启用 和 禁用 状态)包含自动化测试,以确保功能正常工作。如果未为两种状态包含自动化测试,应在部署到生产前对未测试代码路径关联的功能进行手动测试。
在测试环境中使用时,所有功能标志默认启用。
可在 spec/spec_helper.rb 文件 中将标志默认禁用。
添加内联注释说明为何需要禁用该标志。如果可能,可附加 issue URL 作为参考。
要在测试中禁用功能标志,使用 stub_feature_flags 助手。例如,在测试中全局禁用 ci_live_trace 功能标志:
stub_feature_flags(ci_live_trace: false)
Feature.enabled?(:ci_live_trace) # => false测试两种路径的常见模式如下:
it 'ci_live_trace works' do
# 测试假设 ci_live_trace 在测试中默认启用
Feature.enabled?(:ci_live_trace) # => true
end
context 'when ci_live_trace is disabled' do
before do
stub_feature_flags(ci_live_trace: false)
end
it 'ci_live_trace does not work' do
Feature.enabled?(:ci_live_trace) # => false
end
end如果您希望设置测试,使功能标志仅对某些 actor 启用而对其他 actor 禁用,可以在传递给助手的选项中指定。例如,为特定项目启用 ci_live_trace 功能标志:
project1, project2 = build_list(:project, 2)
# 功能仅对 project1 启用
stub_feature_flags(ci_live_trace: project1)
Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => falseFlipperGate 的行为如下:
- 您可以为指定 actor 启用覆盖。
- 您可以禁用(移除)指定 actor 的覆盖,回退到默认状态。
- 无法明确指定 actor 被禁用的模型。
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true
Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => truehave_pushed_frontend_feature_flags
使用 have_pushed_frontend_feature_flags 测试 push_frontend_feature_flag 是否已将功能标志添加到 HTML。
例如,
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 vs Feature.enable*
在测试环境中,推荐使用 stub_feature_flags 启用功能标志。此方法为简单用例提供了简单且描述清晰的接口。
然而,在某些情况下需要测试更复杂的行为,例如功能标志的百分比发布。这可以通过 .enable_percentage_of_time 或 .enable_percentage_of_actors 完成:
# 良好:功能需要显式禁用,因为未定义时默认启用
stub_feature_flags(my_feature: false)
stub_feature_flags(my_feature: true)
stub_feature_flags(my_feature: project)
stub_feature_flags(my_feature: [project, project2])
# 不良
Feature.enable(:my_feature_2)
# 良好:为 50% 的时间启用 my_feature
Feature.enable_percentage_of_time(:my_feature_3, 50)
# 良好:为 50% 的 actor/gate/thing 启用 my_feature
Feature.enable_percentage_of_actors(:my_feature_4, 50)每个具有定义状态的功能标志在测试执行期间会持久化:
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存根 Actor
当您希望仅为特定 actor 启用功能标志时,可以存根其表示。传递给 Feature.enabled? 和 Feature.disabled? 的 gate 必须是包含 FeatureGate 的对象。
在规范中,您可以使用 stub_feature_flag_gate 方法快速创建自定义 actor:
gate = stub_feature_flag_gate('CustomActor')
stub_feature_flags(ci_live_trace: gate)
Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, gate) # => true在测试中控制功能标志引擎
我们在测试环境中的 Flipper 引擎以内存模式 Flipper::Adapters::Memory 运行。
production 和 development 模式使用 Flipper::Adapters::ActiveRecord。
您可以控制使用 Flipper::Adapters::Memory 还是 ActiveRecord 模式。
stub_feature_flags: true(默认和首选)
在此模式下,Flipper 配置为使用 Flipper::Adapters::Memory 并将所有功能标志标记为默认启用并在首次使用时持久化。
确保功能标志下的行为在某些非特定上下文中未被测试。
stub_feature_flags: false
这禁用了内存存根的 flipper,并使用 Flipper::Adapters::ActiveRecord 模式,该模式由 production 和 development 使用。
您应仅在确实需要测试 Flipper 与 ActiveRecord 交互的方面时使用此模式。
端到端(QA)测试
在端到端(QA)测试中切换功能标志的方式不同。端到端测试框架无法直接访问 Rails 或数据库,因此无法使用 Flipper。相反,它使用 公共 API。每个端到端测试可以在 测试期间启用或禁用功能标志。或者,当您 从 GitLab 仓库的 qa 目录运行测试 或 通过 GitLab QA 运行测试 时,可以在一个或多个测试前启用或禁用功能标志。
如上所述,端到端测试中默认不启用功能标志。
这意味着端到端测试将使用源代码中实现的默认状态运行,或使用被测 GitLab 实例上功能标志的当前状态运行,除非测试明确编写为启用/禁用功能标志。
当功能标志在暂存或 GitLab.com 上更改时,将向 #e2e-run-staging 或 #e2e-run-production 频道发布 Slack 消息,通知流水线分类 DRI,以便他们更容易确定任何故障是否与功能标志更改相关。但是,如果您正在处理变更,可以通过 确认端到端测试在功能标志启用时通过 来帮助避免意外故障。
使用功能标志控制 Sidekiq 工作进程行为
具有 worker 类型 的功能标志可用于控制 Sidekiq 工作进程的行为。
延迟 Sidekiq 作业
禁用时,格式为 run_sidekiq_jobs_{WorkerName} 的功能标志通过在稍后时间调度作业来延迟工作进程的执行。所有工作进程默认启用此功能标志。
在争议性行为(如工作进程实例耗尽基础设施资源(数据库和数据库连接池))的事故期间,延迟作业可能很有用。实现在 SkipJobs Sidekiq 服务器中间件 中。
只要功能标志禁用,作业就会无限期延迟。在工作进程被认为可以安全处理后,必须移除功能标志。
设为 false 时,100% 的作业被延迟。当您希望恢复处理时,可以使用 时间百分比 发布。例如:
# 不运行任何作业,延迟所有 100% 的作业
/chatops run feature set run_sidekiq_jobs_SlowRunningWorker false
# 仅运行 10% 的作业,延迟 90% 的作业
/chatops run feature set run_sidekiq_jobs_SlowRunningWorker 10
# 运行 50% 的作业,延迟 50% 的作业
/chatops run feature set run_sidekiq_jobs_SlowRunningWorker 50
# 恢复正常运行所有作业
/chatops run feature delete run_sidekiq_jobs_SlowRunningWorker丢弃 Sidekiq 作业
与其 延迟作业,作业可通过启用 drop_sidekiq_jobs_{WorkerName} 功能标志完全丢弃。当您确定作业不需要在未来处理,因此可以安全丢弃时,使用此功能标志。
# 丢弃所有作业
/chatops run feature set drop_sidekiq_jobs_SlowRunningWorker true
# 正常处理作业
/chatops run feature delete drop_sidekiq_jobs_SlowRunningWorker丢弃功能标志 (drop_sidekiq_jobs_{WorkerName}) 优先于延迟功能标志 (run_sidekiq_jobs_{WorkerName})。当启用 drop_sidekiq_jobs 且禁用 run_sidekiq_jobs 时,作业被完全丢弃。