InfoGrab Docs

관리자를 위한 작업 아티팩트 트러블슈팅

요약

작업 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다. GitLab 18.6 이전에는 원격 저장소에서 로컬 저장소로 마이그레이션할 때 잘못된 파일명으로 아티팩트가 복사될 수 있었습니다. GitLab 인스턴스에서 이런 상황이 발생했다면 다음을 실행합니다:

작업 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다.

작업 아티팩트의 파일명이 잘못될 수 있음#

GitLab 18.6 이전에는 원격 저장소에서 로컬 저장소로 마이그레이션할 때 잘못된 파일명으로 아티팩트가 복사될 수 있었습니다.

예를 들어:

  • 아티팩트는 path/to/artifacts/2025_10_15/922/485/artifacts.zip과 유사하게 보여야 합니다.
  • 잘못된 파일명의 아티팩트는 path/to/artifacts/2025_10_15/922/485/4f8681af93715b90c913e507f24b05cc6ca6e (.zip 확장자 없음)처럼 보입니다.

GitLab 인스턴스에서 이런 상황이 발생했다면 다음을 실행합니다:

gitlab-rake gitlab:artifacts:fix_artifact_filepath

이 작업은 잘못된 파일명을 가진 로컬 저장소의 아티팩트를 확인하고 예상되는 파일명으로 이름을 변경합니다.

작업 아티팩트가 디스크 공간을 너무 많이 사용함#

작업 아티팩트는 예상보다 빠르게 디스크 공간을 채울 수 있습니다. 가능한 원인은 다음과 같습니다:

이러한 경우와 다른 경우에 디스크 공간 사용에 가장 많이 기여하는 프로젝트를 파악하고, 어떤 유형의 아티팩트가 가장 많은 공간을 사용하는지 확인하고, 경우에 따라 디스크 공간을 회수하기 위해 작업 아티팩트를 수동으로 삭제합니다.

아티팩트 하우스키핑#

아티팩트 하우스키핑은 만료된 아티팩트를 식별하고 삭제하는 프로세스입니다.

unknown 상태 아티팩트 확인#

일부 아티팩트는 하우스키핑 시스템이 올바른 잠금 상태를 결정할 수 없기 때문에 unknown 상태를 가집니다. 이러한 아티팩트는 만료된 후에도 자동 정리에서 처리되지 않아 과도한 디스크 공간 사용에 기여할 수 있습니다.

인스턴스에 unknown 상태의 아티팩트가 있는지 확인하려면:

  1. 데이터베이스 콘솔을 시작합니다:

   sudo gitlab-psql
   # toolbox 파드 찾기
   kubectl --namespace <namespace> get pods -lapp=toolbox
   # PostgreSQL 콘솔에 연결
   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
  1. 다음 쿼리를 실행합니다:

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

잠금 상태가 2인 레코드가 반환되면 이는 unknown 아티팩트입니다. 예를 들어:

           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

unknown 아티팩트가 있는 경우 더 짧은 만료 시간을 설정하거나 수동으로 제거하여 디스크 공간을 회수할 수 있습니다.

unknown 아티팩트 정리#

unknown 아티팩트를 정리하려면 더 짧은 만료 시간을 설정하면 자동 정리 프로세스가 이를 처리할 수 있습니다:

  1. Rails 콘솔을 시작합니다.

  2. unknown 아티팩트의 만료를 현재 시간으로 설정합니다:

    # unknown 아티팩트를 즉시 정리하도록 표시
    Ci::JobArtifact.where(locked: 2).update_all(expire_at: Time.current)
    

자동 하우스키핑 프로세스가 다음 실행 시 이러한 아티팩트를 정리합니다.

개체 저장소에서 @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
# 파드 찾기
kubectl get pods --namespace <namespace> -lapp=toolbox

# Rails 콘솔 열기
kubectl exec -it -c toolbox <toolbox-pod-name> bash
gitlab-rake gitlab:cleanup:list_orphan_job_artifact_final_objects

명령이 완료되면 파일을 세션에서 영구 저장소로 복사합니다.

Rake 작업에는 모든 유형의 GitLab 배포에 적용되는 몇 가지 추가 기능이 있습니다:

고아 작업 아티팩트 삭제#
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
# 파드 찾기
kubectl get pods --namespace <namespace> -lapp=toolbox

# Rails 콘솔 열기
kubectl exec -it -c toolbox <toolbox-pod-name> bash
gitlab-rake gitlab:cleanup:delete_orphan_job_artifact_final_objects
  • 명령이 완료되면 파일을 세션에서 영구 저장소로 복사합니다.

다음은 모든 유형의 GitLab 배포에 적용됩니다:

특정 만료 날짜(또는 만료 없음)의 아티팩트를 가진 프로젝트와 빌드 목록#

Rails 콘솔을 사용하여 다음 조건에 해당하는 작업 아티팩트를 가진 프로젝트를 찾을 수 있습니다:

아티팩트 삭제와 유사하게 다음 예시 기간을 사용하고 필요에 맞게 조정합니다:

다음 스크립트는 .limit(50)으로 검색을 50개로 제한하지만 필요에 따라 변경할 수 있습니다:

# 절대 만료되지 않는 아티팩트가 있는 빌드 및 프로젝트 찾기
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

# 오늘부터 7일 이후에 만료되는 아티팩트가 있는 빌드 및 프로젝트 찾기
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)을 원하는 숫자로 수정하여 나열되는 작업 아티팩트 수를 변경할 수 있습니다.

단일 프로젝트의 아티팩트 목록#

단일 프로젝트의 아티팩트를 아티팩트 크기 기준으로 정렬하여 나열합니다. 출력에는 다음이 포함됩니다:

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)의 숫자를 변경합니다.

오래된 빌드 및 아티팩트 삭제#

Warning

이 명령은 데이터를 영구적으로 제거합니다. 프로덕션 환경에서 실행하기 전에 테스트 환경에서 먼저 시도하고 필요한 경우 복원할 수 있는 인스턴스 백업을 만드세요.

프로젝트에 대한 오래된 아티팩트 삭제#

이 단계는 사용자가 유지하기로 선택한 아티팩트도 지웁니다:

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.ago, 2.weeks.ago, 7.days.ago)으로 재실행합니다.

erase_erasable_artifacts! 메서드는 동기적으로 실행되며, 실행 시 아티팩트가 즉시 제거됩니다; 백그라운드 큐로 예약되지 않습니다.

아티팩트 삭제 후 디스크 공간이 즉시 회수되지 않음#

아티팩트가 삭제될 때 프로세스는 두 단계로 진행됩니다:

  1. 삭제 준비 표시: Ci::JobArtifact 레코드가 데이터베이스에서 제거되고 미래 pick_up_at 타임스탬프와 함께 Ci::DeletedObject 레코드로 변환됩니다.
  2. 저장소에서 제거: Ci::ScheduleDeleteObjectsCronWorker 워커가 Ci::DeletedObject 레코드를 처리하고 물리적으로 파일을 제거할 때까지 아티팩트 파일이 디스크에 남아있습니다.

시스템 리소스를 압도하지 않기 위해 제거가 의도적으로 제한됩니다:

대규모 삭제의 경우 디스크 공간이 완전히 회수되기 전에 물리적 정리가 상당한 시간이 걸릴 수 있습니다. 매우 큰 삭제의 경우 정리에 며칠이 걸릴 수 있습니다.

디스크 공간을 빠르게 회수해야 하는 경우 아티팩트 삭제를 가속화할 수 있습니다.

아티팩트 제거 가속화#

많은 수의 아티팩트를 삭제한 후 디스크 공간을 빠르게 회수해야 하는 경우 표준 스케줄링 제한을 우회하여 삭제 프로세스를 가속화할 수 있습니다.

Warning

이 명령은 많은 수의 아티팩트를 삭제하는 경우 시스템에 상당한 부하를 줍니다.

# 모든 아티팩트의 pick_up_date를 현재 시간으로 설정
# 즉시 삭제하도록 표시됨
Ci::DeletedObject.update_all(pick_up_at: Time.current)

# 삭제 표시된 아티팩트 수 가져오기
Ci::DeletedObject.where("pick_up_at < ?", Time.current)

# 디스크에서 아티팩트 삭제
while Ci::DeletedObject.where("pick_up_at < ?", Time.current).count > 0
  Ci::DeleteObjectsService.new.execute
  sleep(10)
end

# 삭제 표시된 아티팩트 수 가져오기 (이제 0이어야 함)
Ci::DeletedObject.count

오래된 파이프라인 삭제#

Warning

이 명령은 데이터를 영구적으로 제거합니다. 프로덕션 환경에서 실행하기 전에 지원 엔지니어의 안내를 구하는 것을 고려하세요. 테스트 환경에서 먼저 시도하고 필요한 경우 복원할 수 있는 인스턴스 백업을 만드세요.

파이프라인을 삭제하면 해당 파이프라인의 다음 항목도 제거됩니다:

작업 및 파이프라인 메타데이터를 제거하면 데이터베이스의 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)#

아티팩트에 개체 저장소를 사용 중이고 작업 아티팩트 업로드가 실패하는 경우 다음을 검토합니다:

두 경우 모두 작업 아티팩트 개체 저장소 구성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//artifacts: 500 Internal Server Error  id=1296 responseStatus=500 Internal Server Error status=500 token=*****
FATAL: invalid argument

이 문제를 해결하려면 다음을 시도합니다:

자세한 내용은 조사 세부 정보를 참조하세요.

사용 쿼터에 잘못된 아티팩트 저장소 사용량 표시#

아티팩트 저장소 사용량이 아티팩트가 사용하는 총 저장 공간에 대해 잘못된 값을 표시하는 경우가 있습니다. 인스턴스의 모든 프로젝트에 대해 아티팩트 사용 통계를 다시 계산하려면 다음 백그라운드 스크립트를 실행할 수 있습니다:

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_downloadfalse로 설정되면 GitLab은 미리 서명된 URL로 개체 저장소에서 아티팩트를 다운로드하도록 러너를 리다이렉트합니다. 러너가 소스에서 직접 가져오는 것이 보통 더 빠르므로 이 구성이 일반적으로 권장됩니다. 또한 데이터를 GitLab이 가져와 러너로 보낼 필요가 없으므로 대역폭 사용량을 줄여야 합니다. 그러나 러너가 개체 저장소에 직접 액세스해야 합니다.

요청 흐름은 다음과 같습니다:

Mermaid 다이어그램 (26줄)
소스 코드 보기
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
    accTitle: Direct artifact download flow
    accDescr: Runner authenticates, gets redirected to object storage, and downloads artifacts directly.
autonumber
participant C as Runner
participant O as Object Storage
participant W as Workhorse
participant R as Rails
participant P as PostgreSQL
C-&gt;&gt;+W: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over C,W: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
W--&gt;+R: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over W,R: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
R-&gt;&gt;P: Look up job for CI_JOB_TOKEN
R-&gt;&gt;P: Find user who triggered job
R-&gt;&gt;R: Does user have :read_build access?
alt Yes
  R-&gt;&gt;W: Send 302 redirect to object storage presigned URL
  R-&gt;&gt;C: 302 redirect
  C-&gt;&gt;O: GET &lt;presigned URL&gt;
else No
  R-&gt;&gt;W: 401 Unauthorized
  W-&gt;&gt;C: 401 Unauthorized
end</code></pre></details></div>

이 다이어그램에서:

  1. 먼저 러너는 GET /api/v4/jobs/:id/artifacts 엔드포인트를 사용하여 작업 아티팩트를 가져오려고 합니다. 러너는 첫 번째 시도에서 개체 저장소에서 직접 다운로드할 수 있음을 나타내기 위해 direct_download=true 쿼리 파라미터를 첨부합니다. 직접 다운로드는 러너 구성에서 FF_USE_DIRECT_DOWNLOAD 기능 플래그를 통해 비활성화할 수 있습니다. 이 플래그는 기본적으로 true로 설정됩니다.

  2. 러너는 gitlab-ci-token 사용자 이름과 자동 생성된 CI/CD 작업 토큰을 비밀번호로 사용하는 HTTP 기본 인증으로 GET 요청을 보냅니다. 이 토큰은 GitLab이 생성하고 작업 시작 시 러너에게 제공됩니다.

  3. GET 요청이 GitLab API로 전달되어 데이터베이스에서 토큰을 조회하고 작업을 트리거한 사용자를 찾습니다.

  4. 5-8 단계에서:

    • 사용자가 빌드에 액세스할 수 있으면 GitLab이 미리 서명된 URL을 생성하고 해당 URL로 Location이 설정된 302 리다이렉트를 보냅니다. 러너는 302 리다이렉트를 따라 아티팩트를 다운로드합니다.

    • 작업을 찾을 수 없거나 사용자가 작업에 액세스할 수 없으면 API가 401 Unauthorized를 반환합니다.

    러너는 다음 HTTP 상태 코드를 받으면 재시도하지 않습니다:

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

    그러나 러너가 500 오류와 같은 다른 상태 코드를 받으면 각 시도 사이에 1초를 기다리며 두 번 더 아티팩트 다운로드를 재시도합니다. 후속 시도는 direct_download=true를 생략합니다.

프록시 다운로드 활성화#

proxy_downloadtrue이면 러너가 direct_download=true 쿼리 파라미터를 보내더라도 GitLab은 항상 개체 저장소에서 아티팩트를 가져와 러너로 데이터를 보냅니다. 러너의 네트워크 액세스가 제한된 경우 프록시 다운로드가 바람직할 수 있습니다.

다음 다이어그램은 비활성화된 프록시 다운로드 예시와 유사하지만 6-9 단계에서 GitLab이 러너에게 302 리다이렉트를 보내지 않습니다. 대신 GitLab이 Workhorse에게 데이터를 가져와 러너로 스트리밍하도록 지시합니다. 러너 관점에서 /api/v4/jobs/:id/artifacts에 대한 원래 GET 요청이 이진 데이터를 직접 반환합니다.

Mermaid 다이어그램 (27줄)
소스 코드 보기
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
    accTitle: Proxied artifact download flow
    accDescr: Runner authenticates, GitLab fetches from object storage, and streams artifacts back.
autonumber
participant C as Runner
participant O as Object Storage
participant W as Workhorse
participant R as Rails
participant P as PostgreSQL
C-&gt;&gt;+W: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over C,W: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
W--&gt;+R: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over W,R: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
R-&gt;&gt;P: Look up job for CI_JOB_TOKEN
R-&gt;&gt;P: Find user who triggered job
R-&gt;&gt;R: Does user have :read_build access?
alt Yes
  R-&gt;&gt;W: SendURL with object storage presigned URL
  W-&gt;&gt;O: GET &lt;presigned URL&gt;
  O-&gt;&gt;W: &lt;artifacts data&gt;
  W-&gt;&gt;C: &lt;artifacts data&gt;
else No
  R-&gt;&gt;W: 401 Unauthorized
  W-&gt;&gt;C: 401 Unauthorized
end</code></pre></details></div>

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에 더 높은 값을 설정합니다.

관리자를 위한 작업 아티팩트 트러블슈팅

Tier: Free, Premium, Ultimate
Offering: GitLab Self-Managed
원문 보기
요약

작업 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다. GitLab 18.6 이전에는 원격 저장소에서 로컬 저장소로 마이그레이션할 때 잘못된 파일명으로 아티팩트가 복사될 수 있었습니다. GitLab 인스턴스에서 이런 상황이 발생했다면 다음을 실행합니다:

작업 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다.

작업 아티팩트의 파일명이 잘못될 수 있음#

GitLab 18.6 이전에는 원격 저장소에서 로컬 저장소로 마이그레이션할 때 잘못된 파일명으로 아티팩트가 복사될 수 있었습니다.

예를 들어:

  • 아티팩트는 path/to/artifacts/2025_10_15/922/485/artifacts.zip과 유사하게 보여야 합니다.
  • 잘못된 파일명의 아티팩트는 path/to/artifacts/2025_10_15/922/485/4f8681af93715b90c913e507f24b05cc6ca6e (.zip 확장자 없음)처럼 보입니다.

GitLab 인스턴스에서 이런 상황이 발생했다면 다음을 실행합니다:

gitlab-rake gitlab:artifacts:fix_artifact_filepath

이 작업은 잘못된 파일명을 가진 로컬 저장소의 아티팩트를 확인하고 예상되는 파일명으로 이름을 변경합니다.

작업 아티팩트가 디스크 공간을 너무 많이 사용함#

작업 아티팩트는 예상보다 빠르게 디스크 공간을 채울 수 있습니다. 가능한 원인은 다음과 같습니다:

이러한 경우와 다른 경우에 디스크 공간 사용에 가장 많이 기여하는 프로젝트를 파악하고, 어떤 유형의 아티팩트가 가장 많은 공간을 사용하는지 확인하고, 경우에 따라 디스크 공간을 회수하기 위해 작업 아티팩트를 수동으로 삭제합니다.

아티팩트 하우스키핑#

아티팩트 하우스키핑은 만료된 아티팩트를 식별하고 삭제하는 프로세스입니다.

unknown 상태 아티팩트 확인#

일부 아티팩트는 하우스키핑 시스템이 올바른 잠금 상태를 결정할 수 없기 때문에 unknown 상태를 가집니다. 이러한 아티팩트는 만료된 후에도 자동 정리에서 처리되지 않아 과도한 디스크 공간 사용에 기여할 수 있습니다.

인스턴스에 unknown 상태의 아티팩트가 있는지 확인하려면:

  1. 데이터베이스 콘솔을 시작합니다:

   sudo gitlab-psql
   # toolbox 파드 찾기
   kubectl --namespace <namespace> get pods -lapp=toolbox
   # PostgreSQL 콘솔에 연결
   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
  1. 다음 쿼리를 실행합니다:

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

잠금 상태가 2인 레코드가 반환되면 이는 unknown 아티팩트입니다. 예를 들어:

           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

unknown 아티팩트가 있는 경우 더 짧은 만료 시간을 설정하거나 수동으로 제거하여 디스크 공간을 회수할 수 있습니다.

unknown 아티팩트 정리#

unknown 아티팩트를 정리하려면 더 짧은 만료 시간을 설정하면 자동 정리 프로세스가 이를 처리할 수 있습니다:

  1. Rails 콘솔을 시작합니다.

  2. unknown 아티팩트의 만료를 현재 시간으로 설정합니다:

    # unknown 아티팩트를 즉시 정리하도록 표시
    Ci::JobArtifact.where(locked: 2).update_all(expire_at: Time.current)
    

자동 하우스키핑 프로세스가 다음 실행 시 이러한 아티팩트를 정리합니다.

개체 저장소에서 @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
# 파드 찾기
kubectl get pods --namespace <namespace> -lapp=toolbox

# Rails 콘솔 열기
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

  • 환경 변수를 설정하여 다른 파일명을 지정할 수 있습니다:

    # 패키지된 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
# 파드 찾기
kubectl get pods --namespace <namespace> -lapp=toolbox

# Rails 콘솔 열기
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

다음 스크립트는 .limit(50)으로 검색을 50개로 제한하지만 필요에 따라 변경할 수 있습니다:

# 절대 만료되지 않는 아티팩트가 있는 빌드 및 프로젝트 찾기
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

# 오늘부터 7일 이후에 만료되는 아티팩트가 있는 빌드 및 프로젝트 찾기
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)의 숫자를 변경합니다.

오래된 빌드 및 아티팩트 삭제#

Warning

이 명령은 데이터를 영구적으로 제거합니다. 프로덕션 환경에서 실행하기 전에 테스트 환경에서 먼저 시도하고 필요한 경우 복원할 수 있는 인스턴스 백업을 만드세요.

프로젝트에 대한 오래된 아티팩트 삭제#

이 단계는 사용자가 유지하기로 선택한 아티팩트도 지웁니다:

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.ago, 2.weeks.ago, 7.days.ago)으로 재실행합니다.

erase_erasable_artifacts! 메서드는 동기적으로 실행되며, 실행 시 아티팩트가 즉시 제거됩니다; 백그라운드 큐로 예약되지 않습니다.

아티팩트 삭제 후 디스크 공간이 즉시 회수되지 않음#

아티팩트가 삭제될 때 프로세스는 두 단계로 진행됩니다:

  1. 삭제 준비 표시: Ci::JobArtifact 레코드가 데이터베이스에서 제거되고 미래 pick_up_at 타임스탬프와 함께 Ci::DeletedObject 레코드로 변환됩니다.
  2. 저장소에서 제거: Ci::ScheduleDeleteObjectsCronWorker 워커가 Ci::DeletedObject 레코드를 처리하고 물리적으로 파일을 제거할 때까지 아티팩트 파일이 디스크에 남아있습니다.

시스템 리소스를 압도하지 않기 위해 제거가 의도적으로 제한됩니다:

  • 워커는 16분 지점에서 시간당 한 번 실행됩니다.
  • 최대 20개의 동시 작업으로 객체를 배치로 처리합니다.
  • 각 삭제된 객체에는 물리적 삭제 가능 시점을 결정하는 pick_up_at 타임스탬프가 있습니다.

대규모 삭제의 경우 디스크 공간이 완전히 회수되기 전에 물리적 정리가 상당한 시간이 걸릴 수 있습니다. 매우 큰 삭제의 경우 정리에 며칠이 걸릴 수 있습니다.

디스크 공간을 빠르게 회수해야 하는 경우 아티팩트 삭제를 가속화할 수 있습니다.

아티팩트 제거 가속화#

많은 수의 아티팩트를 삭제한 후 디스크 공간을 빠르게 회수해야 하는 경우 표준 스케줄링 제한을 우회하여 삭제 프로세스를 가속화할 수 있습니다.

Warning

이 명령은 많은 수의 아티팩트를 삭제하는 경우 시스템에 상당한 부하를 줍니다.

# 모든 아티팩트의 pick_up_date를 현재 시간으로 설정
# 즉시 삭제하도록 표시됨
Ci::DeletedObject.update_all(pick_up_at: Time.current)

# 삭제 표시된 아티팩트 수 가져오기
Ci::DeletedObject.where("pick_up_at < ?", Time.current)

# 디스크에서 아티팩트 삭제
while Ci::DeletedObject.where("pick_up_at < ?", Time.current).count > 0
  Ci::DeleteObjectsService.new.execute
  sleep(10)
end

# 삭제 표시된 아티팩트 수 가져오기 (이제 0이어야 함)
Ci::DeletedObject.count

오래된 파이프라인 삭제#

Warning

이 명령은 데이터를 영구적으로 제거합니다. 프로덕션 환경에서 실행하기 전에 지원 엔지니어의 안내를 구하는 것을 고려하세요. 테스트 환경에서 먼저 시도하고 필요한 경우 복원할 수 있는 인스턴스 백업을 만드세요.

파이프라인을 삭제하면 해당 파이프라인의 다음 항목도 제거됩니다:

  • 작업 아티팩트
  • 작업 로그
  • 작업 메타데이터
  • 파이프라인 메타데이터

작업 및 파이프라인 메타데이터를 제거하면 데이터베이스의 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//artifacts: 500 Internal Server Error  id=1296 responseStatus=500 Internal Server Error status=500 token=*****
FATAL: invalid argument

이 문제를 해결하려면 다음을 시도합니다:

  • CIFS 대신 ext4 마운트로 전환합니다.
  • CIFS 파일 임대와 관련된 여러 중요한 버그 수정이 포함된 최소 Linux 커널 5.15로 업그레이드합니다.
  • 구 커널의 경우 파일 임대를 비활성화하는 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_downloadfalse로 설정되면 GitLab은 미리 서명된 URL로 개체 저장소에서 아티팩트를 다운로드하도록 러너를 리다이렉트합니다. 러너가 소스에서 직접 가져오는 것이 보통 더 빠르므로 이 구성이 일반적으로 권장됩니다. 또한 데이터를 GitLab이 가져와 러너로 보낼 필요가 없으므로 대역폭 사용량을 줄여야 합니다. 그러나 러너가 개체 저장소에 직접 액세스해야 합니다.

요청 흐름은 다음과 같습니다:

Mermaid 다이어그램 (26줄)
소스 코드 보기
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
    accTitle: Direct artifact download flow
    accDescr: Runner authenticates, gets redirected to object storage, and downloads artifacts directly.
autonumber
participant C as Runner
participant O as Object Storage
participant W as Workhorse
participant R as Rails
participant P as PostgreSQL
C-&gt;&gt;+W: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over C,W: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
W--&gt;+R: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over W,R: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
R-&gt;&gt;P: Look up job for CI_JOB_TOKEN
R-&gt;&gt;P: Find user who triggered job
R-&gt;&gt;R: Does user have :read_build access?
alt Yes
  R-&gt;&gt;W: Send 302 redirect to object storage presigned URL
  R-&gt;&gt;C: 302 redirect
  C-&gt;&gt;O: GET &lt;presigned URL&gt;
else No
  R-&gt;&gt;W: 401 Unauthorized
  W-&gt;&gt;C: 401 Unauthorized
end</code></pre></details></div>

이 다이어그램에서:

  1. 먼저 러너는 GET /api/v4/jobs/:id/artifacts 엔드포인트를 사용하여 작업 아티팩트를 가져오려고 합니다. 러너는 첫 번째 시도에서 개체 저장소에서 직접 다운로드할 수 있음을 나타내기 위해 direct_download=true 쿼리 파라미터를 첨부합니다. 직접 다운로드는 러너 구성에서 FF_USE_DIRECT_DOWNLOAD 기능 플래그를 통해 비활성화할 수 있습니다. 이 플래그는 기본적으로 true로 설정됩니다.

  2. 러너는 gitlab-ci-token 사용자 이름과 자동 생성된 CI/CD 작업 토큰을 비밀번호로 사용하는 HTTP 기본 인증으로 GET 요청을 보냅니다. 이 토큰은 GitLab이 생성하고 작업 시작 시 러너에게 제공됩니다.

  3. GET 요청이 GitLab API로 전달되어 데이터베이스에서 토큰을 조회하고 작업을 트리거한 사용자를 찾습니다.

  4. 5-8 단계에서:

    • 사용자가 빌드에 액세스할 수 있으면 GitLab이 미리 서명된 URL을 생성하고 해당 URL로 Location이 설정된 302 리다이렉트를 보냅니다. 러너는 302 리다이렉트를 따라 아티팩트를 다운로드합니다.

    • 작업을 찾을 수 없거나 사용자가 작업에 액세스할 수 없으면 API가 401 Unauthorized를 반환합니다.

    러너는 다음 HTTP 상태 코드를 받으면 재시도하지 않습니다:

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

    그러나 러너가 500 오류와 같은 다른 상태 코드를 받으면 각 시도 사이에 1초를 기다리며 두 번 더 아티팩트 다운로드를 재시도합니다. 후속 시도는 direct_download=true를 생략합니다.

프록시 다운로드 활성화#

proxy_downloadtrue이면 러너가 direct_download=true 쿼리 파라미터를 보내더라도 GitLab은 항상 개체 저장소에서 아티팩트를 가져와 러너로 데이터를 보냅니다. 러너의 네트워크 액세스가 제한된 경우 프록시 다운로드가 바람직할 수 있습니다.

다음 다이어그램은 비활성화된 프록시 다운로드 예시와 유사하지만 6-9 단계에서 GitLab이 러너에게 302 리다이렉트를 보내지 않습니다. 대신 GitLab이 Workhorse에게 데이터를 가져와 러너로 스트리밍하도록 지시합니다. 러너 관점에서 /api/v4/jobs/:id/artifacts에 대한 원래 GET 요청이 이진 데이터를 직접 반환합니다.

Mermaid 다이어그램 (27줄)
소스 코드 보기
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
    accTitle: Proxied artifact download flow
    accDescr: Runner authenticates, GitLab fetches from object storage, and streams artifacts back.
autonumber
participant C as Runner
participant O as Object Storage
participant W as Workhorse
participant R as Rails
participant P as PostgreSQL
C-&gt;&gt;+W: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over C,W: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
W--&gt;+R: GET /api/v4/jobs/:id/artifacts?direct_download=true
Note over W,R: gitlab-ci-token@&lt;CI_JOB_TOKEN&gt;
R-&gt;&gt;P: Look up job for CI_JOB_TOKEN
R-&gt;&gt;P: Find user who triggered job
R-&gt;&gt;R: Does user have :read_build access?
alt Yes
  R-&gt;&gt;W: SendURL with object storage presigned URL
  W-&gt;&gt;O: GET &lt;presigned URL&gt;
  O-&gt;&gt;W: &lt;artifacts data&gt;
  W-&gt;&gt;C: &lt;artifacts data&gt;
else No
  R-&gt;&gt;W: 401 Unauthorized
  W-&gt;&gt;C: 401 Unauthorized
end</code></pre></details></div>

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에 더 높은 값을 설정합니다.