故障排查

作业产物使用过多磁盘空间

作业工件可以比预期更快地填满您的磁盘空间。一些可能的原因是:

在以上和其他情况下,确定对磁盘空间使用负有最大责任的项目,找出使用最多空间的产物类型,并且在某些情况下,手动删除作业产物来回收磁盘空间。

产物清理

产物清理是一个确定哪些工件已过期且可删除的过程。

产物清理功能在 15.0 到 15.2 版本禁用

产物清理功能在 15.0 版本发生了重大变化,受功能标志控制,默认禁用。功能标志在极狐GitLab 15.3 中变为默认启用。

如果产物清理在 15.0 ~ 15.2 中不可用,您应该检查功能标志是否启用。

要检查功能标志是否已启用:

  1. 启动 Rails 控制台

  2. 检查是否启用了功能标志。

    • 14.10 及更早版本:
    Feature.enabled?(:ci_detect_wrongly_expired_artifacts)
    Feature.enabled?(:ci_update_unlocked_job_artifacts)
    Feature.enabled?(:ci_job_artifacts_backlog_work)
    
  3. 如果禁用了任何功能标志,请启用它们:

    Feature.enable(:ci_detect_wrongly_expired_artifacts)
    Feature.enable(:ci_update_unlocked_job_artifacts)
    Feature.enable(:ci_destroy_unlocked_job_artifacts)
    

这些更改包括将产物从 unlocked 切换到 ulocked,如果它们应该保留

产物的 unknown 状态

在引入此功能之前创建的产物的状态为 unknown。在它们过期后,这些产物不会由新的理性维护作业处理。

您可以检查数据库,确认您的实例是否具有状态为 unknown 的产物:

  1. 启动数据库控制台:

    ::Tabs

    :::TabTitle Linux package (Omnibus)

    sudo gitlab-psql
    

    :::TabTitle Helm chart (Kubernetes)

    # Find the toolbox pod
    kubectl --namespace <namespace> get pods -lapp=toolbox
    # Connect to the PostgreSQL console
    kubectl exec -it <toolbox-pod-name> -- /srv/gitlab/bin/rails dbconsole --include-password --database main
    

    :::TabTitle Docker

    sudo docker exec -it <container_name> /bin/bash
    gitlab-psql
    

    :::TabTitle Self-compiled (source)

    sudo -u git -H psql -d gitlabhq_production
    

    ::EndTabs

  2. 运行如下查询:

    select expire_at, file_type, locked, count(*) from ci_job_artifacts
    where expire_at is not null and
    file_type != 3
    group by expire_at, file_type, locked having count(*) > 1;
    

如果返回记录,则说明存在无法处理的产物。例如:

           expire_at           | file_type | locked | count
-------------------------------+-----------+--------+--------
 2021-06-21 22:00:00+00        |         1 |      2 |  73614
 2021-06-21 22:00:00+00        |         2 |      2 |  73614
 2021-06-21 22:00:00+00        |         4 |      2 |   3522
 2021-06-21 22:00:00+00        |         9 |      2 |     32
 2021-06-21 22:00:00+00        |        12 |      2 |    163

锁定状态为 2 的产物为 unknown

清理 unknown 产物

15.3 及更高版本默认启用处理所有 unknown 产物的 Sidekiq worker。它分析上述数据库查询返回的产物,并确定哪些应该 lockedunlocked。如果需要,该 worker 随后会删除产物。

可以在私有化部署实例上启用 worker:

  1. 启动 Rails 控制台

  2. 检查该功能是否已启用。

    Feature.enabled?(:ci_job_artifacts_backlog_work)
    
  3. 如果需要,启用该功能:

    Feature.enable(:ci_job_artifacts_backlog_work)
    

Worker 每 7 分钟处理 10,000 个 unknown 产物,或者 24 小时内处理大约 200 万个。

有一个相关的 ci_job_artifacts_backlog_large_loop_limit 功能标志,它会使得 worker 以五倍大的批次处理 unknown 产物。不建议在私有化部署实例上使用此标志。

列出具有特定到期时间(或无到期时间)的产物的项目和构建

使用 Rails 控制台,您可以找到具有以下作业产物的项目:

  • 没有到期日期。
  • 未来 7 天以上的到期日期。

类似于删除产物,使用以下示例时间范围并根据需要更改它们:

  • 7.days.from_now
  • 10.days.from_now
  • 2.weeks.from_now
  • 3.months.from_now
  • 1.year.from_now

以下每个脚本还使用 .limit(50) 将搜索限制为 50 个结果,但也可以根据需要更改此数字:

# Find builds & projects with artifacts that never expire
builds_with_artifacts_that_never_expire = Ci::Build.with_downloadable_artifacts.where(artifacts_expire_at: nil).limit(50)
builds_with_artifacts_that_never_expire.find_each do |build|
  puts "Build with id #{build.id} has artifacts that don't expire and belongs to project #{build.project.full_path}"
end

# Find builds & projects with artifacts that expire after 7 days from today
builds_with_artifacts_that_expire_in_a_week = Ci::Build.with_downloadable_artifacts.where('artifacts_expire_at > ?', 7.days.from_now).limit(50)
builds_with_artifacts_that_expire_in_a_week.find_each do |build|
  puts "Build with id #{build.id} has artifacts that expire at #{build.artifacts_expire_at} and belongs to project #{build.project.full_path}"
end

按存储的作业产物的总大小列出项目

通过在 Rails 控制台(sudo gitlab-rails 控制台)中运行以下代码,列出前 20 个项目,按存储的作业产物的总大小排序:

include ActionView::Helpers::NumberHelper
ProjectStatistics.order(build_artifacts_size: :desc).limit(20).each do |s|
  puts "#{number_to_human_size(s.build_artifacts_size)} \t #{s.project.full_path}"
end

您可以通过将 .limit(20) 修改为您想要的数量来更改列出的项目数量。

列出单个项目中最大的产物

通过在 Rails 控制台(sudo gitlab-rails 控制台)中运行以下代码,列出单个项目中 50 个最大的作业产物:

include ActionView::Helpers::NumberHelper
project = Project.find_by_full_path('path/to/project')
Ci::JobArtifact.where(project: project).order(size: :desc).limit(50).map { |a| puts "ID: #{a.id} - #{a.file_type}: #{number_to_human_size(a.size)}" }

您可以通过将 .limit(50) 修改为您想要的数量来更改列出的作业产物的数量。

列出单个项目中的产物

列出单个项目的产物,按产物大小排序。输出包括:

  • 创建产物的作业 ID
  • 产物大小
  • 产物文件类型
  • 产物创建日期
  • 产物的磁盘位置
p = Project.find_by_id(<project_id>)
arts = Ci::JobArtifact.where(project: p)

list = arts.order(size: :desc).limit(50).each do |art|
    puts "Job ID: #{art.job_id} - Size: #{art.size}b - Type: #{art.file_type} - Created: #{art.created_at} - File loc: #{art.file}"
end

要更改列出的作业产物数量,请更改 limit(50) 中的数量。

从特定日期之前完成的作业中删除作业产物

caution这些命令从数据库和磁盘中永久删除数据。 在运行它们之前,我们强烈建议寻求支持工程师的指导,或者在测试环境中运行它们,并准备好恢复实例的备份,以防万一。

如果您需要在保留作业日志的同时手动删除与多个作业关联的作业产物,可以从 Rails 控制台(sudo gitlab-rails 控制台)完成:

  1. 选择要删除的作业:

    要为单个项目选择所有带有产物的作业:

    project = Project.find_by_full_path('path/to/project')
    builds_with_artifacts =  project.builds.with_downloadable_artifacts
    

    要在整个 GitLab 实例中选择所有带有产物的作业:

    builds_with_artifacts = Ci::Build.with_downloadable_artifacts
    
  2. 删除早于特定日期的作业产物:

    note此步骤还会删除用户选择 “keep” 的产物。
    builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
    builds_to_clear.find_each do |build|
      Ci::JobArtifacts::DeleteService.new(build).execute
      build.update!(artifacts_expire_at: Time.now)
    end
    

    在 15.3 及更早版本,使用以下命令:

    builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
    builds_to_clear.find_each do |build|
      build.artifacts_expire_at = Time.now
      build.erase_erasable_artifacts!
    end
    

    1.week.ago 是 Rails 的 ActiveSupport::Duration 方法,用于计算过去的新日期或时间。其他有效的例子包括:

    • 7.days.ago
    • 3.months.ago
    • 1.year.ago

    erase_erasable_artifacts! 是一种同步方法,执行后会立即删除工件;它们不是由后台队列调度的。

从特定日期之前完成的作业中删除作业产物和日志

caution这些命令从数据库和磁盘中永久删除数据。 在运行它们之前,我们强烈建议寻求支持工程师的指导,或者在测试环境中运行它们,并准备好恢复实例的备份,以防万一。

如果您需要手动删除与多个作业关联的所有作业产物,包括作业日志,可以从 Rails 控制台(sudo gitlab-rails 控制台)完成:

  1. 选择要删除的作业:

    要为单个项目选择具有产物的作业:

    project = Project.find_by_full_path('path/to/project')
    builds_with_artifacts =  project.builds.with_existing_job_artifacts(Ci::JobArtifact.trace)
    

    要在整个极狐GitLab 实例中选择具有产物的作业:

    builds_with_artifacts = Ci::Build.with_existing_job_artifacts(Ci::JobArtifact.trace)
    
  2. 选择 Web UI 中提到的用户擦除作业:

    admin_user = User.find_by(username: 'username')
    
  3. 清除早于特定日期的作业产物和日志:

    builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
    builds_to_clear.find_each do |build|
      print "Ci::Build ID #{build.id}... "
    
      if build.erasable?
        Ci::BuildEraseService.new(build, admin_user).execute
        puts "Erased"
      else
        puts "Skipped (Nothing to erase or not erasable)"
      end
    end
    

    在 15.3 及更早版本,使用 build.erase(erased_by: admin_user) 代替 Ci::BuildEraseService.new(build, admin_user).execute

    1.week.ago 是 Rails 的 ActiveSupport::Duration 方法,用于计算过去的新日期或时间。其他有效的例子包括:

    • 7.days.ago
    • 3.months.ago
    • 1.year.ago

作业产物上传失败,错误 500

如果您将对象存储用于产物并且作业产物无法上传,请查看:

  • 错误消息的作业日志类似于:

    WARNING: Uploading artifacts as "archive" to coordinator... failed id=12345 responseStatus=500 Internal Server Error status=500 token=abcd1234
    
  • workhorse 日志的错误消息类似于:

    {"error":"MissingRegion: could not find region configuration","level":"error","msg":"error uploading S3 session","time":"2021-03-16T22:10:55-04:00"}
    

在这两种情况下,您可能需要将 region 添加到作业产物对象存储配置

作业产物上传失败 500 Internal Server Error (Missing file)

统一形式对象存储不支持包含文件夹路径的存储桶名称。 例如,bucket/path。如果存储桶名称中包含路径,您可能会收到类似于以下内容的错误:

WARNING: Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/job_id/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+day: 500 Internal Server Error (Missing file) 
FATAL: invalid argument

如果在使用整合对象存储时作业产物上传失败并出现上述错误,请确保您为每种数据类型使用单独的存储桶。 –>

当使用 Windows 挂载时,作业产物上传使用,并提示 FATAL: invalid argument

如果您正在使用 CIFS 进行 Windows 挂载时,当 runner 尝试上传产物时您会看到 invalid argument 错误:

WARNING: Uploading artifacts as "dotenv" to coordinator... POST https://<your-gitlab-instance>/api/v4/jobs/<JOB_ID>/artifacts: 500 Internal Server Error  id=1296 responseStatus=500 Internal Server Error status=500 token=*****
FATAL: invalid argument

要解决此问题,您可以尝试:

  • 转而使用 ext4 进行挂载,而不是 CIFS。
  • 升级内核,至少为 5.15,此版本包含了与 CIFS 文件租赁(file leasing)相关的缺陷修复。
  • 对于老旧版本的内核,使用 nolease 挂载选项禁用文件租赁。

产物存储的使用额度显示不正确

有时候,产物存储使用量会显示不正确。为了重新计算实例中所有项目和群组所用的产物使用量,您可以使用如下后台脚本:

gitlab-rake gitlab:refresh_project_statistics_build_artifacts_size[https://example.com/path/file.csv]

https://example.com/path/file.csv 这个文件必须列出所有你想要重新计算工件存储使用量的项目的项目 ID。为文件使用如何格式:

PROJECT_ID
1
2

在脚本运行期间,工件使用量的值可能会波动至 0。重新计算后,使用量应再次按预期显示。

产物下载流图标 download flow diagrams

以下流程图展示了作业工件的工作原理。这些流程图假定已为作业工件配置了对象存储。

代理下载禁用

proxy_download 设置为 false,则极狐GitLab 会重定向 runner 来从存储对象使用预签名的 URL 下载产物。对 runner 来说,从源直接下载产物总是非常快的,因此通常会推荐此种配置。此外,它还会减少带宽使用,因为数据不会从极狐GitLab 拉取并发送给 runner。然而,应该给予 runner 直接访问对象存储的权限。

请求流如下:

sequenceDiagram autonumber participant C as Runner participant O as Object Storage participant W as Workhorse participant R as Rails participant P as PostgreSQL C->>+W: GET /api/v4/jobs/:id/artifacts?direct_download=true Note over C,W: gitlab-ci-token@<CI_JOB_TOKEN> W-->+R: GET /api/v4/jobs/:id/artifacts?direct_download=true Note over W,R: gitlab-ci-token@<CI_JOB_TOKEN> R->>P: Look up job for CI_JOB_TOKEN R->>P: Find user who triggered job R->>R: Does user have :read_build access? alt Yes R->>W: Send 302 redirect to object storage presigned URL R->>C: 302 redirect C->>O: GET <presigned URL> else No R->>W: 401 Unauthorized W->>C: 401 Unauthorized end

在此图中:

  1. 首先,runner 尝试使用 GET /api/v4/jobs/:id/artifacts 端点来拉取作业产物。首次尝试时,runner 会附加 direct_download=true 查询参数,以显示可以直接从对象存储下载产物。在 runner 配置中,可以通过FF_USE_DIRECT_DOWNLOAD 功能标志来禁用直接下载。此标志默认为 true

  2. runner 使用 HTTP Basic 认证,用户名为 gitlab-ci-token,以自动生成的的 CI/CD 作业令牌为密码来发送 GET 请求。此令牌由极狐GitLab 生成并且在作业开始时给到 runner。

  3. GET 请求发送到极狐GitLab API,这会在数据库中寻找令牌并找到触发作业的用户。

  4. 在步骤 5 ~ 8 中:

    • 如果用户访问构建,则极狐GitLab 生成一个预签名 URL,然后发送一个 302 重定向,其中 Location 设置为该 URL。runner 遵循 302 重定向并下载产物。

    • 如果无法找到作业或用户无法访问作业,则 API 返回 401 未授权。

    如果接收到如下 HTTP 状态码,则 runner 不会进行重试:

    • 200 OK
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found

    然而,如果 runner 接收到其他任何状态码,比如 500 错误,则它会重新尝试下载产物,每次尝试间休眠 1s。随后的尝试会忽略 direct_download=true

代理下载启用

如果 proxy_downloadtrue,则极狐GitLab 会总是从对象存储中下载产物并发送数据到 runner,即使 runner 发送 direct_download=true 查询参数。如果 runner 有受限制的网络访问,则可能需要启用代理下载。

以下图标和禁用代理下载示例相似,除了第 6 ~ 9 步外,极狐GitLab 不会发送 302 重定向到 runner。相反地,极狐GitLab 会指导 Workhorse 来拉取数据并返回给 runner。从 runner 角度来说,针对 /api/v4/jobs/:id/artifacts 的 GET 请求会返回二进制数据目录。

sequenceDiagram autonumber participant C as Runner participant O as Object Storage participant W as Workhorse participant R as Rails participant P as PostgreSQL C->>+W: GET /api/v4/jobs/:id/artifacts?direct_download=true Note over C,W: gitlab-ci-token@<CI_JOB_TOKEN> W-->+R: GET /api/v4/jobs/:id/artifacts?direct_download=true Note over W,R: gitlab-ci-token@<CI_JOB_TOKEN> R->>P: Look up job for CI_JOB_TOKEN R->>P: Find user who triggered job R->>R: Does user have :read_build access? alt Yes R->>W: SendURL with object storage presigned URL W->>O: GET <presigned URL> O->>W: <artifacts data> W->>C: <artifacts data> else No R->>W: 401 Unauthorized W->>C: 401 Unauthorized end

413 Request Entity Too Large 错误

如果产物太大,作业可能会因为如下错误导致失败;

Uploading artifacts as "archive" to coordinator... too large archive <job-id> responseStatus=413 Request Entity Too Large status=413" at end of a build job on pipeline when trying to store artifacts to <object-storage>.

您可能需要:

  • 增加最大产物大小
  • 如果您在使用 NGINX 作为代理服务器,增加文件上传的大小限制,默认为 1MB。在 NGINX 配置文件中,将 client-max-body-size 的值设的更大一点儿。