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

管理员作业制品故障排除

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab Self-Managed

在管理作业制品时,您可能会遇到以下问题。

作业制品占用过多磁盘空间

作业制品可能会比预期更快地占满您的磁盘空间。一些可能的原因是:

在这些和其他情况下,找出占用磁盘空间最多的项目,确定哪些类型的制品占用了最多空间,在某些情况下,手动删除作业制品以回收磁盘空间。

制品清理维护

制品清理维护是识别哪些制品已过期并可以删除的过程。

GitLab 15.0 到 15.2 中清理维护被禁用

制品清理维护在 GitLab 15.0 中得到了显著改进,引入了默认禁用的功能标志。这些标志在GitLab 15.3中默认启用。

如果制品清理维护在 GitLab 15.0 到 GitLab 15.2 中似乎没有工作,您应该检查功能标志是否已启用。

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

  1. 启动 Rails 控制台

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

    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_job_artifacts_backlog_work)

这些更改包括将制品从 unlocked(未锁定)切换到 locked(锁定),如果它们应该被保留

状态为 unknown(未知)的制品

在清理维护更新之前创建的制品状态为 unknown。过期后,这些制品不会被新的清理维护处理。

您可以检查数据库以确认您的实例是否有状态为 unknown 的制品:

  1. 启动数据库控制台:

    sudo gitlab-psql
    # 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
    sudo docker exec -it <container_name> /bin/bash
    gitlab-psql
    sudo -u git -H psql -d gitlabhq_production
  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(未知)。查看问题 #346261了解更多详情。

清理 unknown(未知)制品

处理所有 unknown 制品的 Sidekiq worker 在 GitLab 15.3 及更高版本中默认启用。它分析先前数据库查询返回的制品,并确定哪些应该是 locked(锁定)或 unlocked(未锁定)。然后,如果需要,该 worker 会删除这些制品。

可以在 GitLab Self-Managed 上启用该 worker:

  1. 启动 Rails 控制台

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

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

    Feature.enable(:ci_job_artifacts_backlog_work)

该 worker 每七分钟处理 10,000 个 unknown 制品,大约 24 小时内处理两百万个。

@final 制品未从对象存储中删除

在 GitLab 16.1 及更高版本中,制品直接上传到 @final 目录中的最终存储位置,而不是首先使用临时位置。

GitLab 16.1 和 16.2 中的一个问题导致制品在过期时未从对象存储中删除。过期制品的清理过程不会从 @final 目录中删除制品。此问题在 GitLab 16.3 及更高版本中已修复。

运行 GitLab 16.1 或 16.2 一段时间的 GitLab 实例管理员可能会看到制品使用的对象存储增加。按照此过程检查并删除这些制品。

删除文件是一个两阶段过程:

  1. 识别哪些文件已成为孤立文件
  2. 从对象存储中删除已识别的文件
列出孤立的作业制品
sudo gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objects
docker exec -it <container-id> bash
gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objects

要么写入容器挂载的持久卷,要么在命令完成时:将输出文件复制出会话。

sudo -u git -H bundle exec rake gitlab:cleanup:list_orphan_job_artifact_final_objects RAILS_ENV=production
# find the pod
kubectl get pods --namespace <namespace> -lapp=toolbox

# open the Rails console
kubectl exec -it -c toolbox <toolbox-pod-name> bash
gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objects

当命令完成时,将文件从会话中复制到持久存储。

Rake 任务有一些适用于所有类型 GitLab 部署的附加功能:

  • 扫描对象存储可以被中断。进度记录在 Redis 中,用于从该点恢复扫描制品。

  • 默认情况下,Rake 任务生成一个 CSV 文件: /opt/gitlab/embedded/service/gitlab-rails/tmp/orphan_job_artifact_final_objects.csv

  • 设置环境变量以指定不同的文件名:

    # Packaged GitLab
    sudo su -
    FILENAME='custom_filename.csv' gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objects
  • 如果输出文件已存在(默认文件或指定文件),它会将条目追加到文件中。

  • 每行包含逗号分隔的 object_path,object_size 字段,没有文件头。例如:

    35/13/35135aaa6cc23891b40cb3f378c53a17a1127210ce60e125ccf03efcfdaec458/@final/1a/1a/5abfa4ec66f1cc3b681a4d430b8b04596cbd636f13cdff44277211778f26,201
删除孤立的作业制品
sudo gitlab-rake gitlab:cleanup:delete_orphan_job_artifact_final_objects
docker exec -it <container-id> bash
gitlab-rake gitlab:cleanup:delete_orphan_job_artifact_final_objects
  • 当命令完成时,将输出文件复制出会话,或将其写入容器已挂载的卷。
sudo -u git -H bundle exec rake gitlab:cleanup:delete_orphan_job_artifact_final_objects RAILS_ENV=production
# find the pod
kubectl get pods --namespace <namespace> -lapp=toolbox

# open the Rails console
kubectl exec -it -c toolbox <toolbox-pod-name> bash
gitlab-rake gitlab:cleanup:delete_orphan_job_artifact_final_objects
  • 当命令完成时,将文件从会话中复制到持久存储。

以下适用于所有类型的 GitLab 部署:

  • 使用 FILENAME 变量指定输入文件名。默认情况下,脚本查找: /opt/gitlab/embedded/service/gitlab-rails/tmp/orphan_job_artifact_final_objects.csv
  • 当脚本删除文件时,它会写出一个包含已删除文件的 CSV 文件:
    • 文件与输入文件在同一目录中

    • 文件名前缀为 deleted_from--。例如:deleted_from--orphan_job_artifact_final_objects.csv

    • 文件中的行是:object_path,object_size,object_generation/version,例如:

      35/13/35135aaa6cc23891b40cb3f378c53a17a1127210ce60e125ccf03efcfdaec458/@final/1a/1a/5abfa4ec66f1cc3b681a4d430b8b04596cbd636f13cdff44277211778f26,201,1711616743796587

列出具有特定过期时间(或无过期时间)的制品的项目和构建

使用 Rails 控制台,您可以找到具有以下任一情况的作业制品的项目:

  • 没有过期日期。
  • 过期日期在 7 天以上。

类似于删除制品,使用以下示例时间框架并根据需要进行修改:

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

以下每个脚本还将搜索限制为 50 个结果,使用 .limit(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 控制台中运行以下代码,列出前 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 控制台中运行以下代码,列出单个项目中最大的 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) 中的数字。

删除旧的构建和制品

这些命令会永久删除数据。在生产环境中运行它们之前, 您应该先在测试环境中尝试,并制作实例的备份, 以便在需要时可以恢复。

删除项目的旧制品

此步骤还会删除用户选择保留的制品:

project = Project.find_by_full_path('path/to/project')
builds_with_artifacts =  project.builds.with_downloadable_artifacts
builds_with_artifacts.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.each do |build|
    Ci::JobArtifacts::DeleteService.new(build).execute
  end

  batch.update_all(artifacts_expire_at: Time.current)
end

实例范围删除旧制品

此步骤还会删除用户选择保留的制品:

builds_with_artifacts = Ci::Build.with_downloadable_artifacts
builds_with_artifacts.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.each do |build|
    Ci::JobArtifacts::DeleteService.new(build).execute
  end

  batch.update_all(artifacts_expire_at: Time.current)
end

删除项目的旧作业日志和制品

project = Project.find_by_full_path('path/to/project')
builds =  project.builds
admin_user = User.find_by(username: 'username')
builds.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.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
end

实例范围删除旧作业日志和制品

builds = Ci::Build.all
admin_user = User.find_by(username: 'username')
builds.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.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
end

1.year.ago 是一个 Rails ActiveSupport::Duration 方法。 从较长的时间开始,以减少意外删除仍在使用的制品的风险。 根据需要,使用较短的时间重新运行删除,例如 3.months.ago2.weeks.ago7.days.ago

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

删除制品不会立即回收磁盘空间

当制品被删除时,过程分为两个阶段:

  1. 标记为准备删除Ci::JobArtifact 记录从数据库中删除, 并转换为具有未来 pick_up_at 时间戳的 Ci::DeletedObject 记录。
  2. 从存储中移除:制品文件保留在磁盘上,直到 Ci::ScheduleDeleteObjectsCronWorker worker 处理 Ci::DeletedObject 记录并物理删除文件。

删除操作被有意限制以防止压倒系统资源:

  • worker 每小时运行一次,在第 16 分钟。
  • 它以批处理方式处理对象,最多 20 个并发作业。
  • 每个已删除的对象都有一个 pick_up_at 时间戳,确定它何时 有资格进行物理删除

对于大规模删除,物理清理可能需要大量时间 才能完全回收磁盘空间。对于非常大的删除,清理可能需要几天时间。

如果您需要快速回收磁盘空间,可以加快制品删除速度。

加快制品删除

如果您在删除大量制品后需要快速回收磁盘空间, 可以绕过标准调度限制并加快删除过程。

如果您要删除大量制品,这些命令会给您的系统带来巨大负载。

# Set the pick_up_date to the current time on all artifacts
# This will mark them for immediate deletion
Ci::DeletedObject.update_all(pick_up_at: Time.current)

# Get the count of artifacts marked for deletion
Ci::DeletedObject.where("pick_up_at < ?", Time.current)

# Delete the artifacts from disk
while Ci::DeletedObject.where("pick_up_at < ?", Time.current).count > 0
  Ci::DeleteObjectsService.new.execute
  sleep(10)
end

# Get the count of artifacts marked for deletion (should now be zero)
Ci::DeletedObject.count

删除旧的流水线

这些命令会永久删除数据。在生产环境中运行它们之前, 考虑寻求支持工程师的指导。您还应该先在测试环境中尝试它们, 并制作实例的备份,以便在需要时可以恢复。

删除流水线也会删除该流水线的:

  • 作业制品
  • 作业日志
  • 作业元数据
  • 流水线元数据

删除作业和流水线元数据有助于减少数据库中 CI 表的大小。 CI 表通常是实例数据库中最大的表。

删除项目的旧流水线

project = Project.find_by_full_path('path/to/project')
user = User.find(1)
project.ci_pipelines.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.each do |pipeline|
    puts "Erasing pipeline #{pipeline.id}"
    Ci::DestroyPipelineService.new(pipeline.project, user).execute(pipeline)
  end
end

实例范围删除旧流水线

user = User.find(1)
Ci::Pipeline.where("finished_at < ?", 1.year.ago).each_batch do |batch|
  batch.each do |pipeline|
    puts "Erasing pipeline #{pipeline.id} for project #{pipeline.project_id}"
    Ci::DestroyPipelineService.new(pipeline.project, user).execute(pipeline)
  end
end

作业制品上传失败,出现错误 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 挂载,当运行器尝试上传制品时,您可能会看到 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。
  • 升级到至少 Linux 内核 5.15,其中包含许多重要的错误修复, 与 CIFS 文件租约相关。
  • 对于较旧的内核,使用 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。重新计算后, 使用情况应该再次正常显示。

制品下载流程图

以下流程图说明了作业制品的工作原理。这些 图表假设已为作业制品配置了对象存储。

代理下载已禁用

proxy_download 设置为 false时,GitLab 将运行器重定向到使用预签名 URL 从对象存储下载制品。 运行器直接从源获取通常更快,因此通常推荐此配置。 它还应该减少带宽使用,因为数据不必由 GitLab 获取并发送给运行器。但是,它确实需要 给予运行器对对象存储的直接访问。

请求流程如下:

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. 首先,运行器尝试使用 GET /api/v4/jobs/:id/artifacts 端点获取作业制品。运行器附加 direct_download=true 查询参数在第一次尝试上,以指示 它能够直接从对象存储下载。直接 下载可以通过运行器配置中的 FF_USE_DIRECT_DOWNLOAD 功能标志禁用。 此标志默认设置为 true

  2. 运行器使用 HTTP 基本身份验证发送 GET 请求, 用户名为 gitlab-ci-token,密码为自动生成的 CI/CD 作业令牌。此令牌由 GitLab 生成, 并在作业开始时提供给运行器。

  3. GET 请求被传递到 GitLab API,该 API 在 数据库中查找令牌并找到触发作业的用户。

  4. 在步骤 5-8 中:

    • 如果用户有权访问构建,那么 GitLab 会生成 一个预签名 URL 并发送一个 302 重定向,Location 设置为该 URL。运行器遵循 302 重定向并下载制品。

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

    如果运行器收到以下 HTTP 状态码,则不会重试:

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

    但是,如果运行器收到任何其他状态码,如 500 错误, 它会再尝试两次下载制品,每次尝试之间 休眠 1 秒。后续尝试省略 direct_download=true

代理下载已启用

如果 proxy_downloadtrue,GitLab 始终从 对象存储获取制品并将数据发送给运行器,即使 运行器发送 direct_download=true 查询参数。代理 下载可能是可取的,如果运行器的网络访问受限。

下图与禁用代理下载示例类似, 除了在步骤 6-9,GitLab 不会向运行器发送 302 重定向。 相反,GitLab 指示 Workhorse 获取数据并将其 流式传输回运行器。从运行器的角度来看, 对 /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 作为代理服务器,请增加文件上传大小限制,默认限制为 1 MB。 在 NGINX 配置文件中为 client-max-body-size 设置更高的值。