管理员作业制品故障排除
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed
在管理作业制品时,您可能会遇到以下问题。
作业制品占用过多磁盘空间
作业制品可能会比预期更快地占满您的磁盘空间。一些可能的原因是:
- 用户配置的作业制品过期时间过长。
- 运行的作业数量(以及生成的制品数量)高于预期。
- 作业日志比预期大,并且随着时间的推移不断累积。
- 文件系统可能会耗尽 inode,因为制品清理维护留下了空目录。用于孤立制品文件的 Rake 任务会删除这些目录。
- 制品文件可能留在磁盘上,未被清理维护删除。运行用于孤立制品文件的 Rake 任务来删除它们。这个脚本应该总能找到工作要做,因为它还会删除空目录(见上一条原因)。
- 制品清理维护在 GitLab 15.0 到 15.2 中发生了重大变化,您可能需要启用功能标志来使用更新后的系统。
- 保留最近成功作业的最新制品功能已启用。
在这些和其他情况下,找出占用磁盘空间最多的项目,确定哪些类型的制品占用了最多空间,在某些情况下,手动删除作业制品以回收磁盘空间。
制品清理维护
制品清理维护是识别哪些制品已过期并可以删除的过程。
GitLab 15.0 到 15.2 中清理维护被禁用
制品清理维护在 GitLab 15.0 中得到了显著改进,引入了默认禁用的功能标志。这些标志在GitLab 15.3中默认启用。
如果制品清理维护在 GitLab 15.0 到 GitLab 15.2 中似乎没有工作,您应该检查功能标志是否已启用。
要检查功能标志是否已启用:
-
启动 Rails 控制台。
-
检查功能标志是否已启用。
Feature.enabled?(:ci_detect_wrongly_expired_artifacts) Feature.enabled?(:ci_update_unlocked_job_artifacts) Feature.enabled?(:ci_job_artifacts_backlog_work) -
如果任何功能标志被禁用,请启用它们:
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 的制品:
-
启动数据库控制台:
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 mainsudo docker exec -it <container_name> /bin/bash gitlab-psqlsudo -u git -H psql -d gitlabhq_production -
运行以下查询:
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:
-
启动 Rails 控制台。
-
检查该功能是否已启用。
Feature.enabled?(:ci_job_artifacts_backlog_work) -
如果需要,启用该功能:
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 实例管理员可能会看到制品使用的对象存储增加。按照此过程检查并删除这些制品。
删除文件是一个两阶段过程:
列出孤立的作业制品
sudo gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objectsdocker 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_objectsdocker 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_now10.days.from_now2.weeks.from_now3.months.from_now1.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
end1.year.ago 是一个 Rails ActiveSupport::Duration 方法。
从较长的时间开始,以减少意外删除仍在使用的制品的风险。
根据需要,使用较短的时间重新运行删除,例如 3.months.ago、2.weeks.ago 或 7.days.ago。
方法 erase_erasable_artifacts! 是同步的,执行后制品会立即删除;
它们不是由后台队列调度的。
删除制品不会立即回收磁盘空间
当制品被删除时,过程分为两个阶段:
- 标记为准备删除:
Ci::JobArtifact记录从数据库中删除, 并转换为具有未来pick_up_at时间戳的Ci::DeletedObject记录。 - 从存储中移除:制品文件保留在磁盘上,直到
Ci::ScheduleDeleteObjectsCronWorkerworker 处理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
在此图表中:
-
首先,运行器尝试使用
GET /api/v4/jobs/:id/artifacts端点获取作业制品。运行器附加direct_download=true查询参数在第一次尝试上,以指示 它能够直接从对象存储下载。直接 下载可以通过运行器配置中的FF_USE_DIRECT_DOWNLOAD功能标志禁用。 此标志默认设置为true。 -
运行器使用 HTTP 基本身份验证发送 GET 请求, 用户名为
gitlab-ci-token,密码为自动生成的 CI/CD 作业令牌。此令牌由 GitLab 生成, 并在作业开始时提供给运行器。 -
GET 请求被传递到 GitLab API,该 API 在 数据库中查找令牌并找到触发作业的用户。
-
在步骤 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_download 为 true,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设置更高的值。