Geo (development)

原文:https://docs.gitlab.com/ee/development/geo.html

Geo (development)

Geo 将 GitLab 实例连接在一起. 一个 GitLab 实例被指定为主节点,并且可以与多个辅助节点一起运行. Geo 精心策划了很多组件,这些组件可以在下图中看到,并在本文档中进行了更详细的描述.

Geo Architecture Diagram

Replication layer

Geo 处理不同组件的复制:

  • 数据库 :包括整个应用程序,缓存和作业除外.
  • Git 存储库 :包括项目和 Wiki.
  • 上载的 Blob :包括从问题附带的图像到 CI 的原始日志和资产的所有内容.

除数据库复制外,在辅助节点上,所有内容均由Geo Log Cursor协调.

Geo Log Cursor daemon

Geo Log Cursor 守护程序是在每个辅助节点上运行的单独进程. 它监视地理事件日志中是否有新事件,并为每种特定事件类型创建后台作业.

例如,当更新存储库时,Geo 节点会创建一个带有关联的存储库更新事件的 Geo 事件. Geo Log Cursor 守护程序接收事件并安排一个Geo::ProjectSyncWorker作业,该作业将使用Geo::RepositorySyncServiceGeo::WikiSyncService类分别更新存储库和 Wiki.

Geo Log Cursor 守护程序可以自动在高可用性模式下运行. 守护程序将不时尝试获取锁,一旦获得锁定,它将充当活动守护程序.

在同一节点上的所有其他正在运行的守护程序都将处于待机模式,如果活动守护程序释放其锁定,则可以恢复工作.

We use the ExclusiveLease lock type with a small TTL, that is renewed at every pooling cycle. That allows us to implement this global lock with a timeout.

在池化周期结束时,如果守护程序无法更新和/或重新获得锁定,它将切换到待机模式.

Database replication

Geo 使用流复制将数据库从节点复制辅助节点. 通过此复制, 辅助节点可以访问数据库中保存的所有数据. 因此,用户可以登录次级和读取所有的问题,合并请求等辅助节点上.

Repository replication

Geo 还复制存储库. 每个辅助节点都跟踪跟踪数据库中每个存储库的状态.

存在以下几种方式来复制存储库:

Project Registry

Geo::ProjectRegistry类定义用于跟踪存储库复制状态的模型. 对于主数据库中的每个项目,在跟踪数据库中保留一个记录.

它记录有关存储库的以下内容:

  • 他们上次同步的时间.
  • 上一次成功同步它们.
  • 是否需要重新同步.
  • 重试的时间.
  • 重试次数.
  • 是否以及何时进行验证.

它还将项目 Wiki 的这些属性存储在专用列中.

Repository Sync worker

The Geo::RepositorySyncWorker class runs periodically in the background and it searches the Geo::ProjectRegistry model for projects that need updating. Those projects can be:

  • 未同步:从未在辅助节点上同步过的项目,因此尚不存在.
  • 最近更新:项目的last_repository_updated_at时间戳比Geo::ProjectRegistry模型中的last_repository_successful_sync_at时间戳更新.
  • 手动:管理员可以在地理管理面板中手动标记存储库以重新同步.

当我们在次要RETRIES_BEFORE_REDOWNLOAD时间内无法获取存储库时,Geo 会进行所谓的重新下载 . 它将干净地克隆到存储根目录中的@geo-temporary目录中. 成功后,我们用新克隆的仓库替换主仓库.

Uploads replication

文件上传也被复制到辅助节点. 为了跟踪同步状态,使用了Geo::UploadRegistry模型.

Upload Registry

项目注册表类似,有一个Geo::UploadRegistry模型可以跟踪同步的上传.

CI Job Artifacts and LFS objects are synced in a similar way as uploads, but they are tracked by Geo::JobArtifactRegistry, and Geo::LfsObjectRegistry models respectively.

File Download Dispatch worker

还类似于Repository Sync 工作器 ,有一个Geo::FileDownloadDispatchWorker类,该类定期运行以同步所有尚未同步到 Geo 辅助节点的上载.

通过 HTTP 复制文件,并通过/api/v4/geo/transfers/:type/:id端点(例如/api/v4/geo/transfers/lfs/123 .

Authentication

为了验证文件传输,每个GeoNode记录都有两个字段:

  • 公共访问密钥( access_key字段).
  • 秘密访问密钥( secret_access_key字段).

次要节点通过JWT 请求进行身份验证. 当辅助节点希望下载文件时,它将发送带有Authorization标头的 HTTP 请求:

  1. Authorization: GL-Geo <access_key>:<JWT payload>

节点使用access_key字段查找相应的辅助节点并解密 JWT 有效负载,该负载包含用于标识文件请求的其他信息. 这样可以确保辅助节点为正确的数据库 ID 下载正确的文件. 例如,对于 LFS 对象,请求还必须包含文件的 SHA256 和. JWT 有效负载示例如下所示:

  1. { "data": { sha256: "31806bb23580caab78040f8c45d329f5016b0115" }, iat: "1234567890" }

如果请求的文件与请求的 SHA256 总和匹配,则 Geo 节点将通过X-Sendfile功能发送数据,这使 NGINX 可以处理文件传输而不会占用 Rails 或 Workhorse.

注意: JWT 需要在所涉及的机器之间同步时钟,否则它可能会因加密错误而失败.

Git Push to Geo secondary

Git Push Proxy 作为gitlab-shell组件内置的功能存在. 它仅在辅助节点上处于活动状态. 它允许从辅助节点克隆存储库的用户推送到相同的 URL.

定向到节点的 Git push请求将发送到节点,而pull请求将继续由节点服务,以实现最大效率.

HTTPS 和 SSH 请求的处理方式不同:

  • 使用 HTTPS,我们将为用户提供指向节点上项目的HTTP 302 Redirect . Git 客户端足够聪明,可以理解该状态码并处理重定向.
  • 使用 SSH,因为没有等效的方法来执行重定向,所以我们必须代理请求. 这是在gitlab-shell内部gitlab-shell ,方法是先将请求转换为 HTTP 协议,然后将其代理到节点.

gitlab-shell守护程序根据/api/v4/allowed的响应知道何时进行代理. 返回一个特殊的HTTP 300状态代码,我们执行在响应正文中指定的”自定义操作”. 该响应包含允许代理push操作在节点上发生的其他数据.

Using the Tracking Database

除了要复制的主数据库外,Geo 辅助节点还具有自己的单独的跟踪数据库 .

跟踪数据库包含辅助节点的状态.

任何需要在升级过程中运行的数据库迁移都需要应用于每个辅助节点上的跟踪数据库.

Configuration

数据库配置在config/database_geo.yml设置. ee/db/geo目录包含此数据库的架构和迁移.

要为数据库编写迁移,请使用GeoMigrationGenerator

  1. rails g geo_migration [args] [options]

要迁移跟踪数据库,请运行:

  1. bundle exec rake geo:db:migrate

Foreign Data Wrapper

在 GitLab 10.1 中引入.

地理日志游标使用了外部数据包装器( FDW ),并提高了许多同步操作的性能.

FDW 是 PostgreSQL 扩展( postgres_fdw ),已在 Geo Tracking 数据库中(在辅助节点上)启用,它允许它连接到只读数据库副本并执行查询和过滤来自两个实例的数据.

该持久连接被配置为名为gitlab_secondary的 FDW 服务器. 此配置仅存在于数据库的用户上下文中. 要访问gitlab_secondary ,GitLab 需要使用先前配置的同一数据库用户.

地理跟踪数据库以常规用户身份通过​​FDW 访问只读数据库副本,受其自身限制的限制. 凭据被配置为与先前映射的SERVERgitlab_secondary )相关联的USER MAPPING .

FDW 配置和凭据定义由 Omnibus GitLab gitlab-ctl reconfigure命令自动管理.

Refreshing the Foreign Tables

每当在节点上配置了新的地理节点或数据库架构更改时,都必须通过运行以下命令来刷新辅助节点上的外部表:

  1. bundle exec rake geo:db:refresh_foreign_tables

否则,将阻止辅助节点正常运行. 辅助节点将生成错误消息,如以下 PostgreSQL 错误所示:

  1. ERROR: relation "gitlab_secondary.ci_job_artifacts" does not exist at character 323
  2. STATEMENT: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
  3. pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
  4. FROM pg_attribute a LEFT JOIN pg_attrdef d
  5. ON a.attrelid = d.adrelid AND a.attnum = d.adnum
  6. WHERE a.attrelid = '"gitlab_secondary"."ci_job_artifacts"'::regclass
  7. AND a.attnum > 0 AND NOT a.attisdropped
  8. ORDER BY a.attnum

Accessing data from a Foreign Table

在 SQL 级别,您要做的就是从gitlab_secondary.* SELECT数据.

这是一个如何从地理跟踪数据库的 FDW 访问所有项目的示例:

  1. SELECT * FROM gitlab_secondary.projects;

作为一个更真实的示例,这是在”跟踪数据库”中过滤未归档项目的方式:

  1. SELECT project_registry.*
  2. FROM project_registry
  3. JOIN gitlab_secondary.projects
  4. ON (project_registry.project_id = gitlab_secondary.projects.id
  5. AND gitlab_secondary.projects.archived IS FALSE)

在 ActiveRecord 级别,我们还有其他表示外部表的模型. 必须以稍微不同的方式映射它们,并且它们是只读的.

检查ee/app/models/geo/fdw现有的 FDW 模型以供参考.

从开发人员的角度来看,这与创建代表数据库视图的模型没有什么不同.

通过上面的示例,您可以通过以下方式访问项目:

  1. Geo::Fdw::Project.all

并通过未归档的项目访问ProjectRegistry过滤:

  1. # We have to use Arel here:
  2. project_registry_table = Geo::ProjectRegistry.arel_table
  3. fdw_project_table = Geo::Fdw::Project.arel_table
  4. project_registry_table.join(fdw_project_table)
  5. .on(project_registry_table[:project_id].eq(fdw_project_table[:id]))
  6. .where((fdw_project_table[:archived]).eq(true)) # if you append `.to_sql` you can check generated query

Finders

Geo 使用Finders ,这是一些类,可以帮助您轻松查找项目/附件/等. 在跟踪数据库和主数据库中.

Finders Performance

查找者需要将主数据库中的数据与跟踪数据库中的数据进行比较. 例如,计算已同步项目的数量通常涉及从一个数据库中检索项目 ID,并检查它们在另一个数据库中的状态. 这很慢并且需要大量内存.

为了克服这个问题,查找器使用FDW或外部数据包装器. 这允许在主数据库和跟踪数据库之间进行常规的JOIN .

Redis

Redis 的辅助节点上的工作方式相同的节点上. 它用于缓存,存储会话和其他持久性数据.

不使用节点和辅助节点之间的 Redis 数据复制,因此会话等不在节点之间共享.

Object Storage

GitLab 可以选择使用对象存储来存储否则将存储在磁盘上的数据. 这些事情可以是:

  • LFS Objects
  • CI 工作工件
  • Uploads

Geo 不处理存储在对象存储中的对象. Geo 会忽略对象存储中的项目. 要么:

  • 对象存储层应注意其自身的地理复制.
  • 所有辅助节点应使用相同的存储节点.

Verification

Repository verification

存储库将通过校验和进行验证.

节点在存储库上计算校验和. 它基本上将所有 Git 引用散列在一起,并将该散列存储在数据库的project_repository_states表中.

节点执行相同操作以计算其克隆的哈希,然后将哈希与节点计算出的值进行比较. 如果存在不匹配,Geo 会将其标记为不匹配,管理员可以在Geo 管理面板中看到此不匹配.

Glossary

Primary node

节点是地理设置中具有读写功能的单个节点. 这是事实的唯一来源,Geo 次要节点从那里复制数据.

在地理位置设置中,只能有一个节点. 所有辅助节点都连接到该节点.

Secondary node

辅助节点是在不同地理位置运行的主要节点的只读副本.

Streaming replication

Geo 取决于 PostgreSQL 的流复制功能. 它完全复制数据库数据和数据库模式. 数据库副本是只读副本.

流复制取决于预写日志或 WAL. 这些日志将复制到副本并在此处重放.

由于流复制还可以复制架构,因此数据库迁移不需要在辅助节点上运行.

Tracking database

每个 Geo 辅助节点上的数据库,该数据库保留其所在节点的状态. 在使用跟踪数据库中阅读更多信息 .

FDW

外部数据包装程序(FDW)是 PostgreSQL 内置的功能. 它允许从不同的数据源查询数据. 在 Geo 中,它用于查询来自不同 PostgreSQL 实例的数据.

Geo Event Log

Geo 主数据库将事件存储在geo_event_log表中. 日志中的每个条目都包含特定类型的事件. 这些类型的事件包括:

  • 存储库已删除事件
  • 存储库重命名事件
  • 存储库更改事件
  • 存储库创建事件
  • 哈希存储迁移事件
  • Lfs 对象已删除事件
  • 哈希存储附件事件
  • Job Artifact Deleted 事件
  • 上载已删除的活动

Geo Log Cursor

在寻找新的Geo::EventLog行的辅助节点上运行的进程.

Code features

Gitlab::Geo utilities

与 Geo 相关的小型实用工具方法进入ee/lib/gitlab/geo.rb文件.

其中许多方法都使用RequestStore类进行缓存,以减少在整个代码库中使用这些方法对性能的影响.

Current node

类方法.current_node返回当前节点的GeoNode记录.

我们使用gitlab.ymlhostportrelative_url_root值,并在数据库中搜索以标识我们所在的节点(请参见GeoNode.current_node ).

Primary or secondary

要确定当前节点是节点还是辅助节点,请使用.primary?.secondary? 类方法.

当未启用节点时,这些方法都可能在节点上都返回false . 请参阅启用 .

Geo Database configured?

处理初始化期间发生的事情时,还有一个附加的陷阱. 在一些地方,我们使用Gitlab::Geo.geo_database_configured? 检查节点是否具有跟踪数据库的方法,该数据库仅存在于辅助节点上. 这克服了在引导新节点期间可能发生的竞争条件.

Enablement

当用户拥有包括功能的有效许可证,并且在”地理节点”屏幕上定义了至少一个节点时,我们认为地理功能已启用.

See Gitlab::Geo.enabled? and Gitlab::Geo.license_allows? methods.

Read-only

所有地理辅助节点均为只读.

只读数据库的一般原则适用于所有 Geo 辅助节点. 那么Gitlab::Database.read_only? 方法将始终在辅助节点上返回true .

当由于节点是辅助节点而不允许某些写操作时,请考虑添加Gitlab::Database.read_only? 还是Gitlab::Database.read_write? 后卫,而不是Gitlab::Geo.secondary? .

在复制的设置中,数据库本身已经是只读的,因此我们无需为此采取任何额外的步骤.

Steps needed to replicate a new data type

随着 GitLab 的发展,我们不断需要向 Geo 复制系统添加新资源. 具体实现取决于资源的具体情况,但是需要注意以下几点:

  • 主站点上的事件生成. 每当更改/更新新资源时,我们都需要为 Log Cursor 创建一个任务.
  • 事件处理. 日志游标需要为主站点生成的每种事件类型都有一个处理程序.
  • 派遣工人(临时工). 确保回填条件良好.
  • 同步工作者.
  • 注册所有可能的状态.
  • Verification.
  • 清洁器. 更改辅助站点的同步设置后,需要清理一些资源.
  • 地理节点状态. 我们需要在 GitLab 管理区域中提供 API 端点以及一些演示.
  • 健康检查. 如果我们可以执行一些预检查并在出现问题时使节点不正常,则应该这样做. rake gitlab:geo:check命令也必须更新.

Geo self-service framework (alpha)

我们开始开发新的Geo 自助服务框架(alpha) ,这使添加新数据类型变得容易得多.

History of communication channel

自从第一次迭代以来,沟通渠道已经发生了变化,您可以在此处查看历史性决策以及我们为何迁移到新实现的原因.

Custom code (GitLab 8.6 and earlier)

在 8.6 之前的 GitLab 版本中,自定义代码用于处理 HTTP 请求从节点到辅助节点的通知.

System hooks (GitLab 8.7 to 9.5)

后来,决定放弃自定义代码,开始使用系统挂钩. 使用它们的人越来越多,因此许多人将从对该通信层进行的改进中受益.

我们的 API 代码(Grape)中有一个特定的内部端点,该端点接收来自此系统挂钩的所有请求: /api/v4/geo/receive_events .

我们通过event_name字段切换和过滤每个事件.

Geo Log Cursor (GitLab 10.0 and up)

从 GitLab 10.0 开始,不再使用系统 Webhooks而是使用 Geo Log Cursor. 日志光标遍历Geo::EventLog行以查看自上次检查日志以来是否有更改,并将处理存储库更新,删除,更改和重命名.

该表位于复制的数据库中. 与旧方法相比,它具有两个优点:

  • 复制是同步的,我们保留事件的顺序.
  • 事件的复制与数据库中的更改同时发生.

Self-service framework

如果您想为正在处理的资源添加简单的地理复制,请查看我们的自助服务框架 .