Help us learn about your current experience with the documentation. Take the survey.

长轮询

  • 版本:Free, Premium, Ultimate
  • 提供:GitLab.com, GitLab Self-Managed, GitLab Dedicated

默认情况下,GitLab Runner 会定期轮询 GitLab 实例以获取新的 CI/CD 作业。实际的轮询间隔取决于运行器配置文件中配置的 check_interval 和运行器数量

在处理多个运行器的服务器上,这种轮询可能导致以下性能问题:

  • 更长的队列等待时间
  • GitLab 实例的 CPU 使用率升高

为缓解这些问题,您应该启用长轮询。

先决条件:

  • 您必须是管理员。

启用长轮询

您可以配置 GitLab 实例,使其在准备新作业之前,将来自运行器的作业请求保持在长轮询状态。

为此,通过配置 GitLab Workhorse 的长轮询持续时间(apiCiLongPollingDuration)来启用长轮询:

  1. 编辑 /etc/gitlab/gitlab.rb

    gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
  2. 保存文件并重新配置 GitLab:

    sudo gitlab-ctl reconfigure

使用 gitlab.webservice.workhorse.extraArgs 设置启用长轮询。

  1. 导出 Helm 值:

    helm get values gitlab > gitlab_values.yaml
  2. 编辑 gitlab_values.yaml

    gitlab:
      webservice:
        workhorse:
          extraArgs: "-apiCiLongPollingDuration 50s"
  3. 保存文件并应用新值:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
  1. 编辑 docker-compose.yml

    version: "3.6"
    services:
      gitlab:
        image: 'gitlab/gitlab-ee:latest'
        restart: always
        hostname: 'gitlab.example.com'
        environment:
          GITLAB_OMNIBUS_CONFIG: |
            gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
  2. 保存文件并重启 GitLab:

    docker compose up -d

指标

启用长轮询后,GitLab Workhorse 会订阅 Redis PubSub 频道并等待通知。当运行器密钥发生变化或达到 apiCiLongPollingDuration 时,作业请求会从长轮询中释放。您可以监控以下 Prometheus 指标:

指标 类型 描述 标签
gitlab_workhorse_keywatcher_keywatchers Gauge GitLab Workhorse 正在监视的密钥数量
gitlab_workhorse_keywatcher_redis_subscriptions Gauge Redis PubSub 订阅数量
gitlab_workhorse_keywatcher_total_messages Counter GitLab Workhorse 在 PubSub 频道上接收到的消息总数
gitlab_workhorse_keywatcher_actions_total Counter 各种密钥监视器操作计数 action
gitlab_workhorse_keywatcher_received_bytes_total Counter PubSub 频道上接收的总字节数

您可以查看一个用户如何使用这些指标发现长轮询问题的示例

长轮询工作流程

该图显示了启用长轮询时单个运行器获取作业的过程:

%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: 长轮询工作流程
accDescr: 启用长轮询时单个运行器获取作业的流程

    autonumber
    participant C as Runner
    participant W as Workhorse
    participant Redis as Redis
    participant R as Rails
    participant S as Sidekiq
    C->>+W: POST /api/v4/jobs/request
    W->>+Redis: 运行器 A 有新作业吗?
    Redis->>+W: 未知
    W->>+R: POST /api/v4/jobs/request
    R->>+Redis: 运行器 A: last_update = X
    R->>W: 204 无作业,X-GitLab-Last-Update = X
    W->>C: 204 无作业,X-GitLab-Last-Update = X
    C->>W: POST /api/v4/jobs/request, X-GitLab-Last-Update: X
    W->>Redis: last_update 变化时通知
    Note over W: 请求保持在长轮询中
    Note over S: CI 作业已创建
    Note over S, Redis: 更新所有已注册的运行器
    S->>Redis: 运行器 A: last_update = Z
    Redis->>W: 运行器: last_update 已更改
    Note over W: 请求从长轮询中释放
    W->>Rails: POST /api/v4/jobs/request
    Rails->>W: 201 作业已调度
    W->>C: 201 作业已调度

在步骤 1 中,当运行器请求新作业时,它会向 GitLab 服务器发出 POST 请求(/api/v4/jobs/request),首先由 Workhorse 处理。

Workhorse 从 X-GitLab-Last-Update HTTP 标头中读取运行器令牌和值,构造一个密钥,并使用该密钥订阅 Redis PubSub 频道。如果该密钥没有对应的值,则 Workhorse 会立即将请求转发到 Rails(步骤 3 和 4)。

Rails 检查作业队列。如果没有可用的作业,Rails 会向运行器返回一个带有 last_update 令牌的 204 无作业 响应(步骤 5 到 7)。

运行器使用该 last_update 令牌再次请求作业,并在 X-GitLab-Last-Update HTTP 标头中填充此令牌。这次,Workhorse 检查运行器的 last_update 令牌是否已更改。如果没有,Workhorse 会将请求保持最多 apiCiLongPollingDuration 指定的持续时间。

如果用户触发新的流水线或作业运行,Sidekiq 中的后台任务会更新所有可用运行器的 last_update 值。运行器可以在项目、组和/或实例级别注册。

步骤 10 和 11 中的这个"tick"将作业请求从 Workhorse 长轮询队列中释放,请求被发送到 Rails(步骤 12)。Rails 查找可用作业并将运行器分配给该作业(步骤 13 和 14)。

启用长轮询后,运行器在新作业可用时会立即收到通知。这不仅有助于减少作业队列等待时间,还降低了服务器开销,因为作业请求仅在有新工作时才会到达 Rails。

故障排除

使用长轮询时,您可能会遇到以下问题。

作业获取缓慢

长轮询默认未启用,因为在某些运行器配置中,运行器无法及时获取作业。请参阅问题 27709

如果运行器 config.toml 中的 concurrent 设置值低于定义的运行器数量,就可能发生这种情况。要解决此问题,请确保 concurrent 的值等于或大于运行器数量。

例如,如果 config.toml 中有三个 [[runners]] 条目,请确保 concurrent 至少设置为 3。

启用长轮询后,运行器:

  1. 启动 concurrent 数量的 Goroutine。
  2. 等待 Goroutine 在长轮询后返回。
  3. 运行另一批请求。

例如,考虑单个 config.toml 配置了以下内容:

  • 项目 A 的 3 个运行器
  • 项目 B 的 1 个运行器
  • concurrent 设置为 3

在此示例中,运行器为前 3 个项目启动 Goroutine。在最坏的情况下,运行器在为项目 B 请求作业之前,会为项目 A 等待完整的长轮询间隔。