관리자를 위한 작업 아티팩트 문제 해결
GitLab v19.4Offering: GitLab Self-Managed
요약
job 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다. GitLab 18.6 이전에는 원격 스토리지에서 로컬 스토리지로 마이그레이션할 때 아티팩트가 잘못된 파일 이름으로 복사될 수 있었습니다. 사용 중인 GitLab 인스턴스에서 이 문제가 발생했다면 다음을 실행합니다.
job 아티팩트를 관리할 때 다음과 같은 문제가 발생할 수 있습니다.
job 아티팩트의 파일 이름이 잘못될 수 있음#
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
이 태스크는 로컬 스토리지에서 파일 이름이 잘못된 아티팩트를 확인하고 예상되는 파일 이름으로 이름을 변경합니다.
job 아티팩트가 디스크 공간을 너무 많이 사용함#
job 아티팩트는 예상보다 빠르게 디스크 공간을 채울 수 있습니다. 가능한 원인은 다음과 같습니다.
- 사용자가 job 아티팩트 만료 기간을 필요 이상으로 길게 설정했습니다.
- 실행된 job 수, 따라서 생성된 아티팩트 수가 예상보다 많습니다.
- job 로그가 예상보다 크고 시간이 지나면서 누적되었습니다.
- 아티팩트 하우스키핑이 빈 디렉터리를 남기기 때문에 파일 시스템의 inode가 부족해질 수 있습니다. 고아 아티팩트 파일용 Rake 태스크가 이를 제거합니다.
- 아티팩트 파일이 디스크에 남아 하우스키핑으로 삭제되지 않을 수 있습니다. 이를 제거하려면 고아 아티팩트 파일용 Rake 태스크를 실행합니다. 이 스크립트는 빈 디렉터리도 함께 제거하므로(앞의 원인 참고) 항상 처리할 작업이 있습니다.
unknown상태인 아티팩트는 자동 정리로 처리되지 않을 수 있습니다. 이러한 아티팩트를 확인하고 정리해 디스크 공간을 회수할 수 있습니다.- 최근 성공한 job의 최신 아티팩트 유지 기능이 활성화되어 있습니다.
이런 경우와 그 밖의 경우에는 디스크 공간 사용에 가장 큰 영향을 주는 프로젝트를 파악하고, 어떤 유형의 아티팩트가 공간을 가장 많이 사용하는지 확인하며, 경우에 따라 job 아티팩트를 수동으로 삭제해 디스크 공간을 회수합니다.
아티팩트 하우스키핑#
아티팩트 하우스키핑은 어떤 아티팩트가 만료되어 삭제할 수 있는지 식별하는 과정입니다.
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 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 | 163unknown아티팩트가 있다면 만료 시간을 더 짧게 설정하거나 수동으로 제거해 디스크 공간을 회수할 수 있습니다.unknown아티팩트 정리#unknown아티팩트를 정리하려면 만료 시간을 더 짧게 설정해 자동 정리 과정이 이를 처리하도록 할 수 있습니다.-
Rails 콘솔을 시작합니다.
-
unknown아티팩트의 만료 시각을 현재 시각으로 설정합니다.# This marks unknown artifacts for immediate cleanup Ci::JobArtifact.where(locked: 2).update_all(expire_at: Time.current)
그러면 자동 하우스키핑 과정이 다음 실행에서 이러한 아티팩트를 정리합니다.
오브젝트 스토리지에서
@final아티팩트가 삭제되지 않음#아티팩트는 임시 위치를 먼저 사용하지 않고
@final디렉터리의 최종 스토리지 위치로 직접 업로드됩니다.GitLab 16.1 및 16.2의 문제로 아티팩트가 만료될 때 오브젝트 스토리지에서 삭제되지 않습니다. 만료된 아티팩트를 정리하는 과정이
@final디렉터리의 아티팩트를 제거하지 않습니다. 이 문제는 GitLab 16.3 이상에서 수정되었습니다.GitLab 16.1 또는 16.2를 일정 기간 운영한 GitLab 인스턴스의 관리자는 아티팩트가 사용하는 오브젝트 스토리지가 늘어난 것을 확인할 수 있습니다. 이러한 아티팩트를 확인하고 제거하려면 다음 절차를 따릅니다.
파일 제거는 두 단계로 진행됩니다.
고아 job 아티팩트 목록 조회#
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
고아 job 아티팩트 삭제#
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 콘솔을 사용하면 다음 중 하나에 해당하는 job 아티팩트가 있는 프로젝트를 찾을 수 있습니다.
- 만료 날짜가 없음.
- 만료 날짜가 7일 이후임.
아티팩트 삭제와 마찬가지로 다음 예시 기간을 사용하고 필요에 따라 변경합니다.
7.days.from_now10.days.from_now2.weeks.from_now3.months.from_now1.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저장된 job 아티팩트 총 크기별 프로젝트 목록 조회#
Rails 콘솔에서 다음 코드를 실행하면 저장된 job 아티팩트 총 크기순으로 상위 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 콘솔에서 다음 코드를 실행하면 단일 프로젝트에서 가장 큰 job 아티팩트 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)을 원하는 숫자로 바꾸면 조회되는 job 아티팩트 수를 변경할 수 있습니다.단일 프로젝트의 아티팩트 목록 조회#
단일 프로젝트의 아티팩트를 아티팩트 크기순으로 조회합니다. 출력에는 다음이 포함됩니다.
- 아티팩트를 생성한 job의 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조회되는 job 아티팩트 수를 변경하려면
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프로젝트의 오래된 job 로그 및 아티팩트 삭제#
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인스턴스 전체의 오래된 job 로그 및 아티팩트 삭제#
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::ScheduleDeleteObjectsCronWorker워커가Ci::DeletedObject레코드를 처리해 파일을 물리적으로 제거할 때까지 아티팩트 파일은 디스크에 남아 있습니다.
시스템 리소스에 과도한 부담을 주지 않도록 제거는 의도적으로 제한됩니다.
- 워커는 매시 16분에 시간당 한 번 실행됩니다.
- 오브젝트를 배치로 처리하며 최대 20개의 job을 동시에 실행합니다.
- 삭제된 각 오브젝트에는 물리적 삭제 대상이 되는 시점을 결정하는
pick_up_at타임스탬프가 있습니다
대규모 삭제에서는 디스크 공간이 완전히 회수되기까지 물리적 정리에 상당한 시간이 걸릴 수 있습니다. 매우 큰 규모의 삭제라면 정리에 며칠이 걸릴 수도 있습니다.
디스크 공간을 빠르게 회수해야 한다면 아티팩트 삭제를 가속할 수 있습니다.
아티팩트 제거 가속#
많은 아티팩트를 삭제한 뒤 디스크 공간을 빠르게 회수해야 한다면, 표준 스케줄링 제한을 우회해 삭제 과정을 가속할 수 있습니다.
Warning많은 아티팩트를 삭제하는 경우 다음 명령은 시스템에 상당한 부하를 줍니다.
# 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오래된 파이프라인 삭제#
Warning다음 명령은 데이터를 영구적으로 제거합니다. 프로덕션 환경에서 실행하기 전에 지원 엔지니어의 안내를 받는 것을 고려합니다. 또한 테스트 환경에서 먼저 시도하고, 필요하면 복원할 수 있도록 인스턴스를 백업해야 합니다.
파이프라인을 삭제하면 해당 파이프라인의 다음 항목도 제거됩니다.
- job 아티팩트
- job 로그
- job 메타데이터
- 파이프라인 메타데이터
job 및 파이프라인 메타데이터를 제거하면 데이터베이스의 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 endjob 아티팩트 업로드가 오류 500으로 실패함#
아티팩트에 오브젝트 스토리지를 사용하는데 job 아티팩트 업로드가 실패하면 다음을 확인합니다.
-
job 로그에서 다음과 비슷한 오류 메시지
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"}
두 경우 모두 job 아티팩트 오브젝트 스토리지 구성에
region을 추가해야 할 수 있습니다.job 아티팩트 업로드가
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통합 오브젝트 스토리지를 사용할 때 위 오류로 job 아티팩트 업로드가 실패하면, 데이터 유형별로 별도의 버킷을 사용하고 있는지 확인합니다.
Windows 마운트 사용 시 job 아티팩트 업로드가
FATAL: invalid argument로 실패함#job 아티팩트에 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까지 변동할 수 있습니다. 다시 계산한 뒤에는 사용량이 다시 정상적으로 표시됩니다.아티팩트 다운로드 흐름 다이어그램#
다음 흐름 다이어그램은 job 아티팩트의 동작 방식을 보여 줍니다. 이 다이어그램은 job 아티팩트에 오브젝트 스토리지가 구성되어 있다고 가정합니다.
프록시 다운로드 비활성화#
proxy_download가false로 설정되어 있으면 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->>+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</code></pre></details></div>이 다이어그램의 내용은 다음과 같습니다.
-
먼저 러너는
GET /api/v4/jobs/:id/artifacts엔드포인트로 job 아티팩트를 가져오려고 시도합니다. 러너는 오브젝트 스토리지에서 직접 내려받을 수 있음을 알리기 위해 첫 시도에direct_download=true쿼리 파라미터를 붙입니다. 직접 다운로드는 러너 구성의FF_USE_DIRECT_DOWNLOAD기능 플래그로 비활성화할 수 있습니다. 이 플래그는 기본적으로true로 설정되어 있습니다. -
러너는
gitlab-ci-token사용자 이름과 자동 생성된 CI/CD job 토큰을 비밀번호로 사용하는 HTTP 기본 인증으로 GET 요청을 보냅니다. 이 토큰은 GitLab이 생성해 job 시작 시점에 러너에 전달합니다. -
GET 요청은 GitLab API로 전달되고, API는 데이터베이스에서 토큰을 조회해 job을 트리거한 사용자를 찾습니다.
-
5~8단계의 동작은 다음과 같습니다.
-
사용자가 해당 빌드에 접근할 수 있으면 GitLab은 사전 서명된 URL을 생성하고
Location에 그 URL을 설정한 302 Redirect를 보냅니다. 러너는 302 Redirect를 따라가 아티팩트를 내려받습니다. -
job을 찾을 수 없거나 사용자가 그 job에 접근할 수 없으면 API는 401 Unauthorized를 반환합니다.
러너는 다음 HTTP 상태 코드를 받으면 재시도하지 않습니다.
- 200 OK
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
다만 러너가 500 오류처럼 그 밖의 상태 코드를 받으면 각 시도 사이에 1초씩 대기하면서 아티팩트 다운로드를 두 번 더 시도합니다. 이후 시도에서는
direct_download=true를 생략합니다. -
프록시 다운로드 활성화#
proxy_download가true이면 러너가direct_download=true쿼리 파라미터를 보내더라도 GitLab은 항상 오브젝트 스토리지에서 아티팩트를 가져와 데이터를 러너로 보냅니다. 러너의 네트워크 접근이 제한된 환경에서는 프록시 다운로드가 바람직할 수 있습니다.다음 다이어그램은 프록시 다운로드를 비활성화한 예시와 비슷하지만, 6~9단계에서 GitLab이 러너에 302 Redirect를 보내지 않습니다. 대신 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->>+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</code></pre></details></div>413 Request Entity Too Large오류#아티팩트가 너무 크면 job이 다음 오류로 실패할 수 있습니다.
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에 더 큰 값을 설정합니다.
-