Gitaly developers guide
Gitaly developers guide
Gitaly是 GitLab Rails,Workhorse 和 GitLab Shell 使用的高级 Git RPC 服务.
Deep Dive
在 2019 年 5 月,Bob Van Landuyt 主持了有关 GitLab 的Gitaly 项目以及如何以 Ruby 的形式进行的 Deep Dive(仅限 GitLab 团队成员: https://gitlab.com/gitlab-org/create-stage/issues/1 : //gitlab.com/gitlab-org/create-stage/issues/1 )开发人员,以便与将来可能在代码库这一部分工作的任何人共享他的特定领域知识.
您可以在 YouTube 上找到录音 ,在Google 幻灯片和PDF 中找到幻灯片 .
截至 GitLab 11.11 为止,本次深入介绍中涉及的所有内容都是准确的,尽管自那时以来特定细节可能有所更改,但它仍应作为一个很好的介绍.
Beginner’s guide
首先阅读 Gitaly 存储库的Gitaly 贡献初学者指南 . 它描述了如何设置 Gitaly,Gitaly 的各个组件以及它们的作用以及如何运行其测试套件.
Developing new Git features
要读取或写入 Git 数据,必须向 Gitaly 发出请求. 这意味着如果您要开发一项新功能,则需要lib/gitlab/git尚不可用的数据,则必须对 Gitaly 进行更改.
这是一个尚未明确定义的新过程. 如果您想提供 Git 功能,但遇到困难,请与 Gitaly 团队或
@jacobvosmaer-gitlab.
"新功能"是指lib/gitlab/git中从lib/gitlab/git外部调用的任何方法或类. 有关从lib/gitlab/git内部调用的新方法,请参见下面的"修改现有 Git 功能".
在lib/gitlab/git之外的任何地方,都不应有任何通过磁盘访问(例如 Rugged, git , rm -rf )接触 Git 存储库的新代码.
添加新的 Gitaly 功能的过程是:
- 探索/原型制作
 - 在
gitaly-proto设计和创建新的 Gitaly RPC - 发行新版本的
gitaly-proto - 用 Go 或 Ruby 在 Gitaly 中编写 RPC 的实现和测试
 - release a new version of Gitaly
 - 在调用新 Gitaly RPC 的 GitLab CE / EE,GitLab Workhorse 或 GitLab Shell 中编写客户端代码
 
这些步骤经常重叠. 在测试和开发过程中,可以使用未发行的 Gitaly 和gitaly-proto版本.
- 有关使用未发布的协议编写服务器端代码的说明,请参见Gitaly 存储库 .
 - 有关使用修改后的 Gitaly 版本运行 GitLab CE 测试的说明,请参见下文 .
 - 在 GDK 中运行
gdk install并重新启动gdk run(或gdk run app)以使用本地修改的 Gitaly 版本进行开发 
gitaly-ruby
可以在gitaly-ruby使用 Ruby 代码在gitaly-ruby实现和测试 RPC. 这应该使对不喜欢编写 Go 代码的开发人员的贡献更加容易.
Gitaly 存储库中提供了有关此方法的文档 .
Gitaly-Related Test Failures
如果您的测试套件因 Gitaly 问题而失败,请首先尝试运行:
rm -rf tmp/tests/gitaly 
在 RSpec 测试期间,Gitaly 实例会将日志写入gitlab/log/gitaly-test.log .
Legacy Rugged code
尽管 Gitaly 可以处理所有 Git 访问,但许多 GitLab 客户仍在 NFS 上运行 Gitaly. 由于 N + 1 个 Gitaly 调用和其他原因,用于 Git 调用的传统 Rugged 实现可能比 Gitaly RPC 更快. 有关更多详细信息,请参见问题 .
在 GitLab 消除了大多数这些低效率问题或对 Git 数据停止使用 NFS 之前,可以通过功能标志启用某些最常用 RPC 的 Rugged 实现:
rugged_find_commitrugged_get_tree_entriesrugged_tree_entryrugged_commit_is_ancestorrugged_commit_tree_entryrugged_list_commits_by_oid
方便的 Rake 任务可用于一起启用或禁用这些标志. 启用:
bundle exec rake gitlab:features:enable_rugged 
禁用:
bundle exec rake gitlab:features:disable_rugged 
此代码大部分存在于lib/gitlab/git/rugged_impl目录中.
注意:除非与Gitaly Team明确讨论,否则您无需添加或修改与 Rugged 相关的代码. 此代码不适用于 GitLab.com 或其他不使用 NFS 的 GitLab 实例.
TooManyInvocationsError errors
在开发和测试过程中,您可能会遇到Gitlab::GitalyClient::TooManyInvocationsError故障. 该GitalyClient将试图通过时 Gitaly 被称为 30 倍以上在单个的 Rails 请求或 Sidekiq 执行提高此错误来阻止对潜在的 n + 1 点的问题.
作为临时措施,导出GITALY_DISABLE_REQUEST_LIMITS=1以消除该错误. 这将在开发环境中禁用 n + 1 检测.
请在 GitLab CE 或 EE 存储库中提出问题,以报告该问题. 包括标签〜Gitaly〜性能〜"技术债务". 请确保问题包含完整的堆栈跟踪和TooManyInvocationsError错误消息. 如果可能,还包括任何已知的失败测试.
找出 n + 1 问题的根源. 通常,这将是一个循环,导致对数组中的每个元素调用 Gitaly. 如果您无法找出问题所在,请与Gitaly 小组成员联系以寻求帮助.
找到源之后,将其包装在allow_n_plus_1_calls块中,如下所示:
# n+1: link to n+1 issue
Gitlab::GitalyClient.allow_n_plus_1_calls do
  # original code
  commits.each { |commit| ... }
end 
一旦将代码包装在此块中,将从 n + 1 检测中排除此代码路径.
Request counts
提交和其他 Git 数据现在通过 Gitaly 获取. 可以像数据库一样批处理这些提取. 这样可以提高客户端以及 Gitaly 本身以及用户的性能. 为了保持性能稳定并防止性能下降,可以对 Gitaly 通话进行计数,并可以对通话计数进行测试. 这需要设置:request_store标志.
describe 'Gitaly Request count tests' do
  context 'when the request store is activated', :request_store do
    it 'correctly counts the gitaly requests made' do
      expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(10)
    end
  end
end 
Running tests with a locally modified version of Gitaly
通常,GitLab CE / EE 测试在tmp/tests/gitaly使用 Gitaly 的本地克隆,固定在GITALY_SERVER_VERSION指定的版本上. GITALY_SERVER_VERSION文件还支持分支和 SHA,以在https://gitlab.com/gitlab-org/gitaly 中使用自定义提交.
注:通过引入自动部署 Gitaly 的,格式GITALY_SERVER_VERSION用总括的语法一致. 它不再支持=revision ,它将评估文件内容作为 Git 引用(分支或 SHA),仅当它与 semver 匹配时才在v .
如果要针对修改后的 Gitaly 版本在本地运行测试,则可以用符号链接替换tmp/tests/gitaly . 这要快得多,因为这样可以避免每次运行rspec时都重新安装 Gitaly.
rm -rf tmp/tests/gitaly
ln -s /path/to/gitaly tmp/tests/gitaly 
在运行测试之前,请确保在本地 Gitaly 目录中运行make . 否则,Gitaly 将无法启动.
如果您在两次测试之间更改了本地 Gitaly,则需要再次手动运行make .
请注意,CI 测试不会使用您本地修改的 Gitaly 版本. 要在 CI 中使用自定义 Gitaly 版本,您需要按照本段开头所述更新 GITALY_SERVER_VERSION.
要使用其他 Gitaly 存储库(例如,如果您的更改出现在 fork 上),则可以在运行测试时指定GITALY_REPO_URL环境变量:
GITALY_REPO_URL=https://gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb 
如果您的 Gitaly 分支是私有的,则可以生成一个Deploy Token并在 URL 中指定它:
GITALY_REPO_URL=https://gitlab+deploy-token-1000:token-here@gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb 
要在 CI 中使用自定义的 Gitaly 存储库,例如,如果您希望 GitLab 分支始终使用自己的 Gitaly 分支,请将GITALY_REPO_URL设置为CI 环境变量 .
Use a locally modified version of Gitaly RPC client
如果要对 RPC 客户端进行更改,例如添加新端点或向现有端点添加新参数,请遵循Gitaly proto指南. 在推送具有更改的分支(例如new-feature-branch )之后:
将 Rails 的
Gemfile的gitaly行更改为:gem 'gitaly', git: 'https://gitlab.com/gitlab-org/gitaly.git', branch: 'new-feature-branch'- 运行
bundle install以使用修改后的 RPC 客户端. 
- 运行
 
Return to Development documentation
Wrapping RPCs in Feature Flags
以下是在功能标记后在 Gitaly 中选通新功能的步骤.
Gitaly
创建一个包范围的标志名:
var findAllTagsFeatureFlag = "go-find-all-tags"- 使用
featureflag包在代码中创建一个开关: 
if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { // go implementation } else { // ruby implementation }- 创建 Prometheus 指标:
 
var findAllTagsRequests = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "gitaly_find_all_tags_requests_total", Help: "Counter of go vs ruby implementation of FindAllTags", }, []string{"implementation"}, ) func init() { prometheus.Register(findAllTagsRequests) } if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { findAllTagsRequests.WithLabelValues("go").Inc() // go implementation } else { findAllTagsRequests.WithLabelValues("ruby").Inc() // ruby implementation }- Set headers in tests:
 
import ( "google.golang.org/grpc/metadata" "gitlab.com/gitlab-org/gitaly/internal/featureflag" ) //... md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"}) ctx = metadata.NewOutgoingContext(context.Background(), md) c, err = client.FindAllTags(ctx, rpcRequest) require.NoError(t, err)- 使用
 
GitLab Rails
通过设置功能标记在 Rails 控制台中进行测试:
注意:请注意标志的名称以及在 Rails 控制台中使用的标志的名称. 它们之间是有区别的(用下划线代替短划线,并且更改了名称前缀). 确保在所有标志
gitaly_加上gitaly_.Feature.enable('gitaly_go_find_all_tags')
Testing with GDK
为确保正确设置该标志并将其放入 Gitaly 中,可以使用 GDK 检查集成:
标志的状态必须是可观察的. 要检查它,您需要通过获取 Prometheus 指标来启用它: 1. 导航到 GDK 的根目录. 2. 确保您已经为 Gitaly 签出了正确的分支. 3. 使用
make gitaly-setup重新编译它,并使用gdk restart gitaly重新启动服务. 4. 确保您的设置正在运行:gdk status | grep praefectgdk status | grep praefect. 5. 检查使用了什么配置文件:cat ./services/praefect/run | grep praefect-config标志的cat ./services/praefect/run | grep praefect值 6. 在配置文件中取消注释prometheus_listen_addr并运行gdk restart gitaly.Make sure that the flag is not enabled yet: 1. 执行触发更改所需的任何操作(项目创建,提交提交,观察历史记录等). 2. 检查当前指标列表是否具有新的功能标记计数器:
curl --silent http://localhost:9236/metrics | grep go_find_all_tags- 一旦观察到新功能标志的度量标准并递增,就可以启用新功能:
 - 导航到 GDK 的根目录.
 - 启动一个 Rails 控制台:
 
bundle install && bundle exec rails console检查功能标志列表:
Feature :: Gitaly . server_feature_flags应该禁用
"gitaly-feature-go-find-all-tags"=>"false".启用它:
Feature . enable ( 'gitaly_go_find_all_tags' )- 退出 Rails 控制台并执行触发更改所需的任何操作(项目创建,提交提交,观察历史记录等).
 
通过观察该功能的度量来确认该功能已启用:
curl --silent http://localhost:9236/metrics | grep go_find_all_tags