作业产物管理

本文是管理员文档。要了解如何在 GitLab CI/CD 流水线中使用作业产物,请参阅作业产物配置文档

产物是作业完成后附加到作业的文件和目录列表。在所有极狐GitLab 安装中默认启用此功能。

禁用作业产物

要在站点范围内禁用产物:

Omnibus 安装实例:

  1. 编辑 /etc/gitlab/gitlab.rb 并添加以下内容:

    gitlab_rails['artifacts_enabled'] = false
    
  2. 保存文件并重新配置极狐GitLab,使更改生效。

源安装实例:

  1. 编辑 /home/git/gitlab/config/gitlab.yml 并添加或修改以下几行:

    artifacts:
      enabled: false
    
  2. 保存文件并重启极狐GitLab,使更改生效。

存储作业产物

GitLab Runner 可以将包含作业产物的存档上传到极狐GitLab。默认情况下,这是在作业成功时完成的,但也可以在失败时完成,或者总是使用 artifacts:when 参数完成。

大多数产物在发送到 coordinator 之前由 GitLab Runner 压缩。报告产物例外,它们在上传后被压缩。

使用本地存储

要更改产物在本地存储的位置,请按照以下步骤操作。

Omnibus 安装实例:

产物默认存储在 /var/opt/gitlab/gitlab-rails/shared/artifacts 中。

  1. 例如,要将存储路径更改为 /mnt/storage/artifacts,请编辑 /etc/gitlab/gitlab.rb 并添加以下行:

    gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
    
  2. 保存文件并重新配置极狐GitLab,使更改生效。

源安装实例:

产物默认存储在 /home/git/gitlab/shared/artifacts 中。

  1. 要将存储路径更改为例如 /mnt/storage/artifacts,请编辑 /home/git/gitlab/config/gitlab.yml 并添加或修改以下行:

    artifacts:
      enabled: true
      path: /mnt/storage/artifacts
    
  2. 保存文件并重启极狐GitLab,使更改生效。

使用对象存储

如果您不想使用安装极狐GitLab 的本地磁盘来存储产物,则可以改用像 AWS S3 这样的对象存储。 此配置依赖于已配置的有效 AWS 凭证。 使用像 AWS S3 这样的对象存储选项来存储作业产物。

如果您将极狐GitLab 配置为在对象存储上存储产物,您可能还想禁用作业日志使用本地磁盘。 在这两种情况下,作业日志都会在作业完成时存档并移动到对象存储中。

caution在多服务器设置中,您必须使用选项之一来禁用作业日志使用本地磁盘,否则作业日志可能会丢失。

对象存储设置

note我们建议使用整合对象存储设置。本节介绍较早的配置格式。

对于源安装,以下设置嵌套在 artifacts:object_store: 下。在 Omnibus GitLab 安装中,它们以 artifacts_object_store_ 为前缀。

设置 默认 描述
enabled false 启用或禁用对象存储。
remote_directory   存储产物的存储桶名称。仅使用名称,不包括路径。
proxy_download false 设置为 true 启用代理服务的所有文件。此选项允许减少出口流量,因为允许客户端直接从远端存储下载而不是代理所有数据。
connection   下面描述了各种连接选项。

连接设置

Omnibus 安装实例:

产物默认存储在 /var/opt/gitlab/gitlab-rails/shared/artifacts 中。

  1. 编辑 /etc/gitlab/gitlab.rb 并添加以下行,替换您想要的值:

    gitlab_rails['artifacts_enabled'] = true
    gitlab_rails['artifacts_object_store_enabled'] = true
    gitlab_rails['artifacts_object_store_remote_directory'] = "artifacts"
    gitlab_rails['artifacts_object_store_connection'] = {
      'provider' => 'AWS',
      'region' => 'eu-central-1',
      'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
      'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
    }
    
    note如果您使用 AWS IAM 配置文件,请省略 AWS 访问密钥和秘密访问密钥/值对。 例如:
    gitlab_rails['artifacts_object_store_connection'] = {
      'provider' => 'AWS',
      'region' => 'eu-central-1',
      'use_iam_profile' => true
    }
    
  2. 保存文件并重新配置极狐GitLab,使更改生效。
  3. 将任何现有的本地产物迁移到对象存储

源安装实例:

产物默认存储在 /home/git/gitlab/shared/artifacts 中。

  1. 编辑 /home/git/gitlab/config/gitlab.yml 并添加或修改以下几行:

    artifacts:
      enabled: true
      object_store:
        enabled: true
        remote_directory: "artifacts"  # The bucket name
        connection:
          provider: AWS  # Only AWS supported at the moment
          aws_access_key_id: AWS_ACCESS_KEY_ID
          aws_secret_access_key: AWS_SECRET_ACCESS_KEY
          region: eu-central-1
    
  2. 保存文件并重启极狐GitLab,使更改生效。
  3. 将任何现有的本地产物迁移到对象存储

迁移到对象存储

配置对象存储后,使用以下任务将现有作业产物从本地存储迁移到远程存储。 处理在后台 worker 中完成,要求无停机时间

Omnibus 安装实例:

gitlab-rake gitlab:artifacts:migrate

源安装实例:

sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production

您可以选择使用 PostgreSQL 控制台跟踪进度并验证所有作业产物是否已成功迁移 :

  • Omnibus 安装(14.1 及更早版本):sudo gitlab-rails dbconsole
  • Omnibus 安装(14.2 及更高版本):sudo gitlab-rails dbconsole --database main
  • 源安装实例:sudo -u git -H psql -d gitlabhq_production

验证下面的 objectstg(其中 store=2)具有所有作业产物的计数:

gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM ci_job_artifacts;

total | filesystem | objectstg
------+------------+-----------
   19 |          0 |        19

验证 artifacts 文件夹中的磁盘上没有文件:

sudo find /var/opt/gitlab/gitlab-rails/shared/artifacts -type f | grep -v tmp | wc -l

从对象存储迁移到本地存储

Omnibus 安装实例:

迁移回本地存储:

  1. 运行 gitlab-rake gitlab:artifacts:migrate_to_local
  2. gitlab.rb 中的产物禁用 object_storage:
    • 设置 gitlab_rails['artifacts_object_store_enabled'] = false
    • 注释掉所有其他 artifacts_object_store 设置,包括整个 artifacts_object_store_connection 部分,包括关闭的 }
  3. 重新配置极狐GitLab

过期产物

如果 artifacts:expire_in 用于设置产物的到期时间,则在该日期过后将立即将其标记为删除。 否则,它们会根据默认产物过期设置过期。

产物由 Sidekiq 每 7 分钟运行一次的 expire_build_artifacts_worker cron 作业清理(*/7 * * * *)。

要更改产物过期的默认计划,请按照以下步骤操作。

Omnibus 安装实例:

  1. 编辑 /etc/gitlab/gitlab.rb 并添加以下行(如果它已经存在并被注释掉,则取消注释),用 cron 语法替换您的计划:

    gitlab_rails['expire_build_artifacts_worker_cron'] = "*/7 * * * *"
    
  2. 保存文件并重新配置极狐GitLab,使更改生效。

源安装实例:

  1. 编辑 /home/git/gitlab/config/gitlab.yml 并添加或修改以下几行:

    expire_build_artifacts_worker:
      cron: "*/7 * * * *"
    
  2. 保存文件并重启极狐GitLab,使更改生效。

如果您的流水线中未明确设置 expire 指令,则产物会根据默认产物到期设置过期,您可以在 CI/CD 管理设置 中找到该设置。

设置产物的最大文件大小

如果启用了产物,您可以通过管理中心设置,更改 产物的最大文件大小。

存储统计

您可以在管理中心,以及通过群组和项目 API 查看用于群组和项目上的作业产物的总存储空间。

实施细则

当极狐GitLab 收到产物存档时,GitLab Workhorse 也会生成存档元数据文件。此元数据文件描述了位于产物存档本身中的所有条目。 元数据文件采用二进制格式,带有额外的 Gzip 压缩。

极狐GitLab 不提取产物存档,节省空间、内存和磁盘 I/O。相反,它会检查包含所有相关信息的元数据文件。当存在大量产物或存档文件非常大时,这一点尤其重要。

单击特定文件时,GitLab Workhorse 从存档中提取它并开始下载。此实现可节省空间、内存和磁盘 I/O。

故障排查

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

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

  • 用户已将作业产物的到期时间配置为比需要的时间长。
  • 运行的作业数量以及由此产生的产物数量高于预期。
  • 作业日志比预期的要大,并且随着时间的推移而累积。
  • 文件系统可能会用完 inodes,因为产物理性维护处理留下了空目录。 Orphan 产物文件的 Rake 任务可以删除这些文件。
  • 产物文件可能会留在磁盘上,不会被例行维护删除。运行 Orphan 产物文件的 Rake 任务删除它们。这个脚本也会删除空目录(见上文)。
  • 产物例行维护功能发生了重大变化,您可能需要启用功能标志才能使用更新后的系统。

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

产物例行维护功能在 14.6 到 15.2 版本禁用

产物例行维护功能在 14.10 版本发生了重大变化,这些变化被移植回 14.6 及更高版本。在 15.3 版本之前,必须使用功能标志启用更新的例行维护功能。

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

  1. 启动 Rails 控制台

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

    • 14.10 及更早版本:

      Feature.enabled?(:ci_detect_wrongly_expired_artifacts, default_enabled: :yaml)
      Feature.enabled?(:ci_update_unlocked_job_artifacts, default_enabled: :yaml)
      Feature.enabled?(:ci_destroy_unlocked_job_artifacts, default_enabled: :yaml)
      
    • 15.0 及更高版本:

      Feature.enabled?(:ci_detect_wrongly_expired_artifacts)
      Feature.enabled?(:ci_update_unlocked_job_artifacts)
      Feature.enabled?(:ci_destroy_unlocked_job_artifacts)
      
  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 的产物:

  1. 在 Omnibus 上启动数据库控制台:

    sudo gitlab-psql
    
  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

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

可以在运行 14.10 及更高版本的私有化部署实例上启用 worker:

  1. 启动 Rails 控制台

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

    • 14.10 版本:

      Feature.enabled?(:ci_job_artifacts_backlog_work, default_enabled: :yaml)
      
    • 15.0 及更高版本:

      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

以下每个脚本还使用 .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

错误 Downloading artifacts from coordinator... not found

当作业尝试从较早的作业下载产物时,您可能会收到类似于以下内容的错误消息:

Downloading artifacts from coordinator... not found  id=12345678 responseStatus=404 Not Found

这可能是由具有以下配置的 gitlab.rb 文件引起的:

gitlab_rails['artifacts_object_store_background_upload'] = false
gitlab_rails['artifacts_object_store_direct_upload'] = true

为了防止这种情况,注释掉或删除这些行,或切换到它们的默认值,然后运行 sudo gitlab-ctl reconfigure

作业产物上传失败,错误 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

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