Gitaly 和 Gitaly 集群

Gitaly 提供对 Git 仓库的高级 RPC 访问。极狐GitLab 使用它来读取和写入 Git 数据。

Gitaly 存在于每个极狐GitLab 安装实例中,并协调 Git 仓库的存储和检索。Gitaly 是:

  • 在单个 Omnibus GitLab 实例上运行的后台服务。
  • 根据扩展和可用性要求,分离到自己的实例并配置为完整的集群配置。

Gitaly 实现了客户端-服务器架构:

  • Gitaly 服务器是运行 Gitaly 本身的任何节点。
  • Gitaly 客户端是运行向 Gitaly 服务器发出请求的进程的任何节点。Gitaly 客户端也称为 Gitaly 消费者,包括:
    • 极狐GitLab Rails 应用程序
    • 极狐GitLab Shell
    • 极狐GitLab Workhorse
    • 极狐GitLab Elasticsearch Indexer
    • 极狐GitLab Zoekt Indexer
    • 极狐GitLab Agent for Kubernetes (KAS)

Gitaly 仅管理极狐GitLab 的 Git 仓库访问。其他类型的极狐GitLab 数据不能使用 Gitaly 访问。

极狐GitLab 通过配置的仓库存储访问仓库。每个新仓库都根据它们的配置权重存储在其中一个仓库存储中。每个仓库存储:

  • 使用存储路径直接访问仓库的 Gitaly 存储,其中每个仓库都存储在单个 Gitaly 节点上。所有请求都路由到此节点。
  • Gitaly 集群提供的一个虚拟存储,每个仓库可以存储在多个 Gitaly 节点上以实现容错。在 Gitaly 集群中:
    • 读取请求分布在多个 Gitaly 节点之间,可以提高性能。
    • 写请求被广播到仓库副本。

部署 Gitaly 集群之前

Gitaly 集群提供了容错的好处,但也带来了额外的设置和管理复杂性。 在部署 Gitaly 集群之前,请查看:

如果您还没有迁移到 Gitaly 集群,您有两个选择:

  • 一个分片的 Gitaly 实例。
  • Gitaly 集群。

如果您有任何问题,请联系技术支持。

已知问题

下表概述了影响 Gitaly 集群使用的当前已知问题。有关这些问题的当前状态,请参阅参考的议题和史诗。

问题 概述 如何规避
Gitaly Cluster + Geo - 重试失败同步的问题 如果在 Geo 次要站点上使用 Gitaly 集群,同步失败的仓库可能会在 Geo 尝试重新同步它们时继续失败。从此状态恢复需要支持人员的帮助才能运行手动步骤。 15.0 版本之前没有已知的解决方案。在 15.0 到 15.2 版本中,启用 gitaly_praefect_generated_replica_paths 功能标志。在 15.3 版本中,默认情况下启用功能标志。
由于升级后未应用迁移,Praefect 无法将数据插入数据库 如果数据库没有与已完成的迁移保持同步,则 Praefect 节点无法执行正常操作。 确保 Praefect 数据库已启动并在所有迁移完成的情况下运行(例如:/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate-status 应该显示所有应用迁移的列表)。考虑请求技术支持。
从正在运行的集群中的快照恢复 Gitaly 集群节点 因为 Gitaly 集群以一致的状态运行,引入一个落后的节点会导致集群无法协调节点数据和其他节点数据。 不要从备份快照中恢复单个 Gitaly 集群节点。如果您必须从备份中恢复,最好关闭极狐GitLab,同时对所有 Gitaly 集群节点进行快照,并进行 Praefect 数据库转储。
运行 Kubernetes、Amazon ECS 或相似系统的限制 不支持 Praefect(Gitaly Cluster),并且 Gitaly 具有已知的限制。 使用我们的引用架构。

快照备份和恢复限制

Gitaly 集群不支持快照备份。快照备份可能会导致 Praefect 数据库与磁盘存储不同步的问题。由于 Praefect 在还原期间如何重建 Gitaly 磁盘信息的复制元数据,我们建议使用官方备份和还原 Rake 任务。

如果您无法使用任何一种方法,请联系客户支持以获取恢复帮助。

如果您在 Gitaly 集群上遇到问题或限制该怎么办

请联系客户支持以立即获得恢复或恢复方面的帮助。

磁盘要求

Gitaly 和 Gitaly 集群需要快速的本地存储才能有效执行,因为它们是基于 I/O 的繁重进程。因此,我们强烈建议所有 Gitaly 节点都使用固态硬盘 (SSD)。

这些 SSD 的吞吐量至少应为:

  • 每秒 8,000 次输入/输出操作 (IOPS) 用于读取操作。
  • 2,000 IOPS 用于写入操作。

这些 IOPS 值是初始建议值,可能会根据环境工作负载的规模调整为更大或更小的值。如果您在云提供商上运行环境,请参阅他们的文档以了解如何正确配置 IOPS。

对于存储库数据,出于性能和一致性原因,Gitaly 和 Gitaly 集群仅支持本地存储。 不支持 NFS 或基于云的文件系统等替代方案。

直接访问仓库

极狐GitLab 不建议使用 Git 客户端或任何其他工具直接访问存储在磁盘上的 Gitaly 仓库,因为 Gitaly 正在不断改进和更改。这些改进可能会使您的预设无效,从而导致性能下降、不稳定甚至数据丢失。例如:

  • Gitaly 进行了优化,例如 info/refs 广告缓存,它依赖于 Gitaly 通过使用官方 gRPC 接口控制和监视对仓库的访问。
  • Gitaly 集群有一些优化,例如容错和分布式读取,它们依赖于 gRPC 接口和数据库来确定仓库状态。
caution 直接访问 Git 仓库需要您自担风险,并且不受支持。

Gitaly

下面显示了极狐GitLab 设置为使用对 Gitaly 的直接访问:

Shard example

在这个例子中:

  • 每个仓库都存储在三个 Gitaly 存储之一上:storage-1storage-2storage-3
  • 每个存储都由一个 Gitaly 节点提供服务。
  • 三个 Gitaly 节点将数据存储在其文件系统上。

Gitaly 架构

下面说明了 Gitaly 客户端-服务器架构:

flowchart TD subgraph Gitaly clients A[GitLab Rails] B[GitLab Workhorse] C[GitLab Shell] D[...] end subgraph Gitaly E[Git integration] end F[Local filesystem] A -- gRPC --> Gitaly B -- gRPC--> Gitaly C -- gRPC --> Gitaly D -- gRPC --> Gitaly E --> F

配置 Gitaly

Gitaly 预先配置了 Linux 软件包安装,默认配置适用于 20 RPS/1,000 用户。对于:

对于超过 2,000 活跃用户,我们建议使用 Gitaly 集群。

Gitaly 命令行

  • gitaly git 子命令引入于极狐GitLab 17.4。

gitaly 命令是一个能够为 Gitaly 管理员提供额外子命令的命令行工具。比如,Gitaly 命令行工具可以被用在:

对于其他子命令的更多信息,可以运行 sudo -u git -- /opt/gitlab/embedded/bin/gitaly --help

备份仓库

当使用非 Gitaly 外的工具进行仓库备份或同步时,当拷贝仓库数据时,您必须防止写入

Bundle URI

您可以使用 Gitaly bundle URIs。更多详情,可以查看 Bundle URIs 文档

Gitaly 集群

Git 存储是通过极狐GitLab 中的 Gitaly 服务提供的,对于极狐GitLab 的运行至关重要。当用户、仓库和活动的数量增加时,通过以下方式适当扩展 Gitaly 非常重要:

  • 在因资源耗尽降低 Git、Gitaly 和极狐GitLab 应用程序的性能之前,增加可供 Git 使用的 CPU 和内存资源。
  • 在因达到存储限制而导致写入操作失败之前,增加可用存储。
  • 消除单点故障以提高容错能力。如果服务降级会阻止您将更改部署到生产环境,应将 Git 视为关键任务。

Gitaly 可以在集群配置中运行:

  • 扩展 Gitaly 服务。
  • 增加容错能力。

在此配置中,每个 Git 仓库都可以存储在集群中的多个 Gitaly 节点上。

使用 Gitaly 集群通过以下方式提高容错能力:

  • 将写入操作复制到热备用 Gitaly 节点。
  • 检测 Gitaly 节点故障。
  • 自动将 Git 请求路由到可用的 Gitaly 节点。
note Gitaly 集群的技术支持仅限于专业版和旗舰版客户。

下面显示了极狐GitLab 设置为访问 storage-1,这是 Gitaly 集群提供的虚拟存储:

Cluster example

在这个例子中:

  • 存储库存储在名为 storage-1 的虚拟存储中。
  • 三个 Gitaly 节点提供 storage-1 访问:gitaly-1gitaly-2gitaly-3
  • 三个 Gitaly 节点在三个独立的哈希存储位置共享数据。
  • 复制系数3。每个仓库维护三个副本。

假设单个节点故障,Gitaly 集群的可用性目标是:

  • 恢复点目标 (RPO):小于 1 分钟。

    写入是异步复制的。任何尚未复制到新提升的主节点的写入都将丢失。

    强一致性 在某些情况下可以防止丢失。

  • 恢复时间目标 (RTO):小于 10 秒。 每个 Praefect 节点每秒运行一次运行状况检查来检测中断。故障转移需要在每个 Praefect 节点上连续进行十次失败的健康检查。

caution 如果发生完整的集群故障,则应执行灾难恢复计划。这会影响上面讨论的 RPO 和 RTO。

与 Geo 比较

Gitaly 集群和 Geo 都提供冗余。然而:

  • Gitaly Cluster 为数据存储提供容错能力,对用户是不可见的。用户不知道何时使用 Gitaly 集群。
  • Geo 为整个极狐GitLab 实例提供复制和灾难恢复。用户知道他们何时使用 Geo 进行复制。Geo 复制多种数据类型,包括 Git 数据。

下表概述了 Gitaly 集群和 Geo 之间的主要区别:

工具 节点 位置 延迟容忍度 故障转移 一致性 提供冗余
Gitaly 集群 多个 单个 不到 1 秒,理想情况下是个位数毫秒 自动 强一致性 Git 中的数据存储
Geo 多个 多个 最多一分钟 手动 最终一致性 整个极狐GitLab 实例

更多信息,可以查看:

虚拟存储

虚拟存储使在极狐GitLab 中拥有单个仓库存储,使简化仓库管理成为可能。

带有 Gitaly 集群的虚拟存储通常可以替代直接的 Gitaly 存储配置。 但是,这是以在多个 Gitaly 节点上存储每个仓库所需的额外存储空间为代价的。与直接 Gitaly 存储相比,使用 Gitaly 集群虚拟存储的好处是:

  • 改进了容错性,因为每个 Gitaly 节点都有每个仓库的副本。
  • 提高了资源利用率,减少了对特定于分片的峰值负载的过度配置需求,因为读取负载分布在 Gitaly 节点上。
  • 不需要手动重新平衡性能,因为读取负载分布在 Gitaly 节点上。
  • 更简单的管理,因为所有 Gitaly 节点都是相同的。

可以使用复制系数配置仓库副本的数量。

与普通 Gitaly 存储一样,虚拟存储可以分片。

存储布局

caution 存储布局是 Gitaly 集群的一个内部细节,不能保证在不同版本之间保持稳定。这里的信息仅用于提供信息和帮助调试。不支持直接在磁盘上对存储库进行更改,这样做可能会导致系统损坏或所做的更改被覆盖。

Gitaly 集群的虚拟存储提供了一种看起来像单一存储的抽象,但实际上它由多个物理存储组成。Gitaly 集群必须将每个操作复制到每个物理存储中。操作可能在某些物理存储上成功,但在其他物理存储上失败。

部分应用的操作可能会给其他操作带来问题,并使系统处于无法恢复的状态。为避免这类问题,每个操作要么完全应用,要么完全不应用。这种操作的特性被称为原子性。

极狐GitLab 控制存储库存储的存储布局。极狐GitLab 指示存储库存储在哪里创建、删除和移动存储库。当这些操作应用于多个物理存储时,会产生原子性问题。例如:

  • 极狐GitLab 在其一个副本不可用时删除一个存储库。
  • 极狐GitLab 之后重新创建该存储库。

结果是,在删除时不可用的陈旧副本可能会引发冲突并阻止存储库的重新创建。

这些原子性问题在过去已经引发了多个问题,涉及以下方面:

  • 使用 Gitaly 集群向辅助站点进行地理同步。
  • 备份恢复。
  • 存储库在存储库存储之间的迁移。

Gitaly 集群通过将存储库以一种特殊的布局存储在磁盘上,为这些操作提供原子性,这种布局可防止因部分应用的操作而可能发生的冲突。

客户端生成的副本路径

存储库存储在存储设备中,存储位置是由Gitaly 客户端确定的相对路径。这些路径可以通过它们不以 @cluster 前缀开头来识别。相对路径遵循哈希存储模式。

Praefect 生成的副本路径

  • 引入于极狐GitLab 15.0 并使用名为 gitaly_praefect_generated_replica_paths 的功能标志。默认禁用。
  • 在 SaaS(JihuLab.com)上启用于极狐GitLab 15.2。
  • 私有化部署启用于极狐GitLab 15.3。
  • 在极狐GitLab 15.6 中 GA。功能标志 gitaly_praefect_generated_replica_paths 被移除。

当 Gitaly 集群创建一个存储库时,它会为该存储库分配一个唯一且永久的 ID,称为 存储库 ID。存储库 ID 是 Gitaly 集群内部的,与极狐GitLab 中其他任何地方的 ID 都没有关系。如果一个存储库从 Gitaly 集群中移除,之后再移回来,从 Gitaly 集群的角度来看,该存储库会被分配一个新的存储库 ID,并且是一个不同的存储库。存储库 ID 的序列总是递增的,但序列中可能会有空缺。

存储库 ID 用于为集群中的每个存储库推导出一个称为 副本路径 的唯一存储路径。一个存储库的副本都存储在存储设备上相同的副本路径中。副本路径与 相对路径 不同:

  • 相对路径是 Gitaly 客户端用来标识存储库及其虚拟存储的名称,对它们来说是唯一的。
  • 副本路径是物理存储中的实际物理路径。

Praefect 在处理客户端请求时,会将 RPC 中的存储库从虚拟的 (虚拟存储,相对路径) 标识符转换为物理存储库 (存储,副本路径) 标识符。

以下是副本路径的格式:

  • 对象池的副本路径是 @cluster/pools/<xx>/<xx>/<存储库 ID>。对象池存储在与其他存储库不同的目录中。它们必须能被 Gitaly 识别,以避免在清理时将它们修剪掉。修剪对象池可能会导致关联存储库中的数据丢失。
  • 其他存储库的副本路径是 @cluster/repositories/<xx>/<xx>/<存储库 ID>

例如,@cluster/repositories/6f/96/54771

副本路径的最后一部分 54771 是存储库 ID。这可用于在磁盘上标识存储库。

<xx>/<xx> 是存储库 ID 的字符串表示的 SHA256 哈希的前四个十六进制数字。这些数字用于将存储库均匀地分配到子目录中,以避免某些文件系统上可能出现的超大目录问题。在这个例子中,54771 的哈希结果是 6f960ab01689464e768366d3315b3d3b2c28f38761a58a70110554eb04d582f7,所以前四个数字是 6f96

在磁盘上识别仓库

使用 praefect metadata 子命令来:

  • 从元数据存储中获取仓库的虚拟存储和相对路径。当您完成哈希存储路径后,您可以使用 Rails 控制台来检索项目路径。
  • 在集群上通过如下方式找到仓库的存储地方:
    • 虚拟存储和相对路径。
    • 存储库 ID。

磁盘上的存储库在 Git 配置文件中还包含项目路径。即使存储库的元数据已被删除,该配置文件也可用于确定项目路径。 请遵循哈希存储文档中的说明

原子操作

Gitaly 集群使用 PostgreSQL 元数据存储和存储布局来确保存储库创建、删除和移动操作的原子性。磁盘操作无法跨多个存储实现原子性应用。然而,PostgreSQL 保证元数据操作的原子性。Gitaly 集群对操作进行建模,使失败的操作始终能保证元数据的一致性。即使在操作成功后,磁盘上也可能包含过时状态。这种情况是预料之中的,残留状态不会干扰未来的操作,但可能会不必要地占用磁盘空间,直到执行清理操作。

仓库创建

当创建仓库时,Parefect:

  1. 从 PostgreSQL 中预留一个仓库 ID,这是原子的,而且不可能有两个创建收到相同的 ID。
  2. 在源自仓库 ID 的副本路径上的 Gitaly 存储中创建副本。
  3. 当仓库在磁盘上成功创建后,会创建元数据记录。

即使两个并发操作创建了相同的存储库,它们也会存储在存储设备的不同目录中,不会发生冲突。首先完成的操作会创建元数据记录,而另一个操作则会因“已存在”错误而失败。失败的创建操作会在存储设备上留下残余存储库。

存储库 ID 是从 PostgreSQL 中的 repositories_repository_id_seq 生成的。在上面的例子中,失败的操作占用了一个存储库 ID,但未能成功用它创建存储库。预计存储库创建失败会导致存储库 ID 出现空缺。

仓库删除

通过移除元数据记录来删除仓库。一旦元数据记录被删除,该存储库在逻辑上就不复存在。PostgreSQL 保证删除操作的原子性,并发删除会因“未找到”错误而失败。在成功删除元数据记录之后,Praefect 会尝试从存储中移除副本。这可能会失败并在存储中留下残留状态。最终会清理这些残留状态。

仓库移动

不像 Gitaly,Gitaly 集群不移动仓库在存储中的位置,而是仅通过更新元数据存储中的相对路径来虚拟地移动仓库,。

组件

Gitaly 集群由多个组件组成:

  • 负载均衡器,用于分发请求并提供对 Praefect 节点的容错访问。
  • Praefect 节点,用于管理集群并将请求路由到 Gitaly 节点。
  • PostgreSQL 数据库,用于持久化集群元数据和 PgBouncer,推荐用于池化 Praefect 的数据库连接。
  • Gitaly 节点提供仓库存储和 Git 访问。

架构

Praefect 是 Gitaly 的路由器和事务管理器,也是运行 Gitaly 集群的必需组件。

Architecture diagram

功能

Gitaly 集群提供以下功能:

分布式读取

Gitaly 集群支持跨为虚拟存储配置的 Gitaly 节点分布读取操作。

所有标有 ACCESSOR 选项的 RPC 都被重定向到最新且健康的 Gitaly 节点。例如,GetBlob

最新(Up to date)意味着:

  • 没有为此 Gitaly 节点安排的复制操作。
  • 最后一个复制操作处于完成状态。

如果出现以下情况,则选择主节点来处理请求:

  • 没有最新的节点。
  • 在节点选择期间发生任何其他错误。

您可以使用 Prometheus 监控读取分布

强一致性

Gitaly 集群通过将更改同步写入所有健康、最新的副本来提供强一致性。如果副本在事务发生时已过时或不健康,则写入将异步复制到它。

如果强一致性不可用,Gitaly 集群保证最终一致性。在这种情况下,在对主 Gitaly 节点的写入发生后,Gitaly 集群会将所有写入复制到次要 Gitaly 节点。

强一致性是 14.0 及更高版本中的主要复制方法。一部分操作仍然使用复制作业(最终一致性)而不是强一致性。

关于强一致性监控的更多信息,可以查看 Gitaly 集群的 Prometheus 指标文档

复制系数

复制系数是 Gitaly 集群维护给定仓库的副本数。更高的复制参数:

  • 提供更好的冗余和读取工作负载分布。
  • 导致更高的存储成本。

默认情况下,Gitaly 集群将仓库复制到虚拟存储中的每个存储。

有关配置信息,请参阅配置复制参数

配置 Gitaly 集群

有关配置 Gitaly 集群的更多信息,请参阅配置 Gitaly 集群

升级 Gitaly 集群

要升级 Gitaly 集群,遵循零宕机升级中的文档指南。

将 Gitaly 集群降级到之前的版本

如果您需要将 Gitaly 集群回滚到早前的某个版本,某些 Praefect 数据库迁移可能需要回滚。

要降级 Gitaly 集群(假设有多个 Praefect 节点):

  1. 停止所有 Praefect 节点上的 Praefect 服务:

    gitlab-ctl stop praefect
    
  2. 在其中一个 Praefect 节点上将极狐GitLab 软件包降级到老旧版本。
  3. 在降级的节点上,检查 Praefect 迁移的状态:

    /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate-status
    
  4. APPLIED 一栏中,计算 unknown migration 的迁移数量。
  5. 降级的 Praefect 节点上,运行 dry run 来验证需要会退的迁移信息。<CT_UNKNOWN> 是由降级节点报告的未知迁移的数量。

    /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate <CT_UNKNOWN>
    
  6. 如果结果查找正确,使用相同的命令,并带上 -f 选项来回退迁移:

    /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-migrate -f <CT_UNKNOWN>
    
  7. 在其他 Praefect 节点上执行降级操作并再次启动 Praefect 服务:

    gitlab-ctl start praefect
    

迁移到 Gitaly 集群

caution Gitaly 集群中存在一些已知问题。在继续之前查看以下信息。

在迁移到 Gitaly 集群之前:

要迁移到 Gitaly 集群:

  1. 创建所需的存储。请参阅仓库存储建议
  2. 创建并配置 Gitaly 集群
  3. 使用 TPC 配置现有的 Gitaly 实例,如果还没有这样配置的话。
  4. 移动仓库。要迁移到 Gitaly 集群,必须移动存储在 Gitaly 集群之外的现有仓库。没有自动迁移,但可以使用极狐GitLab API 安排移动。

即使您不使用 default 仓库存储,也必须确保已对其进行配置。

迁移出 Gitaly 集群

如果发现 Gitaly 集群的限制和取舍不适合您的环境,您可以将 Gitaly 集群迁移到分片的 Gitaly 实例:

  1. 创建并配置一个新的 Gitaly 服务器
  2. 移动仓库到新创建的存储。您可以按分片或按组移动它们,这样您就有机会将它们分布在多个 Gitaly 服务器上。

过渡到 Gitaly 集群

由于移除复杂性的原因,我们必须在极狐GitLab 上移除直接 Git 访问。但是,只要某些极狐GitLab 安装需要在 NFS 上使用 Git 存储库,我们就无法移除它。

我们在极狐GitLab 中尝试消除直接 Git 访问的两个方面因素是:

  • 减少极狐GitLab 发起的低效 Gitaly 查询的数量。
  • 说服具有容错能力或水平扩展的极狐GitLab 实例的管理员从 NFS 迁移出去。

第二个是唯一真正的解决方案。为此,我们开发了Gitaly 集群

极狐GitLab 技术支持

如果您在配置过程中遇到任何问题,您可以在极狐GitLab 官方论坛上发帖求助,您也可以直接扫描下方二维码咨询专业人员:

技术支持