관리자를 위한 작업 아티팩트 문제 해결

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

디스크 공간을 너무 많이 사용하는 작업 아티팩트

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

이와 같은 경우 등에서는 디스크 공간 사용량이 가장 많은 프로젝트를 식별하고, 어떤 종류의 아티팩트가 가장 많은 공간을 사용하는지 파악하며, 경우에 따라 매뉴얼으로 작업 아티팩트를 삭제하여 디스크 공간을 회수하세요.

아티팩트 정리

아티팩트 정리는 만료된 아티팩트를 식별하고 삭제할 수 있는 프로세스입니다.

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

이 변경에는 작업 아티팩트를 유지해야 할 경우, 아티팩트를 유지에서 잠금으로 변경하는 내용이 포함됩니다.

unknown 상태의 아티팩트

정리가 업데이트되기 전에 생성된 아티팩트는 unknown 상태를 가집니다. 만료되면, 이러한 아티팩트는 새로운 정리에 의해 처리되지 않습니다.

데이터베이스를 확인하여 인스턴스에 unknown 상태의 아티팩트가 있는지 확인할 수 있습니다:

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

    Linux 패키지 (Omnibus)
    sudo gitlab-psql
    
    Helm 차트 (Kubernetes)
    # 도구 상자 pod을 찾습니다
    kubectl --namespace <namespace> get pods -lapp=toolbox
    # PostgreSQL 콘솔에 연결합니다
    kubectl exec -it <toolbox-pod-name> -- /srv/gitlab/bin/rails dbconsole --include-password --database main
    
    Docker
    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 워커가 GitLab 15.3 이후에는 기본적으로 활성화됩니다. 이는 위의 데이터베이스 쿼리에서 반환된 아티팩트를 분석하고 잠금 또는 잠금 해제할지를 결정합니다. 필요한 경우 해당 워커가 아티팩트를 삭제합니다.

Self-Managed형 인스턴스에서 워커를 활성화할 수 있습니다:

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

  2. 기능이 활성화되어 있는지 확인합니다.

    Feature.enabled?(:ci_job_artifacts_backlog_work)
    
  3. 필요하다면 기능을 활성화합니다:

    Feature.enable(:ci_job_artifacts_backlog_work)
    

워커는 매 7분마다 10,000개의 unknown 아티팩트를 처리하며, 이는 대략 24시간에 200만 개의 아티팩트를 처리합니다.

관리자를 위한 워커가 unknown 아티팩트를 처리하는데 5배 큰 일괄 처리를 유도하는 ci_job_artifacts_backlog_large_loop_limit 피처 플래그도 있습니다. 이러한 플래그는 Self-Managed형 인스턴스에서 사용을 권장하지 않습니다.

특정 만료 일자(또는 만료 없음)를 가진 프로젝트 및 아티팩트 디렉터리

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개로 제한하지만, 이 숫자는 필요에 따라 변경할 수 있습니다:

# 만료되지 않는 아티팩트를 가진 빌드 및 프로젝트 찾기
artifacts_that_never_expire = Ci::Build.with_downloadable_artifacts.where(artifacts_expire_at: nil).limit(50)
artifacts_that_never_expire.find_each do |build|
  puts "ID가 #{build.id}인 빌드에는 만료되지 않는 아티팩트가 있으며, 해당 프로젝트는 #{build.project.full_path}에 속합니다"
end

# 오늘로부터 7일 이후에 만료되는 아티팩트를 가진 빌드 및 프로젝트 찾기
artifacts_expire_in_a_week = Ci::Build.with_downloadable_artifacts.where('artifacts_expire_at > ?', 7.days.from_now).limit(50)
artifacts_expire_in_a_week.find_each do |build|
  puts "ID가 #{build.id}인 빌드에는 #{build.artifacts_expire_at}에 만료되는 아티팩트가 있으며, 해당 프로젝트는 #{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)의 숫자를 수정하세요.

오래된 빌드 및 애티팩트 삭제

caution
이러한 명령은 데이터를 데이터베이스와 리포지터리에서 영구적으로 제거합니다. 실행하기 전에 지원 엔지니어의 지침을 찾거나, 예방 차원으로 인스턴스의 백업을 준비하고 테스트 환경에서 실행하는 것을 강력히 권장합니다.

프로젝트의 오래된 애티팩트 삭제

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

GitLab 15.3 및 이전 버전에서는 대신 다음을 사용하세요:

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|
    build.artifacts_expire_at = Time.current
    build.erase_erasable_artifacts!
  end
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

GitLab 15.3 및 이전 버전에서는 대신 다음을 사용하세요:

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|
    build.artifacts_expire_at = Time.current
    build.erase_erasable_artifacts!
  end
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

GitLab 15.3 및 이전 버전에서는 Ci::BuildEraseService.new(build, admin_user).execute 대신 build.erase(erased_by: admin_user)로 대체하세요.

1.year.ago는 Rails ActiveSupport::Duration 메서드입니다. 실수로 여전히 사용 중인 애티팩트를 실수로 삭제하는 위험을 줄이기 위해 긴 기간부터 시작합니다. 필요할 때 짧은 기간으로 삭제를 다시 실행하세요. 예를 들어 3.months.ago, 2.weeks.ago, 또는 7.days.ago가 있습니다.

erase_erasable_artifacts! 메서드는 동기적이며 실행 시 즉시 애티팩트가 제거되며, 백그라운드 대기열에 의해 예약되지 않습니다.

작업 애티팩트 업로드가 오류 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"}
    

두 경우 모두 애티팩트의 객체 스토리지 구성(object storage configuration)(object_storage.md)에서 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로 업로드하지 못하는 문제

Windows 마운트에서 CIFS를 사용하여 작업 아티팩트를 사용하는 경우, 러너가 아티팩트를 업로드하려고 시도할 때 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

이 문제를 해결하기 위해 다음을 시도할 수 있습니다:

  • 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으로 변동할 수 있습니다. 재계산 후에는 사용량이 다시 예상대로 표시되어야 합니다.