스토리지 관리 자동화

Tier: Free, Premium, Ultimate Offering: GitLab.com, Self-Managed, GitLab Dedicated

이 페이지에서는 GitLab REST API를 사용하여 스토리지 분석 및 정리를 자동화하는 방법에 대해 설명합니다.

파이프라인 효율성을 향상시켜 스토리지 사용량을 관리할 수도 있습니다.

API 자동화에 대한 추가 도움이 필요하다면 GitLab 커뮤니티 포럼 및 Discord를 사용할 수 있습니다.

caution
이 페이지의 스크립트 예제는 데모 목적으로만 사용되어야 하며 실제 운영에 사용해서는 안 됩니다. 이 예제를 사용하여 자체 스토리지 자동화용 스크립트를 설계하고 테스트할 수 있습니다.

API 요구 사항

스토리지 관리를 자동화하려면 GitLab.com SaaS 또는 Self-Managed 인스턴스가 GitLab REST API에 액세스해야 합니다.

API 인증 범위

API 사용을 위해 다음 범위를 사용합니다:

  • 스토리지 분석:
    • read_api 범위로 API 액세스 읽기
    • 모든 프로젝트에 대해 적어도 Developer 역할
  • 스토리지 정리:
    • api 범위로 전체 API 액세스
    • 모든 프로젝트에 대해 적어도 Maintainer 역할

REST API와 상호 작용하기 위해 명령줄 도구나 프로그래밍 언어를 사용할 수 있습니다.

명령줄 도구

API 요청을 보내려면 다음 중 하나를 설치하세요:

  • 선호하는 패키지 관리자로 curl 설치
  • GitLab CLI 설치하고 glab api 하위 명령을 사용

JSON 응답을 서식 지정하려면 jq를 설치하세요. 자세한 정보는 Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation를 참조하세요.

REST API와 함께 이러한 도구를 사용하려면:

curl
export GITLAB_TOKEN=xxx

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/user" | jq
GitLab CLI
glab auth login

glab api groups/YOURGROUPNAME/projects

GitLab CLI 사용

일부 API 엔드포인트에는 페이지 구성 및 추가 페이지 가져오기가 필요합니다. GitLab CLI는 --paginate 플래그를 제공합니다.

JSON 데이터로 형식화된 POST 본문이 필요한 요청은 --raw-field 매개변수에 전달된 key=value 쌍으로 작성할 수 있습니다.

자세한 내용은 GitLab CLI 엔드포인트 문서를 참조하세요.

API 클라이언트 라이브러리

이 페이지에서 설명하는 스토리지 관리 및 정리 자동화 방법은 다음을 사용합니다:

  • 기능이 풍부한 프로그래밍 인터페이스를 제공하는 python-gitlab 라이브러리
  • GitLab API with Python 프로젝트의 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py 스크립트

python-gitlab 라이브러리의 사용 사례에 대한 자세한 내용은 Efficient DevSecOps workflows: Hands-on python-gitlab API automation를 참조하세요.

다른 API 클라이언트 라이브러리에 대한 자세한 내용은 Third-party clients를 참조하세요.

note
코드를 더 효율적으로 작성하려면 GitLab Duo Code Suggestions를 사용하세요.

스토리지 분석

스토리지 유형 식별

프로젝트 API 엔드포인트는 GitLab 인스턴스 내의 프로젝트에 대한 통계를 제공합니다. 프로젝트 API 엔드포인트를 사용하려면 statistics 키를 boolean true로 설정하세요. 이 데이터는 프로젝트의 다음 스토리지 유형의 스토리지 소비에 대한 통찰을 제공합니다:

  • storage_size: 전체 스토리지
  • lfs_objects_size: LFS 오브젝트 스토리지
  • job_artifacts_size: 작업 아티팩트 스토리지
  • packages_size: 패키지 스토리지
  • repository_size: Git 저장소 스토리지
  • snippets_size: 스니펫 스토리지
  • uploads_size: 업로드 스토리지
  • wiki_size: 위키 스토리지

스토리지 유형을 식별하려면:

curl
curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID?statistics=true" | jq --compact-output '.id,.statistics' | jq
48349590
{
  "commit_count": 2,
  "storage_size": 90241770,
  "repository_size": 3521,
  "wiki_size": 0,
  "lfs_objects_size": 0,
  "job_artifacts_size": 90238249,
  "pipeline_artifacts_size": 0,
  "packages_size": 0,
  "snippets_size": 0,
  "uploads_size": 0
}
GitLab CLI
export GL_PROJECT_ID=48349590
glab api --method GET projects/$GL_PROJECT_ID --field 'statistics=true' | jq --compact-output '.id,.statistics' | jq
48349590
{
  "commit_count": 2,
  "storage_size": 90241770,
  "repository_size": 3521,
  "wiki_size": 0,
  "lfs_objects_size": 0,
  "job_artifacts_size": 90238249,
  "pipeline_artifacts_size": 0,
  "packages_size": 0,
  "snippets_size": 0,
  "uploads_size": 0
}
Python
project_obj = gl.projects.get(project.id, statistics=True)

print("Project {n} statistics: {s}".format(n=project_obj.name_with_namespace, s=json.dump(project_obj.statistics, indent=4)))

터미널에 프로젝트 통계를 인쇄하려면 GL_GROUP_ID 환경 변수를 내보내고 스크립트를 실행하세요:

export GL_TOKEN=xxx
export GL_GROUP_ID=56595735

pip3 install python-gitlab
python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py

Project Developer Evangelism and Technical Marketing at GitLab  / playground / Artifact generator group / Gen Job Artifacts 4 statistics: {
    "commit_count": 2,
    "storage_size": 90241770,
    "repository_size": 3521,
    "wiki_size": 0,
    "lfs_objects_size": 0,
    "job_artifacts_size": 90238249,
    "pipeline_artifacts_size": 0,
    "packages_size": 0,
    "snippets_size": 0,
    "uploads_size": 0
}

프로젝트 및 그룹에서 저장소 분석

여러 프로젝트 및 그룹의 분석을 자동화할 수 있습니다. 예를 들어 최상위 네임스페이스 수준에서 시작하여 순환적으로 모든 하위 그룹과 프로젝트를 분석할 수 있습니다. 또한 다른 저장소 유형을 분석할 수도 있습니다.

다음은 여러 하위 그룹과 프로젝트를 분석하기 위한 알고리즘 예제입니다.

  1. 최상위 네임스페이스 ID를 가져옵니다. 네임스페이스/그룹 개요에서 ID 값을 복사할 수 있습니다.
  2. 최상위 그룹에서 하위 그룹을 모두 가져와 ID를 목록에 저장합니다.
  3. 모든 그룹을 순환하면서 각 그룹에서 프로젝트를 가져옵니다 그리고 ID를 목록에 저장합니다.
  4. 분석할 저장소 유형을 식별하고, 프로젝트 통계 및 작업 아티팩트와 같은 프로젝트 속성에서 정보를 수집합니다.
  5. 그룹별로 모든 프로젝트의 개요와 저장소 정보를 출력합니다.

glab를 사용하는 셸 접근 방식은 작은 분석에 더 적합할 수 있습니다. 큰 분석에는 API 클라이언트 라이브러리를 사용하는 스크립트를 사용해야 합니다. 이 유형의 스크립트는 가독성, 데이터 저장, 흐름 제어, 테스트 및 재사용성을 향상시킬 수 있습니다.

이 스크립트가 API 요청 속도 제한에 도달하지 않도록 하려면, 아래 예제 코드는 병렬 API 요청에 최적화되어 있지 않습니다.

이 알고리즘을 실행하려면:

GitLab CLI
export GROUP_NAME="gitlab-da"

# 하위 그룹 ID 반환
glab api groups/$GROUP_NAME/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
12034712
67218622
67162711
67640130
16058698
12034604

# 모든 수집된 하위 그룹에서 순환하며 하위 그룹 가져오기, 결과 집합이 비어 있을 때까지. 예시 그룹: 12034712
glab api groups/12034712/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
56595735
70677315
67218606
70812167

# 가장 낮은 그룹 수준
glab api groups/56595735/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
# 빈 결과, 반환하고 분석 계속

# 모든 수집된 그룹에서 프로젝트 가져오기. 예시 그룹: 56595735
glab api groups/56595735/projects | jq --compact-output '.[]' | jq --compact-output '.id'
48349590
48349263
38520467
38520405

# 프로젝트에서 저장소 유형 가져오기 (ID 48349590): `artifacts` 키의 작업 아티팩트
glab api projects/48349590/jobs | jq --compact-output '.[]' | jq --compact-output '.id, .artifacts'
4828297946
[{"file_type":"archive","size":52444993,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":156,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3140,"filename":"job.log","file_format":null}]
4828297945
[{"file_type":"archive","size":20978113,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3147,"filename":"job.log","file_format":null}]
4828297944
[{"file_type":"archive","size":10489153,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":158,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3146,"filename":"job.log","file_format":null}]
4828297943
[{"file_type":"archive","size":5244673,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3145,"filename":"job.log","file_format":null}]
4828297940
[{"file_type":"archive","size":1049089,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3140,"filename":"job.log","file_format":null}]
Python
#!/usr/bin/env python

import datetime
import gitlab
import os
import sys

GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
GITLAB_TOKEN = os.environ.get('GL_TOKEN') # 토큰에는 개발자 권한이 필요합니다
PROJECT_ID = os.environ.get('GL_PROJECT_ID') #옵션
GROUP_ID = os.environ.get('GL_GROUP_ID') #옵션

if __name__ == "__main__":
    if not GITLAB_TOKEN:
        print("🤔 GL_TOKEN 환경 변수를 설정하세요.")
        sys.exit(1)

    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN, pagination="keyset", order_by="id", per_page=100)

    # 모든 프로젝트 수집 또는 그룹 ID 또는 프로젝트 ID에서 프로젝트 선호
    projects = []

    # 직접 프로젝트 ID
    if PROJECT_ID:
        projects.append(gl.projects.get(PROJECT_ID))
    # 그룹과 내부 프로젝트
    elif GROUP_ID:
        group = gl.groups.get(GROUP_ID)

        for project in group.projects.list(include_subgroups=True, get_all=True):
            manageable_project = gl.projects.get(project.id , lazy=True)
            projects.append(manageable_project)

    for project in projects:
        jobs = project.jobs.list(pagination="keyset", order_by="id", per_page=100, iterator=True)
        for job in jobs:
            print("DEBUG: ID {i}: {a}".format(i=job.id, a=job.attributes['artifacts']))

이 스크립트는 JSON 형식의 프로젝트 작업 아티팩트를 출력합니다:

[
    {
        "file_type": "archive",
        "size": 1049089,
        "filename": "artifacts.zip",
        "file_format": "zip"
    },
    {
        "file_type": "metadata",
        "size": 157,
        "filename": "metadata.gz",
        "file_format": "gzip"
    },
    {
        "file_type": "trace",
        "size": 3146,
        "filename": "job.log",
        "file_format": null
    }
]

CI/CD 파이프라인 저장소 관리

작업 아티팩트는 대부분의 파이프라인 저장소를 차지하며, 작업 로그 또한 수백 킬로바이트를 생성할 수 있습니다. 불필요한 작업 아티팩트를 먼저 삭제한 다음 분석 후 작업 로그를 정리해야 합니다.

경고: 작업 로그와 아티팩트를 삭제하는 것은 되돌릴 수 없는 파괴적인 작업입니다. 신중하게 사용하세요. 보고 아티팩트, 작업 로그 및 메타데이터 파일을 포함한 특정 파일을 삭제하면 이러한 파일을 데이터 소스로 사용하는 GitLab 기능에 영향을 줍니다.

작업 아티팩트 목록

파이프라인 저장소를 분석하려면 Job API 엔드포인트를 사용하여 작업 아티팩트 목록을 검색할 수 있습니다. 엔드포인트는 ‘artifacts’ 속성의 작업 아티팩트 ‘file_type’ 키를 반환합니다. ‘file_type’ 키는 아티팩트 유형을 나타냅니다:

  • archive는 생성된 작업 아티팩트를 나타내는 zip 파일에 사용됩니다.
  • metadata는 Gzip 파일의 추가 메타데이터에서 사용됩니다.
  • tracejob.log를 나타내는 원시 파일입니다.

작업 아티팩트는 디스크에 캐시 파일로 작성할 수 있는 데이터 구조를 제공하며, 이를 구현 테스트에 사용할 수 있습니다.

모든 프로젝트를 가져오는 코드를 기반으로, Python 스크립트를 확장하여 더 많은 분석을 수행할 수 있습니다.

다음 예는 프로젝트에서 작업 아티팩트를 쿼리한 응답을 보여줍니다:

[
    {
        "file_type": "archive",
        "size": 1049089,
        "filename": "artifacts.zip",
        "file_format": "zip"
    },
    {
        "file_type": "metadata",
        "size": 157,
        "filename": "metadata.gz",
        "file_format": "gzip"
    },
    {
        "file_type": "trace",
        "size": 3146,
        "filename": "job.log",
        "file_format": null
    }
]

스크립트를 구현하는 방식에 따라 다음 중 하나를 수행할 수 있습니다:

  • 모든 작업 아티팩트를 수집하고 스크립트 끝에 요약 테이블을 출력합니다.
  • 정보를 즉시 출력합니다.

다음 예제에서 작업 아티팩트가 ci_job_artifacts 리스트에 수집됩니다. 스크립트는 모든 프로젝트를 순회하고 다음을 가져옵니다:

  • 모든 속성을 포함하는 project_obj 객체 변수.
  • job 객체에서 artifacts 속성.

큰 파이프라인 및 작업 목록을 반복하려면 키셋 페이지네이션(keyset pagination)을 사용할 수 있습니다.

   ci_job_artifacts = []

    for project in projects:
        project_obj = gl.projects.get(project.id)

        jobs = project.jobs.list(pagination="keyset", order_by="id", per_page=100, iterator=True)

        for job in jobs:
            artifacts = job.attributes['artifacts']
            #print("DEBUG: ID {i}: {a}".format(i=job.id, a=json.dumps(artifacts, indent=4)))
            if not artifacts:
                continue

            for a in artifacts:
                data = {
                    "project_id": project_obj.id,
                    "project_web_url": project_obj.name,
                    "project_path_with_namespace": project_obj.path_with_namespace,
                    "job_id": job.id,
                    "artifact_filename": a['filename'],
                    "artifact_file_type": a['file_type'],
                    "artifact_size": a['size']
                }

                ci_job_artifacts.append(data)

    print("\n데이터 수집 완료.")

    if len(ci_job_artifacts) > 0:
        print("|프로젝트|작업|아티팩트 이름|아티팩트 유형|아티팩트 크기|\n|-|-|-|-|-|") #마크다운 테이블 시작
        for artifact in ci_job_artifacts:
            print('| [{project_name}]({project_web_url}) | {job_name} | {artifact_name} | {artifact_type} | {artifact_size} |'.format(project_name=artifact['project_path_with_namespace'], project_web_url=artifact['project_web_url'], job_name=artifact['job_id'], artifact_name=artifact['artifact_filename'], artifact_type=artifact['artifact_file_type'], artifact_size=render_size_mb(artifact['artifact_size'])))
    else:
        print("작업 아티팩트를 찾을 수 없습니다.")

스크립트 끝에 작업 아티팩트가 Markdown 형식의 테이블로 출력됩니다. 테이블 내용을 이슈 코멘트나 설명에 복사하거나 GitLab 저장소의 Markdown 파일에 작성할 수 있습니다.

$ python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py

|프로젝트|작업|아티팩트 이름|아티팩트 유형|아티팩트 크기|
|-|-|-|-|-|
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | artifacts.zip | archive | 50.0154 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | metadata.gz | metadata | 0.0001 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | job.log | trace | 0.0030 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | artifacts.zip | archive | 20.0063 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | metadata.gz | metadata | 0.0001 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | job.log | trace | 0.0030 |

대량 작업 아티팩트 삭제

특정 유형의 작업 아티팩트를 대량으로 삭제하기 위해 Python 스크립트를 사용할 수 있습니다.

API 쿼리 결과를 필터링하여 다음을 비교합니다:

  • 아티팩트의 나이를 계산하는 데 사용되는 created_at 값.
  • 저장 공간 요구 사항을 충족하는지 여부를 결정하는 size 속성.

전형적인 요청:

  • 특정 일 수보다 오래된 작업 아티팩트를 삭제합니다.
  • 지정된 저장량 이상의 작업 아티팩트를 삭제합니다. 예: 100MB 이상.

다음 예에서 스크립트는 작업 속성을 순회하고 삭제를 위해 표시합니다. 컬렉션 루프에서 객체 잠금을 제거하면 스크립트는 삭제할 작업 아티팩트를 삭제합니다.

   for project in projects:
        project_obj = gl.projects.get(project.id)

        jobs = project.jobs.list(pagination="keyset", order_by="id", per_page=100, iterator=True)

        for job in jobs:
            artifacts = job.attributes['artifacts']
            if not artifacts:
                continue

            # 고급 필터링: 연령 및 크기
            # 예: 90일, 10MB 임계값 (TODO: 이 구성 가능하도록 만들기)
            threshold_age = 90 * 24 * 60 * 60
            threshold_size = 10 * 1024 * 1024

            # 작업 연령, API 형식 구문 분석이 필요함: 2023-08-08T22:41:08.270Z
            created_at = datetime.datetime.strptime(job.created_at, '%Y-%m-%dT%H:%M:%S.%fZ')
            now = datetime.datetime.now()
            age = (now - created_at).total_seconds()

            for a in artifacts:
                # ... 가독성을 위해 분석 수집 코드 제거

                # 고급 필터링: 작업 아티팩트 연령 및 크기를 임계값과 비교
                if (float(age) > float(threshold_age)) or (float(a['size']) > float(threshold_size)):
                    # 삭제 대상 작업 표시 (루프 내에서 삭제할 수 없음)
                    jobs_marked_delete_artifacts.append(job)

    print("\n데이터 수집 완료.")

    # 고급 필터링: 삭제할 모든 작업 아티팩트 삭제
    for job in jobs_marked_delete_artifacts:
        # 아티패트 삭제
        print("DEBUG", job)
        job.delete_artifacts()

    # 컬렉션 요약 출력 (가독성을 위해 제거됨)

프로젝트의 모든 작업 아티팩트 삭제

프로젝트의 작업 아티팩트가 필요 없는 경우 아래 명령을 사용하여 모든 작업 아티팩트를 삭제할 수 있습니다. 이 동작은 되돌릴 수 없습니다.

아티팩트 삭제는 삭제할 아티패크트의 수에 따라 몇 분 또는 몇 시간이 걸릴 수 있습니다. API를 통한 후속 분석 쿼리가 잘못된 결과로 아티팩트를 반환할 수 있습니다. 결과의 혼란을 피하려면 추가 API 요청을 즉시 실행하지 마십시오.

가장 최근에 성공한 작업의 아티패크트를 유지하는 것이 기본 설정됩니다.

프로젝트의 모든 작업 아티팩트를 삭제하려면:

curl
export GL_PROJECT_ID=48349590

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" --request DELETE "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID/artifacts"
GitLab CLI
glab api --method GET projects/$GL_PROJECT_ID/jobs | jq --compact-output '.[]' | jq --compact-output '.id, .artifacts'

glab api --method DELETE projects/$GL_PROJECT_ID/artifacts
Python
        project.artifacts.delete()

작업 로그 삭제

작업 로그를 삭제하면 또한 전체 작업이 삭제됩니다.

GitLab CLI를 사용한 예제:

glab api --method GET projects/$GL_PROJECT_ID/jobs | jq --compact-output '.[]' | jq --compact-output '.id'

4836226184
4836226183
4836226181
4836226180

glab api --method POST projects/$GL_PROJECT_ID/jobs/4836226180/erase | jq --compact-output '.name,.status'
"generate-package: [1]"
"success"

python-gitlab API 라이브러리의 경우 job.delete_artifacts() 대신 job.erase()를 사용하십시오. 작업 아티팩트를 삭제하는 호출이 차단되지 않도록 하려면 작업을 삭제하는 호출 사이에 짧은 시간 동안 스크립트를 대기하도록 설정하세요:

    for job in jobs_marked_delete_artifacts:
        # 아티팩트 및 작업 로그 삭제
        print("DEBUG", job)
        #job.delete_artifacts()
        job.erase()
        # 1초 동안 대기
        time.sleep(1)

작업 로그의 보관 정책 생성 지원은 issue 374717에서 제안됩니다.

이전 파이프라인 삭제

파이프라인은 전체 저장소 사용량에 추가되지 않지만 필요한 경우 자동으로 삭제할 수 있습니다.

특정 날짜를 기준으로 파이프라인을 삭제하려면 created_at 키를 지정하세요. 날짜를 사용하여 현재 날짜와 파이프라인이 생성된 날짜 사이의 차이를 계산할 수 있습니다. 나이가 임계값보다 크면 파이프라인이 삭제됩니다.

참고: created_at 키는 타임스탬프를 Unix epoch 시간으로 변환해야 합니다. 예: date -d '2023-08-08T18:59:47.581Z' +%s.

GitLab CLI를 사용한 예제:

export GL_PROJECT_ID=48349590

glab api --method GET projects/$GL_PROJECT_ID/pipelines | jq --compact-output '.[]' | jq --compact-output '.id,.created_at'
960031926
"2023-08-08T22:09:52.745Z"
959884072
"2023-08-08T18:59:47.581Z"

glab api --method DELETE projects/$GL_PROJECT_ID/pipelines/960031926

glab api --method GET projects/$GL_PROJECT_ID/pipelines | jq --compact-output '.[]' | jq --compact-output '.id,.created_at'
959884072
"2023-08-08T18:59:47.581Z"

다음은 Bash 스크립트를 사용한 예제입니다:

  • jq와 GitLab CLI가 설치되어 있고 인가되어 있다.
  • 익스포트된 환경 변수 GL_PROJECT_ID.

전체 get_cicd_pipelines_compare_age_threshold_example.sh 스크립트는 GitLab API with Linux Shell 프로젝트에 위치해 있습니다.

#/bin/bash

CREATED_AT_ARR=$(glab api --method GET projects/$GL_PROJECT_ID/pipelines | jq --compact-output '.[]' | jq --compact-output '.created_at' | jq --raw-output @sh)

for row in ${CREATED_AT_ARR[@]}
do
    stripped=$(echo $row | xargs echo)
    #echo $stripped #DEBUG

    CREATED_AT_TS=$(date -d "$stripped" +%s)
    NOW=$(date +%s)

    AGE=$(($NOW-$CREATED_AT_TS))
    AGE_THRESHOLD=$((90*24*60*60)) # 90일

    if [ $AGE -gt $AGE_THRESHOLD ];
    then
        echo "파이프라인 연령 $AGE이(가) 임계값 $AGE_THRESHOLD보다 오래되었으므로 삭제되어야 합니다."
        # TODO glab를 호출하여 파이프라인을 삭제해야 함. 위의 glab 호출에서 수집된 ID가 필요합니다.
    else
        echo "파이프라인 연령 $AGE이(가) 임계값 $AGE_THRESHOLD보다 오래되지 않았습니다. 무시합니다."
    fi
done

python-gitlab API 라이브러리created_at 속성을 사용하여 유사한 알고리즘을 구현하여 작업 아티팩트 나이를 비교할 수 있습니다:

        # ...

        for pipeline in project.pipelines.list(iterator=True):
            pipeline_obj = project.pipelines.get(pipeline.id)
            print("DEBUG: {p}".format(p=json.dumps(pipeline_obj.attributes, indent=4)))

            created_at = datetime.datetime.strptime(pipeline.created_at, '%Y-%m-%dT%H:%M:%S.%fZ')
            now = datetime.datetime.now()
            age = (now - created_at).total_seconds()

            threshold_age = 90 * 24 * 60 * 60

            if (float(age) > float(threshold_age)):
                print("파이프라인 삭제 중", pipeline.id)
                pipeline_obj.delete()

오래된 파이프라인의 자동 삭제는 issue 338480에서 제안되었습니다.

작업 아티팩트 만료 설정 목록

아티팩트 저장을 관리하기 위해 아티팩트 만료 시기를 업데이트하거나 구성할 수 있습니다. 아티팩트의 만료 설정은 .gitlab-ci.yml의 각 작업 구성에서 구성됩니다.

여러 프로젝트가 있고 CI/CD 구성에서 작업 정의가 어떻게 구성되어 있는지에 따라 만료 설정을 찾기 어려울 수 있습니다. 전체 CI/CD 구성을 검색하기 위해 스크립트를 사용할 수 있습니다. 이는 extends 또는 !reference와 같은 값을 상속한 후 해결된 객체에 액세스합니다.

스크립트는 병합된 CI/CD 구성 파일을 검색하고 아티팩트 키를 찾습니다.

  • 만료 설정이 없는 작업 식별
  • 아티팩트 만료가 구성된 작업의 만료 설정 반환

다음 프로세스는 스크립트가 아티팩트 만료 설정을 검색하는 방법을 설명합니다.

  1. 병합된 CI/CD 구성을 생성하기 위해 스크립트는 모든 프로젝트를 반복하고 ci_lint() 메소드를 호출합니다.
  2. yaml_load 함수는 병합된 구성을 Python 데이터 구조로 로드하여 더 많은 분석을 위해 사용합니다.
  3. script라는 키도 있는 딕셔너리는 artifacts 키가 있을 수 있는 작업 정의를 식별합니다.
  4. 그렇다면, 스크립트는 하위 키 expire_in을 구문 분석하고 나중에 요약적으로 Markdown 테이블에 인쇄할 세부 정보를 저장합니다.
    ci_job_artifacts_expiry = {}

    # 프로젝트를 반복하고 .gitlab-ci.yml을 가져와서 린터를 실행하여 전체 번역된 구성을 가져오고 `artifacts:` 설정을 추출합니다.
    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/ci_lint.html
    for project in projects:
            project_obj = gl.projects.get(project.id)
            project_name = project_obj.name
            project_web_url = project_obj.web_url
            try:
                lint_result = project.ci_lint.get()
                if lint_result.merged_yaml is None:
                    continue

                ci_pipeline = yaml.safe_load(lint_result.merged_yaml)
                #print("Project {p} Config\n{c}\n\n".format(p=project_name, c=json.dumps(ci_pipeline, indent=4)))

                for k in ci_pipeline:
                    v = ci_pipeline[k]
                    # 이것은 `script` 속성이있는 작업 객체입니다.
                    if isinstance(v, dict) and 'script' in v:
                        print(".", end="", flush=True) # 계속 루핑 중임을 나타내는 피드백 획득
                        artifacts = v['artifacts'] if 'artifacts' in v else {}

                        print("Project {p} job {j} artifacts {a}".format(p=project_name, j=k, a=json.dumps(artifacts, indent=4)))

                        expire_in = None
                        if 'expire_in' in artifacts:
                            expire_in = artifacts['expire_in']

                        store_key = project_web_url + '_' + k
                        ci_job_artifacts_expiry[store_key] = { 'project_web_url': project_web_url,
                                                        'project_name': project_name,
                                                        'job_name': k,
                                                        'artifacts_expiry': expire_in}

            except Exception as e:
                 print(f"예외 발생: {e}".format(e=e))

    if len(ci_job_artifacts_expiry) > 0:
        print("|프로젝트|작업|아티팩트 만료|") # 마크다운 테이블 시작
        for k, details in ci_job_artifacts_expiry.items():
            if details['job_name'][0] == '.':
                continue # '.'로 시작하는 작업 템플릿은 무시
            print(f'| [{ details["project_name"] }]({details["project_web_url"]}) | { details["job_name"] } | { details["artifacts_expiry"] if details["artifacts_expiry"] is not None else "❌ N/A" } |')

스크립트는 다음과 같은 Markdown 요약 테이블을 생성합니다.

  • 프로젝트 이름 및 URL
  • 작업 이름
  • artifacts:expire_in 설정 또는 설정이 없을 경우 N/A

스크립트는 다음과 같은 작업 템플릿을 인쇄하지 않습니다.

  • . 문자로 시작하는 템플릿
  • 아티팩트를 생성하지 않는 런타임 작업 객체
export GL_GROUP_ID=56595735

# 스크립트에는 pyyaml도 필요합니다.
pip3 install python-gitlab pyyaml

python3 get_all_cicd_config_artifacts_expiry.py

|프로젝트|작업|아티팩트 만료|
|-|-|-|
| [Gen Job Artifacts 4](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4) | generator | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job10 | 10 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job1 | 1 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job30 | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | generator | 30 days |
| [Gen Job Artifacts 2](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-2) | generator | ❌ N/A |
| [Gen Job Artifacts 1](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-1) | generator | ❌ N/A |

get_all_cicd_config_artifacts_expiry.py 스크립트는 GitLab API with Python project에 위치해 있습니다.

대안으로 고급 검색 및 API 요청을 사용할 수 있습니다. 다음 예시에서는 scope: blobs를 사용하여 모든 *.yml 파일에서 문자열 artifacts를 검색합니다.

# https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs
export GL_PROJECT_ID=48349263

glab api --method GET projects/$GL_PROJECT_ID/search --field "scope=blobs" --field "search=expire_in filename:*.yml"

자세한 정보는 How GitLab can help mitigate deletion of open source container images on Docker Hub을 참조하세요.

작업 아티팩트의 기본 만료일 설정

프로젝트에서 작업 아티팩트의 기본 만료일을 설정하려면 .gitlab-ci.yml 파일에서 expire_in 값을 지정하십시오.

default:
    artifacts:
        expire_in: 1주

컨테이너 레지스트리 저장소 관리

컨테이너 레지스트리는 프로젝트그룹에 사용할 수 있습니다. 두 위치 모두 분석하여 정리 전략을 구현할 수 있습니다.

컨테이너 레지스트리 목록

프로젝트에서 컨테이너 레지스트리를 나열하려면:

curl
export GL_PROJECT_ID=48057080

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID/registry/repositories" | jq --compact-output '.[]' | jq --compact-output '.id,.location' | jq
4435617
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/registry/repositories/4435617?size=true" | jq --compact-output '.id,.location,.size'
4435617
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
3401613
GitLab CLI
export GL_PROJECT_ID=48057080

glab api --method GET projects/$GL_PROJECT_ID/registry/repositories | jq --compact-output '.[]' | jq --compact-output '.id,.location'
4435617
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"

glab api --method GET registry/repositories/4435617 --field='size=true' | jq --compact-output '.id,.location,.size'
4435617
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
3401613

glab api --method GET projects/$GL_PROJECT_ID/registry/repositories/4435617/tags | jq --compact-output '.[]' | jq --compact-output '.name'
"latest"

glab api --method GET projects/$GL_PROJECT_ID/registry/repositories/4435617/tags/latest | jq --compact-output '.name,.created_at,.total_size'
"latest"
"2023-08-07T19:20:20.894+00:00"

컨테이너 이미지 대량 삭제

대량으로 컨테이너 이미지 태그를 삭제할 때 다음을 구성할 수 있습니다:

  • 태그 이름 및 이미지의 일치하는 정규식 (name_regex_keep) 또는 삭제할 정규식 (name_regex_delete)
  • 태그 이름과 일치하는 이미지 태그의 유지 갯수 (keep_n)
  • 이미지 태그를 삭제할 수 있는 일수 (older_than)

경고: GitLab.com에서는 컨테이너 레지스트리의 규모로 인해 이 API에 의해 삭제되는 태그의 수가 제한됩니다. 컨테이너 레지스트리에 삭제해야 하는 태그가 많은 경우 일부만 삭제됩니다. 여러 번 API를 호출해야 할 수도 있습니다. 태그를 자동으로 삭제할 예약을 하려면 대신 정리 정책을 사용하십시오.

다음 예에서는 python-gitlab API 라이브러리를 사용하여 태그 목록을 가져와 필터 매개변수로 delete_in_bulk() 메서드를 호출하는 방법을 보여줍니다.

        repositories = project.repositories.list(iterator=True, size=True)
        if len(repositories) > 0:
            repository = repositories.pop()
            tags = repository.tags.list()

            # 정리: 최신 태그만 남기기
            repository.tags.delete_in_bulk(keep_n=1)
            # 정리: 1개월 이전의 모든 태그 삭제
            repository.tags.delete_in_bulk(older_than="1m")
            # 정리: `v.*`와 일치하는 모든 태그 삭제 및 최신 2개의 태그 유지
            repository.tags.delete_in_bulk(name_regex_delete="v.+", keep_n=2)

컨테이너용 정리 정책 생성

프로젝트 REST API 엔드포인트를 사용하여 컨테이너용 정리 정책을 생성합니다. 정리 정책을 설정한 후에는 규격에 부합하는 모든 컨테이너 이미지가 자동으로 삭제됩니다. 추가 API 자동화 스크립트가 필요하지 않습니다.

속성을 body 매개변수로 보내려면:

  • 표준 입력에서 읽기 위해 --input - 매개변수를 사용하십시오.
  • Content-Type 헤더를 설정하십시오.

다음 예에서는 GitLab CLI를 사용하여 정리 정책을 작성하는 방법을 보여줍니다.

export GL_PROJECT_ID=48057080

echo '{"container_expiration_policy_attributes":{"cadence":"1month","enabled":true,"keep_n":1,"older_than":"14d","name_regex":".*","name_regex_keep":".*-main"}}' | glab api --method PUT --header 'Content-Type: application/json;charset=UTF-8' projects/$GL_PROJECT_ID --input -

...

  "container_expiration_policy": {
    "cadence": "1month",
    "enabled": true,
    "keep_n": 1,
    "older_than": "14d",
    "name_regex": ".*",
    "name_regex_keep": ".*-main",
    "next_run_at": "2023-09-08T21:16:25.354Z"
  },

컨테이너 이미지 최적화

컨테이너 이미지를 최적화하여 컨테이너 레지스트리의 이미지 크기와 전체 저장 공간 사용량을 줄일 수 있습니다. 자세한 내용은 파이프라인 효율성 문서에서 알아보세요.

패키지 레지스트리 저장소 관리

패키지 레지스트리는 프로젝트그룹에 사용할 수 있습니다.

패키지 및 파일 나열

다음 예에서는 GitLab CLI를 사용하여 정의된 프로젝트 ID에서 패키지를 가져오는 방법을 보여줍니다. 결과 세트는 jq 명령 체인으로 필터링할 수 있는 딕셔너리 항목의 배열입니다.

# https://gitlab.com/gitlab-da/playground/container-package-gen-group/generic-package-generator
export GL_PROJECT_ID=48377643

glab api --method GET projects/$GL_PROJECT_ID/packages | jq --compact-output '.[]' | jq --compact-output '.id,.name,.package_type'
16669383
"generator"
"generic"
16671352
"generator"
"generic"
16672235
"generator"
"generic"
16672237
"generator"
"generic"

패키지 ID를 사용하여 패키지의 파일과 파일 크기를 검사하세요.

glab api --method GET projects/$GL_PROJECT_ID/packages/16669383/package_files | jq --compact-output '.[]' |
 jq --compact-output '.package_id,.file_name,.size'

16669383
"nighly.tar.gz"
10487563

유사한 자동화 쉘 스크립트는 이전 파이프라인 삭제 섹션에서 만들어집니다.

다음 예에서는 python-gitlab 라이브러리를 사용하여 루프 안에서 모든 패키지를 가져오고, 그 패키지 파일을 반복하여 file_namesize 속성을 출력하는 방법을 보여줍니다.

        packages = project.packages.list(order_by="created_at")

        for package in packages:

            package_files = package.package_files.list()
            for package_file in package_files:
                print("Package name: {p} File name: {f} Size {s}".format(
                    p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))

패키지 삭제

패키지에서 파일을 삭제하면 패키지가 손상될 수 있습니다. 자동 정리 유지보수를 수행하는 경우 패키지를 삭제해야 합니다.

패키지를 삭제하려면 GitLab CLI를 사용하여 --method 매개변수를 DELETE로 변경합니다.

glab api --method DELETE projects/$GL_PROJECT_ID/packages/16669383

패키지 크기를 계산하고 크기 임계값과 비교하려면 python-gitlab 라이브러리를 사용하여 패키지 및 파일 목록 섹션에 설명된 코드를 확장할 수 있습니다.

다음 코드 예제는 패키지 연령도 계산하고 조건이 맞을 때 패키지를 삭제합니다.

        packages = project.packages.list(order_by="created_at")
        for package in packages:
            package_size = 0.0

            package_files = package.package_files.list()
            for package_file in package_files:
                print("패키지 이름: {p} 파일 이름: {f} 크기 {s}".format(
                    p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))

                package_size =+ package_file.size

            print("패키지 크기: {s}\n\n".format(s=render_size_mb(package_size)))

            threshold_size = 10 * 1024 * 1024

            if (package_size > float(threshold_size)):
                print("패키지 크기 {s} > 임계값 {t}, 패키지를 삭제합니다.".format(
                    s=render_size_mb(package_size), t=render_size_mb(threshold_size)))
                package.delete()

            threshold_age = 90 * 24 * 60 * 60
            package_age = created_at = calculate_age(package.created_at)

            if (float(package_age > float(threshold_age))):
                print("패키지 연령 {a} > 임계값 {t}, 패키지를 삭제합니다.".format(
                    a=render_age_time(package_age), t=render_age_time(threshold_age)))
                package.delete()

코드는 추가 분석에 사용할 수 있는 다음 출력을 생성합니다.

패키지 이름: generator 파일 이름: nighly.tar.gz 크기 10.0017
패키지 크기: 10.0017
패키지 크기 10.0017 > 임계값 10.0000, 패키지를 삭제합니다.

패키지 이름: generator 파일 이름: 1-nightly.tar.gz 크기 1.0004
패키지 크기: 1.0004

패키지 이름: generator 파일 이름: 10-nightly.tar.gz 크기 10.0018
패키지 이름: generator 파일 이름: 20-nightly.tar.gz 크기 20.0033
패키지 크기: 20.0033
패키지 크기 20.0033 > 임계값 10.0000, 패키지를 삭제합니다.

의존성 프록시

정책 정리API를 사용하여 캐시를 지웁니다를 확인하세요.

출력 가독성 향상

타임스탬프 초를 기간 형식으로 변환하거나 원시 바이트를 보다 효과적인 형식으로 출력해야 할 수 있습니다. 다음 도우미 함수를 사용하여 값을 향상된 가독성을 위해 변환할 수 있습니다.

# 현재 Unix 타임스탬프
date +%s

# `created_at` 날짜 및 시간을 시간대와 함께 Unix 타임스탬프로 변환
date -d '2023-08-08T18:59:47.581Z' +%s

python-gitlab API 라이브러리를 사용한 Python 예제:

def render_size_mb(v):
    return "%.4f" % (v / 1024 / 1024)

def render_age_time(v):
    return str(datetime.timedelta(seconds = v))

# `created_at` 날짜 및 시간을 시간대와 함께 Unix 타임스탬프로 변환
def calculate_age(created_at_datetime):
    created_at_ts = datetime.datetime.strptime(created_at_datetime, '%Y-%m-%dT%H:%M:%S.%fZ')
    now = datetime.datetime.now()
    return (now - created_at_ts).total_seconds()

저장소 관리 자동화 테스트

저장소 관리 자동화를 테스트하려면 테스트 데이터를 생성하거나 저장소를 채워서 분석 및 삭제가 예상대로 작동하는지 확인해야 할 수 있습니다. 다음 섹션에서는 짧은 시간 안에 저장소 블롭을 테스트하고 생성하기 위한 도구와 팁을 제공합니다.

작업 artifacts 생성

CI/CD 작업 매트릭스 빌드를 사용하여 가짜 artifact 블롭을 생성하는 테스트 프로젝트를 만들어보세요. 매일 아티팩트를 생성하기 위해 CI/CD 파이프라인을 추가하세요.

  1. 새 프로젝트를 만듭니다.
  2. 다음 스니펫을 .gitlab-ci.yml에 추가하여 작업 artifact 생성기 구성을 포함합니다.

    include:
        - remote: https://gitlab.com/gitlab-da/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml
    
  3. 파이프라인 스케줄링 구성합니다.
  4. 파이프라인을 수동으로 트리거합니다.

대안으로, 매일 생성되는 86 MB의 MB를 다른 값으로 줄이기 위해 MB_COUNT 변수를 사용하세요.

include:
    - remote: https://gitlab.com/gitlab-da/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml

generator:
    parallel:
        matrix:
            - MB_COUNT: [1, 5, 10, 20, 50]

자세한 정보는 작업 Artifact 생성기 README 를 참조하세요. 예제는 다음 그룹에서 확인하세요.

만료 기간이 있는 작업 artifacts 생성

프로젝트 CI/CD 구성은 다음에서 작업 정의를 지정합니다.

  • 주요 .gitlab-ci.yml 구성 파일.
  • artifacts:expire_in 설정.
  • 프로젝트 파일 및 템플릿.

분석 스크립트를 테스트하기 위해 gen-job-artifacts-expiry-included-jobs 프로젝트는 예제 구성을 제공합니다.

# .gitlab-ci.yml
include:
    - include_jobs.yml

default:
  artifacts:
      paths:
          - '*.txt'

.gen-tmpl:
    script:
        - dd if=/dev/urandom of=${$MB_COUNT}.txt bs=1048576 count=${$MB_COUNT}

generator:
    extends: [.gen-tmpl]
    parallel:
        matrix:
            - MB_COUNT: [1, 5, 10, 20, 50]
    artifacts:
        untracked: false
        when: on_success
        expire_in: 30 days

# include_jobs.yml
.includeme:
    script:
        - dd if=/dev/urandom of=1.txt bs=1048576 count=1

included-job10:
    script:
        - echo "Servus"
        - !reference [.includeme, script]
    artifacts:
        untracked: false
        when: on_success
        expire_in: 10 days

included-job1:
    script:
        - echo "Gruezi"
        - !reference [.includeme, script]
    artifacts:
        untracked: false
        when: on_success
        expire_in: 1 days

included-job30:
    script:
        - echo "Grias di"
        - !reference [.includeme, script]
    artifacts:
        untracked: false
        when: on_success
        expire_in: 30 days

컨테이너 이미지 생성

예제 그룹 container-package-gen-group은 다음을 제공하는 프로젝트를 제공합니다:

  • Dockerfile에서 기본 이미지를 사용하여 새 이미지를 빌드합니다.
  • Docker.gitlab-ci.yml 템플릿을 포함하여 GitLab.com SaaS에서 이미지를 빌드합니다.
  • 새 이미지를 매일 생성하도록 파이프라인 일정을 구성합니다.

포크할 수 있는 예제 프로젝트:

일반 패키지 생성

예제 프로젝트 generic-package-generator은 다음을 제공하는 프로젝트를 제공합니다:

  • 랜덤 텍스트 blob을 생성하고 현재 Unix 타임스탬프를 릴리즈 버전으로 하는 tarball을 작성합니다.
  • Unix 타임스탬프를 릴리즈 버전으로 사용하여 tarball을 일반 패키지 레지스트리에 업로드합니다.

일반 패키지를 생성하려면 독립적인 .gitlab-ci.yml 구성을 사용할 수 있습니다:

generate-package:
  parallel:
    matrix:
      - MB_COUNT: [1, 5, 10, 20]
  before_script:
    - apt update && apt -y install curl
  script:
    - dd if=/dev/urandom of="${MB_COUNT}.txt" bs=1048576 count=${MB_COUNT}
    - tar czf "generated-$MB_COUNT-nighly-`date +%s`.tar.gz" "${MB_COUNT}.txt"
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "generated-$MB_COUNT-nighly-`date +%s`.tar.gz" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/generator/`date +%s`/${MB_COUNT}-nightly.tar.gz"'

  artifacts:
    paths:
      - '*.tar.gz'

포크를 사용한 저장소 사용량 생성

forks의 용량 요소에 대한 비용 요소로 저장소 사용량을 테스트하기 위해 다음 프로젝트를 사용합니다:

커뮤니티 자료

다음 자료는 공식적으로 지원되지 않습니다. 파괴적인 정리 명령을 실행하기 전에 스크립트와 자습서를 테스트해보고 되돌릴 수 없는 명령을 실행하지 않도록 주의하세요.