InfoGrab DocsInfoGrab Docs

스토리지 관리 자동화

요약

이 페이지에서는 GitLab REST API를 사용하여 스토리지 사용량을 관리하기 위한 스토리지 분석 및 정리를 자동화하는 방법을 설명합니다. 파이프라인 효율성을 개선하여 스토리지 사용량을 관리할 수도 있습니다. API 자동화에 대한 더 많은 도움이 필요하면 GitLab 커뮤니티 포럼 및 Discord를 활용하세요.

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

파이프라인 효율성을 개선하여 스토리지 사용량을 관리할 수도 있습니다.

API 자동화에 대한 더 많은 도움이 필요하면 GitLab 커뮤니티 포럼 및 Discord를 활용하세요.

Note

이 페이지의 스크립트 예제는 데모 목적으로만 제공되며 프로덕션 환경에서 사용해서는 안 됩니다. 스토리지 자동화를 위한 자체 스크립트를 설계하고 테스트하는 데 예제를 활용할 수 있습니다.

API 요구사항#

스토리지 관리를 자동화하려면 GitLab.com 또는 GitLab Self-Managed 인스턴스에서 GitLab REST API에 접근할 수 있어야 합니다.

API 인증 스코프#

API로 인증하려면 다음 스코프를 사용하세요:

  • 스토리지 분석:

read_api 스코프로 읽기 전용 API 접근.

  • 모든 프로젝트에서 Developer, Maintainer, 또는 Owner 권한.

  • 스토리지 정리:

api 스코프로 전체 API 접근.

  • 모든 프로젝트에서 Maintainer 또는 Owner 권한.

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와 함께 이러한 도구를 사용하려면:

export GITLAB_TOKEN=xxx

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/user" | jq
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 클라이언트 라이브러리에 대한 자세한 내용은 서드파티 클라이언트를 참조하세요.

Note

GitLab Duo Code Suggestions를 사용하면 코드를 더 효율적으로 작성할 수 있습니다.

스토리지 분석#

스토리지 유형 식별#

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

  • storage_size: 전체 스토리지

  • lfs_objects_size: LFS 객체 스토리지

  • job_artifacts_size: job 아티팩트 스토리지

  • packages_size: 패키지 스토리지

  • repository_size: Git 리포지터리 스토리지

  • snippets_size: 스니펫 스토리지

  • uploads_size: 업로드 스토리지

  • wiki_size: Wiki 스토리지

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

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
}
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
}
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
}

프로젝트 및 그룹의 스토리지 분석#

여러 프로젝트와 그룹의 분석을 자동화할 수 있습니다. 예를 들어 최상위 네임스페이스 레벨에서 시작하여 모든 하위 그룹과 프로젝트를 재귀적으로 분석할 수 있습니다. 또한 다양한 스토리지 유형을 분석할 수 있습니다.

다음은 여러 하위 그룹과 프로젝트를 분석하는 알고리즘의 예시입니다:

  • 최상위 네임스페이스 ID를 가져옵니다. 네임스페이스/그룹 개요에서 ID 값을 복사할 수 있습니다.

  • 최상위 그룹에서 모든 하위 그룹을 가져와 ID를 목록에 저장합니다.

  • 모든 그룹을 순환하며 각 그룹의 프로젝트를 모두 가져와 ID를 목록에 저장합니다.

  • 분석할 스토리지 유형을 식별하고 프로젝트 통계, job 아티팩트 등 프로젝트 속성에서 정보를 수집합니다.

  • 그룹별로 그룹화된 모든 프로젝트와 해당 스토리지 정보의 개요를 출력합니다.

glab을 사용하는 셸 방식은 소규모 분석에 더 적합합니다. 대규모 분석의 경우 API 클라이언트 라이브러리를 사용하는 스크립트를 사용해야 합니다. 이러한 스크립트는 가독성, 데이터 저장, 흐름 제어, 테스트, 재사용성을 향상시킬 수 있습니다.

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

이 알고리즘을 구현하려면:

export GROUP_NAME="gitlab-da"

# Return subgroup IDs
glab api groups/$GROUP_NAME/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
12034712
67218622
67162711
67640130
16058698
12034604

# Loop over all subgroups to get subgroups, until the result set is empty. Example group: 12034712
glab api groups/12034712/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
56595735
70677315
67218606
70812167

# Lowest group level
glab api groups/56595735/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
# empty result, return and continue with analysis

# Fetch projects from all collected groups. Example group: 56595735
glab api groups/56595735/projects | jq --compact-output '.[]' | jq --compact-output '.id'
48349590
48349263
38520467
38520405

# Fetch storage types from a project (ID 48349590): Job artifacts in the `artifacts` key
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}]
#!/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') # token requires developer permissions
PROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional
GROUP_ID = os.environ.get('GL_GROUP_ID') #optional

if __name__ == "__main__":
    if not GITLAB_TOKEN:
        print("🤔 Please set the GL_TOKEN env variable.")
        sys.exit(1)

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

    # Collect all projects, or prefer projects from a group id, or a project id
    projects = []

    # Direct project ID
    if PROJECT_ID:
        projects.append(gl.projects.get(PROJECT_ID))
    # Groups and projects inside
    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']))

스크립트는 프로젝트 job 아티팩트를 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 파이프라인 스토리지 관리#

job 아티팩트는 파이프라인 스토리지의 대부분을 차지하며 job 로그도 수백 킬로바이트를 생성할 수 있습니다. 불필요한 job 아티팩트를 먼저 삭제한 후 분석을 통해 job 로그를 정리해야 합니다.

Warning

job 로그와 아티팩트를 삭제하는 것은 되돌릴 수 없는 파괴적인 작업입니다. 주의해서 사용하세요. 리포트 아티팩트, job 로그, 메타데이터 파일을 포함한 특정 파일을 삭제하면 이러한 파일을 데이터 소스로 사용하는 GitLab 기능에 영향을 미칩니다.

job 아티팩트 목록 조회#

파이프라인 스토리지를 분석하려면 Job API 엔드포인트를 사용하여 job 아티팩트 목록을 가져올 수 있습니다. 엔드포인트는 artifacts 속성의 file_type 키로 job 아티팩트를 반환합니다. file_type 키는 아티팩트 유형을 나타냅니다:

  • archive는 zip 파일로 생성된 job 아티팩트에 사용됩니다.

  • metadata는 Gzip 파일의 추가 메타데이터에 사용됩니다.

  • trace는 원시 파일인 job.log에 사용됩니다.

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

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

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

[
    {
        "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
    }
]

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

  • 모든 job 아티팩트를 수집하여 스크립트 끝에 요약 테이블로 출력.

  • 정보를 즉시 출력.

다음 예시에서 job 아티팩트는 ci_job_artifacts 목록에 수집됩니다. 스크립트는 모든 프로젝트를 순환하며 다음을 가져옵니다:

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

  • job 객체의 artifacts 속성.

키셋 페이지네이션을 사용하여 대규모 파이프라인 및 job 목록을 반복할 수 있습니다.

   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("\nDone collecting data.")

    if len(ci_job_artifacts) > 0:
        print("| Project | Job | Artifact name | Artifact type | Artifact size |\n|---------|-----|---------------|---------------|---------------|") # Start markdown friendly table
        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("No artifacts found.")

스크립트가 끝나면 job 아티팩트가 Markdown 포맷 테이블로 출력됩니다. 테이블 내용을 이슈 코멘트나 설명에 복사하거나 GitLab 리포지터리의 Markdown 파일에 채울 수 있습니다.

$ python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py

| Project | Job | Artifact name | Artifact type | Artifact size |
|---------|-----|---------------|---------------|---------------|
| [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 |

job 아티팩트 일괄 삭제#

Python 스크립트를 사용하여 일괄 삭제할 job 아티팩트 유형을 필터링할 수 있습니다.

비교를 위해 API 쿼리 결과를 필터링합니다:

  • 아티팩트 연령을 계산하기 위한 created_at 값.

  • 아티팩트가 크기 임계값을 충족하는지 확인하기 위한 size 속성.

일반적인 요청:

  • 지정된 일수보다 오래된 job 아티팩트 삭제.

  • 지정된 스토리지 양을 초과하는 job 아티팩트 삭제. 예: 100 MB.

다음 예시에서 스크립트는 job 속성을 순환하며 삭제 대상으로 표시합니다. 컬렉션 루프가 객체 잠금을 해제하면 스크립트는 삭제 표시된 job 아티팩트를 삭제합니다.

   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

            # Advanced filtering: Age and Size
            # Example: 90 days, 10 MB threshold (TODO: Make this configurable)
            threshold_age = 90 * 24 * 60 * 60
            threshold_size = 10 * 1024 * 1024

            # job age, need to parse API format: 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()
            # Shorter: Use a function
            # age = calculate_age(job.created_at)

            for a in artifacts:
                # Analysis collection code removed for readability

                # Advanced filtering: match job artifacts age and size against thresholds
                if (float(age) > float(threshold_age)) or (float(a['size']) > float(threshold_size)):
                    # mark job for deletion (cannot delete inside the loop)
                    jobs_marked_delete_artifacts.append(job)

    print("\nDone collecting data.")

    # Advanced filtering: Delete all job artifacts marked to being deleted.
    for job in jobs_marked_delete_artifacts:
        # delete the artifact
        print("DEBUG", job)
        job.delete_artifacts()

    # Print collection summary (removed for readability)

프로젝트의 모든 job 아티팩트 삭제#

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

아티팩트 삭제는 삭제할 아티팩트 수에 따라 몇 분에서 몇 시간이 걸릴 수 있습니다. 이후 API에 대한 분석 쿼리가 아티팩트를 오탐(false-positive) 결과로 반환할 수 있습니다. 결과로 인한 혼란을 피하려면 즉시 추가 API 요청을 실행하지 마세요.

가장 최근에 성공한 job의 아티팩트는 기본적으로 유지됩니다.

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

export GL_PROJECT_ID=48349590

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" --request DELETE "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID/artifacts"
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
        project.artifacts.delete()

job 로그 삭제#

job 로그를 삭제하면 전체 job이 지워집니다.

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()를 사용하세요. 이 API 호출이 차단되지 않도록 job 아티팩트를 삭제하는 호출 사이에 스크립트를 짧은 시간 동안 대기하도록 설정하세요:

    for job in jobs_marked_delete_artifacts:
        # delete the artifacts and job log
        print("DEBUG", job)
        #job.delete_artifacts()
        job.erase()
        # Sleep for 1 second
        time.sleep(1)

job 로그에 대한 보존 정책 생성 지원은 이슈 374717에서 제안되었습니다.

오래된 파이프라인 삭제#

파이프라인은 전체 스토리지 사용량에 추가되지 않지만 필요한 경우 자동 삭제를 자동화할 수 있습니다.

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

Note

created_at 키는 타임스탬프에서 Unix 에포크 시간으로 변환해야 합니다. 예: 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가 내보내져 있습니다. 기본값은 GitLab 사전 정의 변수 CI_PROJECT_ID입니다.

  • GitLab 인스턴스 URL을 가리키는 환경 변수 CI_SERVER_HOST가 내보내져 있습니다.

전체 스크립트 get_cicd_pipelines_compare_age_threshold_example.shGitLab API with Linux Shell 프로젝트에 있습니다.

#!/bin/bash

# Required programs:
# - GitLab CLI (glab): https://docs.gitlab.com/cli/
# - jq: https://jqlang.github.io/jq/

# Required variables:
# - PAT: Project Access Token with API scope and Owner role, or Personal Access Token with API scope
# - GL_PROJECT_ID: ID of the project where pipelines must be cleaned
# - AGE_THRESHOLD (optional): Maximum age in days of pipelines to keep (default: 90)

set -euo pipefail

# Constants
DEFAULT_AGE_THRESHOLD=90
SECONDS_PER_DAY=$((24 * 60 * 60))

# Functions
log_info() {
    echo "[INFO] $1"
}

log_error() {
    echo "[ERROR] $1" >&2
}

delete_pipeline() {
    local project_id=$1
    local pipeline_id=$2
    if glab api --method DELETE "projects/$project_id/pipelines/$pipeline_id"; then
        log_info "Deleted pipeline ID $pipeline_id"
    else
        log_error "Failed to delete pipeline ID $pipeline_id"
    fi
}

# Main script
main() {
    # Authenticate
    if ! glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT"; then
        log_error "Authentication failed"
        exit 1
    fi

    # Set variables
    AGE_THRESHOLD=${AGE_THRESHOLD:-$DEFAULT_AGE_THRESHOLD}
    AGE_THRESHOLD_IN_SECONDS=$((AGE_THRESHOLD * SECONDS_PER_DAY))
    GL_PROJECT_ID=${GL_PROJECT_ID:-$CI_PROJECT_ID}

    # Fetch pipelines
    PIPELINES=$(glab api --method GET "projects/$GL_PROJECT_ID/pipelines")
    if [ -z "$PIPELINES" ]; then
        log_error "Failed to fetch pipelines or no pipelines found"
        exit 1
    fi

    # Process pipelines
    echo "$PIPELINES" | jq -r '.[] | [.id, .created_at] | @tsv' | while IFS=	 read -r id created_at; do
        CREATED_AT_TS=$(date -d "$created_at" +%s)
        NOW=$(date +%s)
        AGE=$((NOW - CREATED_AT_TS))

        if [ "$AGE" -gt "$AGE_THRESHOLD_IN_SECONDS" ]; then
            log_info "Pipeline ID $id created at $created_at is older than threshold $AGE_THRESHOLD days, deleting..."
            delete_pipeline "$GL_PROJECT_ID" "$id"
        else
            log_info "Pipeline ID $id created at $created_at is not older than threshold $AGE_THRESHOLD days. Ignoring."
        fi
    done
}

main

전체 스크립트 cleanup-old-pipelines.shGitLab API with Linux Shell 프로젝트에 있습니다.

#!/bin/bash

set -euo pipefail

# Required environment variables:
# PAT: Project Access Token with API scope and Owner role, or Personal Access Token with API scope.
# Optional environment variables:
# AGE_THRESHOLD: Maximum age (in days) of pipelines to keep. Default: 90 days.
# REPO: Repository to clean up. If not set, the current repository will be used.
# CI_SERVER_HOST: GitLab server hostname.

# Function to display error message and exit
error_exit() {
    echo "Error: $1" >&2
    exit 1
}

# Validate required environment variables
[[ -z "${PAT:-}" ]] && error_exit "PAT (Project Access Token or Personal Access Token) is not set."
[[ -z "${CI_SERVER_HOST:-}" ]] && error_exit "CI_SERVER_HOST is not set."

# Set and validate AGE_THRESHOLD
AGE_THRESHOLD=${AGE_THRESHOLD:-90}
[[ ! "$AGE_THRESHOLD" =~ ^[0-9]+$ ]] && error_exit "AGE_THRESHOLD must be a positive integer."

AGE_THRESHOLD_IN_HOURS=$((AGE_THRESHOLD * 24))

echo "Deleting pipelines older than $AGE_THRESHOLD days"

# Authenticate with GitLab
glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT" || error_exit "Authentication failed"

# Delete old pipelines
delete_cmd="glab ci delete --older-than ${AGE_THRESHOLD_IN_HOURS}h"
if [[ -n "${REPO:-}" ]]; then
    delete_cmd+=" --repo $REPO"
fi

$delete_cmd || error_exit "Pipeline deletion failed"

echo "Pipeline cleanup completed."

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

        # ...

        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("Deleting pipeline", pipeline.id)
                pipeline_obj.delete()

job 아티팩트의 만료 설정 목록 조회#

아티팩트 스토리지를 관리하려면 아티팩트의 만료 시점을 업데이트하거나 구성할 수 있습니다. 아티팩트의 만료 설정은 .gitlab-ci.yml의 각 job 구성에서 설정됩니다.

여러 프로젝트가 있고 CI/CD 구성에서 job 정의가 어떻게 구성되어 있는지에 따라 만료 설정을 찾기 어려울 수 있습니다. 스크립트를 사용하여 전체 CI/CD 구성을 검색할 수 있습니다. 여기에는 extends 또는 !reference와 같이 값을 상속한 후 확인된 객체에 대한 접근이 포함됩니다.

스크립트는 병합된 CI/CD 구성 파일을 가져와 아티팩트 키를 검색하여:

  • 만료 설정이 없는 job을 식별합니다.

  • 아티팩트 만료가 구성된 job의 만료 설정을 반환합니다.

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

  • 병합된 CI/CD 구성을 생성하기 위해 스크립트는 모든 프로젝트를 순환하며 ci_lint() 메서드를 호출합니다.

  • yaml_load 함수는 병합된 구성을 추가 분석을 위해 Python 데이터 구조로 로드합니다.

  • script 키도 가진 딕셔너리는 artifacts 키가 존재할 수 있는 job 정의로 식별됩니다.

  • 그렇다면 스크립트는 하위 키 expire_in을 파싱하고 나중에 Markdown 테이블 요약에 출력하기 위해 세부 정보를 저장합니다.

    ci_job_artifacts_expiry = {}

    # Loop over projects, fetch .gitlab-ci.yml, run the linter to get the full translated config, and extract the `artifacts:` setting
    # 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]
                    # This is a job object with `script` attribute
                    if isinstance(v, dict) and 'script' in v:
                        print(".", end="", flush=True) # Get some feedback that it is still looping
                        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"Exception searching artifacts on ci_pipelines: {e}".format(e=e))

    if len(ci_job_artifacts_expiry) > 0:
        print("| Project | Job | Artifact expiry |\n|---------|-----|-----------------|") #Start markdown friendly table
        for k, details in ci_job_artifacts_expiry.items():
            if details['job_name'][0] == '.':
                continue # ignore job templates that start with a '.'
            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.

  • job 이름.

  • artifacts:expire_in 설정, 또는 설정이 없는 경우 N/A.

스크립트는 다음 job 템플릿을 출력하지 않습니다:

  • . 문자로 시작하는 템플릿.

  • 아티팩트를 생성하는 런타임 job 객체로 인스턴스화되지 않은 템플릿.

export GL_GROUP_ID=56595735

# Install script dependencies
python3 -m pip install 'python-gitlab[yaml]'

python3 get_all_cicd_config_artifacts_expiry.py

| Project | Job | Artifact expiry |
|---------|-----|-----------------|
| [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 프로젝트에 있습니다.

또는 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를 참조하세요.

job 아티팩트의 기본 만료 설정#

프로젝트의 job 아티팩트에 대한 기본 만료를 설정하려면 .gitlab-ci.yml 파일에 expire_in 값을 지정하세요:

default:
    artifacts:
        expire_in: 1 week

컨테이너 레지스트리 스토리지 관리#

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

컨테이너 레지스트리 목록 조회#

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

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
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"
3401613

컨테이너 이미지 일괄 삭제#

컨테이너 이미지 태그를 일괄 삭제할 때 다음을 구성할 수 있습니다:

  • 유지(name_regex_keep)하거나 삭제(name_regex_delete)할 태그 이름 및 이미지에 대한 일치 정규식

  • 태그 이름과 일치하는 유지할 이미지 태그 수(keep_n)

  • 이미지 태그를 삭제할 수 있는 일수(older_than)

Note

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

            # Cleanup: Keep only the latest tag
            repository.tags.delete_in_bulk(keep_n=1)
            # Cleanup: Delete all tags older than 1 month
            repository.tags.delete_in_bulk(older_than="1m")
            # Cleanup: Delete all tags matching the regex `v.*`, and keep the latest 2 tags
            repository.tags.delete_in_bulk(name_regex_delete="v.+", keep_n=2)

컨테이너 정리 정책 생성#

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

속성을 본문 파라미터로 전송하려면:

  • 표준 입력에서 읽으려면 --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("Package name: {p} File name: {f} Size {s}".format(
                    p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))

                package_size =+ package_file.size

            print("Package size: {s}\n\n".format(s=render_size_mb(package_size)))

            threshold_size = 10 * 1024 * 1024

            if (package_size > float(threshold_size)):
                print("Package size {s} > threshold {t}, deleting package.".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("Package age {a} > threshold {t}, deleting package.".format(
                    a=render_age_time(package_age), t=render_age_time(threshold_age)))
                package.delete()

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

Package name: generator File name: nighly.tar.gz Size 10.0017
Package size: 10.0017
Package size 10.0017 > threshold 10.0000, deleting package.

Package name: generator File name: 1-nightly.tar.gz Size 1.0004
Package size: 1.0004

Package name: generator File name: 10-nightly.tar.gz Size 10.0018
Package name: generator File name: 20-nightly.tar.gz Size 20.0033
Package size: 20.0033
Package size 20.0033 > threshold 10.0000, deleting package.

Dependency Proxy#

정리 정책API를 사용하여 캐시를 지우는 방법을 검토하세요.

출력 가독성 향상#

타임스탬프 초를 기간 포맷으로 변환하거나 원시 바이트를 더 이해하기 쉬운 포맷으로 출력해야 할 수 있습니다. 다음 헬퍼 함수를 사용하여 가독성을 높일 수 있습니다:

# Current Unix timestamp
date +%s

# Convert `created_at` date time with timezone to Unix timestamp
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))

# Convert `created_at` date time with timezone to Unix timestamp
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()

스토리지 관리 자동화 테스트#

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

job 아티팩트 생성#

CI/CD job 매트릭스 빌드를 사용하여 가짜 아티팩트 블롭을 생성하는 테스트 프로젝트를 만드세요. 매일 아티팩트를 생성하는 CI/CD 파이프라인을 추가하세요.

  • 새 프로젝트를 만드세요.

  • job 아티팩트 생성기 구성을 포함하려면 .gitlab-ci.yml에 다음 스니펫을 추가하세요.

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

또는 MB_COUNT 변수에서 매일 생성되는 86 MB를 다른 값으로 줄일 수 있습니다.

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]

자세한 내용은 Job Artifact Generator README예제 그룹을 참조하세요.

만료가 있는 job 아티팩트 생성#

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

  • 메인 .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에서 이미지를 빌드합니다.

  • 파이프라인 스케줄을 구성하여 매일 새 이미지를 생성합니다.

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

제네릭 패키지 생성#

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

  • 임의의 텍스트 블롭을 생성하고 현재 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'

포크를 사용한 스토리지 사용량 생성#

포크의 비용 요소로 스토리지 사용량을 테스트하려면 다음 프로젝트를 사용하세요:

커뮤니티 리소스#

다음 리소스는 공식적으로 지원되지 않습니다. 되돌릴 수 없는 파괴적인 정리 명령을 실행하기 전에 스크립트와 튜토리얼을 테스트하세요.

스토리지 관리 자동화

GitLab v19.1
Tier: Free, Premium, Ultimate
Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
원문 보기
요약

이 페이지에서는 GitLab REST API를 사용하여 스토리지 사용량을 관리하기 위한 스토리지 분석 및 정리를 자동화하는 방법을 설명합니다. 파이프라인 효율성을 개선하여 스토리지 사용량을 관리할 수도 있습니다. API 자동화에 대한 더 많은 도움이 필요하면 GitLab 커뮤니티 포럼 및 Discord를 활용하세요.

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

파이프라인 효율성을 개선하여 스토리지 사용량을 관리할 수도 있습니다.

API 자동화에 대한 더 많은 도움이 필요하면 GitLab 커뮤니티 포럼 및 Discord를 활용하세요.

Note

이 페이지의 스크립트 예제는 데모 목적으로만 제공되며 프로덕션 환경에서 사용해서는 안 됩니다. 스토리지 자동화를 위한 자체 스크립트를 설계하고 테스트하는 데 예제를 활용할 수 있습니다.

API 요구사항#

스토리지 관리를 자동화하려면 GitLab.com 또는 GitLab Self-Managed 인스턴스에서 GitLab REST API에 접근할 수 있어야 합니다.

API 인증 스코프#

API로 인증하려면 다음 스코프를 사용하세요:

  • 스토리지 분석:

read_api 스코프로 읽기 전용 API 접근.

  • 모든 프로젝트에서 Developer, Maintainer, 또는 Owner 권한.

  • 스토리지 정리:

api 스코프로 전체 API 접근.

  • 모든 프로젝트에서 Maintainer 또는 Owner 권한.

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와 함께 이러한 도구를 사용하려면:

export GITLAB_TOKEN=xxx

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/user" | jq
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 클라이언트 라이브러리에 대한 자세한 내용은 서드파티 클라이언트를 참조하세요.

Note

GitLab Duo Code Suggestions를 사용하면 코드를 더 효율적으로 작성할 수 있습니다.

스토리지 분석#

스토리지 유형 식별#

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

  • storage_size: 전체 스토리지

  • lfs_objects_size: LFS 객체 스토리지

  • job_artifacts_size: job 아티팩트 스토리지

  • packages_size: 패키지 스토리지

  • repository_size: Git 리포지터리 스토리지

  • snippets_size: 스니펫 스토리지

  • uploads_size: 업로드 스토리지

  • wiki_size: Wiki 스토리지

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

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
}
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
}
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
}

프로젝트 및 그룹의 스토리지 분석#

여러 프로젝트와 그룹의 분석을 자동화할 수 있습니다. 예를 들어 최상위 네임스페이스 레벨에서 시작하여 모든 하위 그룹과 프로젝트를 재귀적으로 분석할 수 있습니다. 또한 다양한 스토리지 유형을 분석할 수 있습니다.

다음은 여러 하위 그룹과 프로젝트를 분석하는 알고리즘의 예시입니다:

  • 최상위 네임스페이스 ID를 가져옵니다. 네임스페이스/그룹 개요에서 ID 값을 복사할 수 있습니다.

  • 최상위 그룹에서 모든 하위 그룹을 가져와 ID를 목록에 저장합니다.

  • 모든 그룹을 순환하며 각 그룹의 프로젝트를 모두 가져와 ID를 목록에 저장합니다.

  • 분석할 스토리지 유형을 식별하고 프로젝트 통계, job 아티팩트 등 프로젝트 속성에서 정보를 수집합니다.

  • 그룹별로 그룹화된 모든 프로젝트와 해당 스토리지 정보의 개요를 출력합니다.

glab을 사용하는 셸 방식은 소규모 분석에 더 적합합니다. 대규모 분석의 경우 API 클라이언트 라이브러리를 사용하는 스크립트를 사용해야 합니다. 이러한 스크립트는 가독성, 데이터 저장, 흐름 제어, 테스트, 재사용성을 향상시킬 수 있습니다.

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

이 알고리즘을 구현하려면:

export GROUP_NAME="gitlab-da"

# Return subgroup IDs
glab api groups/$GROUP_NAME/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
12034712
67218622
67162711
67640130
16058698
12034604

# Loop over all subgroups to get subgroups, until the result set is empty. Example group: 12034712
glab api groups/12034712/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
56595735
70677315
67218606
70812167

# Lowest group level
glab api groups/56595735/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
# empty result, return and continue with analysis

# Fetch projects from all collected groups. Example group: 56595735
glab api groups/56595735/projects | jq --compact-output '.[]' | jq --compact-output '.id'
48349590
48349263
38520467
38520405

# Fetch storage types from a project (ID 48349590): Job artifacts in the `artifacts` key
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}]
#!/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') # token requires developer permissions
PROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional
GROUP_ID = os.environ.get('GL_GROUP_ID') #optional

if __name__ == "__main__":
    if not GITLAB_TOKEN:
        print("🤔 Please set the GL_TOKEN env variable.")
        sys.exit(1)

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

    # Collect all projects, or prefer projects from a group id, or a project id
    projects = []

    # Direct project ID
    if PROJECT_ID:
        projects.append(gl.projects.get(PROJECT_ID))
    # Groups and projects inside
    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']))

스크립트는 프로젝트 job 아티팩트를 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 파이프라인 스토리지 관리#

job 아티팩트는 파이프라인 스토리지의 대부분을 차지하며 job 로그도 수백 킬로바이트를 생성할 수 있습니다. 불필요한 job 아티팩트를 먼저 삭제한 후 분석을 통해 job 로그를 정리해야 합니다.

Warning

job 로그와 아티팩트를 삭제하는 것은 되돌릴 수 없는 파괴적인 작업입니다. 주의해서 사용하세요. 리포트 아티팩트, job 로그, 메타데이터 파일을 포함한 특정 파일을 삭제하면 이러한 파일을 데이터 소스로 사용하는 GitLab 기능에 영향을 미칩니다.

job 아티팩트 목록 조회#

파이프라인 스토리지를 분석하려면 Job API 엔드포인트를 사용하여 job 아티팩트 목록을 가져올 수 있습니다. 엔드포인트는 artifacts 속성의 file_type 키로 job 아티팩트를 반환합니다. file_type 키는 아티팩트 유형을 나타냅니다:

  • archive는 zip 파일로 생성된 job 아티팩트에 사용됩니다.

  • metadata는 Gzip 파일의 추가 메타데이터에 사용됩니다.

  • trace는 원시 파일인 job.log에 사용됩니다.

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

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

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

[
    {
        "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
    }
]

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

  • 모든 job 아티팩트를 수집하여 스크립트 끝에 요약 테이블로 출력.

  • 정보를 즉시 출력.

다음 예시에서 job 아티팩트는 ci_job_artifacts 목록에 수집됩니다. 스크립트는 모든 프로젝트를 순환하며 다음을 가져옵니다:

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

  • job 객체의 artifacts 속성.

키셋 페이지네이션을 사용하여 대규모 파이프라인 및 job 목록을 반복할 수 있습니다.

   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("\nDone collecting data.")

    if len(ci_job_artifacts) > 0:
        print("| Project | Job | Artifact name | Artifact type | Artifact size |\n|---------|-----|---------------|---------------|---------------|") # Start markdown friendly table
        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("No artifacts found.")

스크립트가 끝나면 job 아티팩트가 Markdown 포맷 테이블로 출력됩니다. 테이블 내용을 이슈 코멘트나 설명에 복사하거나 GitLab 리포지터리의 Markdown 파일에 채울 수 있습니다.

$ python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py

| Project | Job | Artifact name | Artifact type | Artifact size |
|---------|-----|---------------|---------------|---------------|
| [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 |

job 아티팩트 일괄 삭제#

Python 스크립트를 사용하여 일괄 삭제할 job 아티팩트 유형을 필터링할 수 있습니다.

비교를 위해 API 쿼리 결과를 필터링합니다:

  • 아티팩트 연령을 계산하기 위한 created_at 값.

  • 아티팩트가 크기 임계값을 충족하는지 확인하기 위한 size 속성.

일반적인 요청:

  • 지정된 일수보다 오래된 job 아티팩트 삭제.

  • 지정된 스토리지 양을 초과하는 job 아티팩트 삭제. 예: 100 MB.

다음 예시에서 스크립트는 job 속성을 순환하며 삭제 대상으로 표시합니다. 컬렉션 루프가 객체 잠금을 해제하면 스크립트는 삭제 표시된 job 아티팩트를 삭제합니다.

   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

            # Advanced filtering: Age and Size
            # Example: 90 days, 10 MB threshold (TODO: Make this configurable)
            threshold_age = 90 * 24 * 60 * 60
            threshold_size = 10 * 1024 * 1024

            # job age, need to parse API format: 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()
            # Shorter: Use a function
            # age = calculate_age(job.created_at)

            for a in artifacts:
                # Analysis collection code removed for readability

                # Advanced filtering: match job artifacts age and size against thresholds
                if (float(age) > float(threshold_age)) or (float(a['size']) > float(threshold_size)):
                    # mark job for deletion (cannot delete inside the loop)
                    jobs_marked_delete_artifacts.append(job)

    print("\nDone collecting data.")

    # Advanced filtering: Delete all job artifacts marked to being deleted.
    for job in jobs_marked_delete_artifacts:
        # delete the artifact
        print("DEBUG", job)
        job.delete_artifacts()

    # Print collection summary (removed for readability)

프로젝트의 모든 job 아티팩트 삭제#

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

아티팩트 삭제는 삭제할 아티팩트 수에 따라 몇 분에서 몇 시간이 걸릴 수 있습니다. 이후 API에 대한 분석 쿼리가 아티팩트를 오탐(false-positive) 결과로 반환할 수 있습니다. 결과로 인한 혼란을 피하려면 즉시 추가 API 요청을 실행하지 마세요.

가장 최근에 성공한 job의 아티팩트는 기본적으로 유지됩니다.

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

export GL_PROJECT_ID=48349590

curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" --request DELETE "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID/artifacts"
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
        project.artifacts.delete()

job 로그 삭제#

job 로그를 삭제하면 전체 job이 지워집니다.

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()를 사용하세요. 이 API 호출이 차단되지 않도록 job 아티팩트를 삭제하는 호출 사이에 스크립트를 짧은 시간 동안 대기하도록 설정하세요:

    for job in jobs_marked_delete_artifacts:
        # delete the artifacts and job log
        print("DEBUG", job)
        #job.delete_artifacts()
        job.erase()
        # Sleep for 1 second
        time.sleep(1)

job 로그에 대한 보존 정책 생성 지원은 이슈 374717에서 제안되었습니다.

오래된 파이프라인 삭제#

파이프라인은 전체 스토리지 사용량에 추가되지 않지만 필요한 경우 자동 삭제를 자동화할 수 있습니다.

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

Note

created_at 키는 타임스탬프에서 Unix 에포크 시간으로 변환해야 합니다. 예: 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가 내보내져 있습니다. 기본값은 GitLab 사전 정의 변수 CI_PROJECT_ID입니다.

  • GitLab 인스턴스 URL을 가리키는 환경 변수 CI_SERVER_HOST가 내보내져 있습니다.

전체 스크립트 get_cicd_pipelines_compare_age_threshold_example.shGitLab API with Linux Shell 프로젝트에 있습니다.

#!/bin/bash

# Required programs:
# - GitLab CLI (glab): https://docs.gitlab.com/cli/
# - jq: https://jqlang.github.io/jq/

# Required variables:
# - PAT: Project Access Token with API scope and Owner role, or Personal Access Token with API scope
# - GL_PROJECT_ID: ID of the project where pipelines must be cleaned
# - AGE_THRESHOLD (optional): Maximum age in days of pipelines to keep (default: 90)

set -euo pipefail

# Constants
DEFAULT_AGE_THRESHOLD=90
SECONDS_PER_DAY=$((24 * 60 * 60))

# Functions
log_info() {
    echo "[INFO] $1"
}

log_error() {
    echo "[ERROR] $1" >&2
}

delete_pipeline() {
    local project_id=$1
    local pipeline_id=$2
    if glab api --method DELETE "projects/$project_id/pipelines/$pipeline_id"; then
        log_info "Deleted pipeline ID $pipeline_id"
    else
        log_error "Failed to delete pipeline ID $pipeline_id"
    fi
}

# Main script
main() {
    # Authenticate
    if ! glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT"; then
        log_error "Authentication failed"
        exit 1
    fi

    # Set variables
    AGE_THRESHOLD=${AGE_THRESHOLD:-$DEFAULT_AGE_THRESHOLD}
    AGE_THRESHOLD_IN_SECONDS=$((AGE_THRESHOLD * SECONDS_PER_DAY))
    GL_PROJECT_ID=${GL_PROJECT_ID:-$CI_PROJECT_ID}

    # Fetch pipelines
    PIPELINES=$(glab api --method GET "projects/$GL_PROJECT_ID/pipelines")
    if [ -z "$PIPELINES" ]; then
        log_error "Failed to fetch pipelines or no pipelines found"
        exit 1
    fi

    # Process pipelines
    echo "$PIPELINES" | jq -r '.[] | [.id, .created_at] | @tsv' | while IFS=	 read -r id created_at; do
        CREATED_AT_TS=$(date -d "$created_at" +%s)
        NOW=$(date +%s)
        AGE=$((NOW - CREATED_AT_TS))

        if [ "$AGE" -gt "$AGE_THRESHOLD_IN_SECONDS" ]; then
            log_info "Pipeline ID $id created at $created_at is older than threshold $AGE_THRESHOLD days, deleting..."
            delete_pipeline "$GL_PROJECT_ID" "$id"
        else
            log_info "Pipeline ID $id created at $created_at is not older than threshold $AGE_THRESHOLD days. Ignoring."
        fi
    done
}

main

전체 스크립트 cleanup-old-pipelines.shGitLab API with Linux Shell 프로젝트에 있습니다.

#!/bin/bash

set -euo pipefail

# Required environment variables:
# PAT: Project Access Token with API scope and Owner role, or Personal Access Token with API scope.
# Optional environment variables:
# AGE_THRESHOLD: Maximum age (in days) of pipelines to keep. Default: 90 days.
# REPO: Repository to clean up. If not set, the current repository will be used.
# CI_SERVER_HOST: GitLab server hostname.

# Function to display error message and exit
error_exit() {
    echo "Error: $1" >&2
    exit 1
}

# Validate required environment variables
[[ -z "${PAT:-}" ]] && error_exit "PAT (Project Access Token or Personal Access Token) is not set."
[[ -z "${CI_SERVER_HOST:-}" ]] && error_exit "CI_SERVER_HOST is not set."

# Set and validate AGE_THRESHOLD
AGE_THRESHOLD=${AGE_THRESHOLD:-90}
[[ ! "$AGE_THRESHOLD" =~ ^[0-9]+$ ]] && error_exit "AGE_THRESHOLD must be a positive integer."

AGE_THRESHOLD_IN_HOURS=$((AGE_THRESHOLD * 24))

echo "Deleting pipelines older than $AGE_THRESHOLD days"

# Authenticate with GitLab
glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT" || error_exit "Authentication failed"

# Delete old pipelines
delete_cmd="glab ci delete --older-than ${AGE_THRESHOLD_IN_HOURS}h"
if [[ -n "${REPO:-}" ]]; then
    delete_cmd+=" --repo $REPO"
fi

$delete_cmd || error_exit "Pipeline deletion failed"

echo "Pipeline cleanup completed."

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

        # ...

        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("Deleting pipeline", pipeline.id)
                pipeline_obj.delete()

job 아티팩트의 만료 설정 목록 조회#

아티팩트 스토리지를 관리하려면 아티팩트의 만료 시점을 업데이트하거나 구성할 수 있습니다. 아티팩트의 만료 설정은 .gitlab-ci.yml의 각 job 구성에서 설정됩니다.

여러 프로젝트가 있고 CI/CD 구성에서 job 정의가 어떻게 구성되어 있는지에 따라 만료 설정을 찾기 어려울 수 있습니다. 스크립트를 사용하여 전체 CI/CD 구성을 검색할 수 있습니다. 여기에는 extends 또는 !reference와 같이 값을 상속한 후 확인된 객체에 대한 접근이 포함됩니다.

스크립트는 병합된 CI/CD 구성 파일을 가져와 아티팩트 키를 검색하여:

  • 만료 설정이 없는 job을 식별합니다.

  • 아티팩트 만료가 구성된 job의 만료 설정을 반환합니다.

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

  • 병합된 CI/CD 구성을 생성하기 위해 스크립트는 모든 프로젝트를 순환하며 ci_lint() 메서드를 호출합니다.

  • yaml_load 함수는 병합된 구성을 추가 분석을 위해 Python 데이터 구조로 로드합니다.

  • script 키도 가진 딕셔너리는 artifacts 키가 존재할 수 있는 job 정의로 식별됩니다.

  • 그렇다면 스크립트는 하위 키 expire_in을 파싱하고 나중에 Markdown 테이블 요약에 출력하기 위해 세부 정보를 저장합니다.

    ci_job_artifacts_expiry = {}

    # Loop over projects, fetch .gitlab-ci.yml, run the linter to get the full translated config, and extract the `artifacts:` setting
    # 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]
                    # This is a job object with `script` attribute
                    if isinstance(v, dict) and 'script' in v:
                        print(".", end="", flush=True) # Get some feedback that it is still looping
                        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"Exception searching artifacts on ci_pipelines: {e}".format(e=e))

    if len(ci_job_artifacts_expiry) > 0:
        print("| Project | Job | Artifact expiry |\n|---------|-----|-----------------|") #Start markdown friendly table
        for k, details in ci_job_artifacts_expiry.items():
            if details['job_name'][0] == '.':
                continue # ignore job templates that start with a '.'
            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.

  • job 이름.

  • artifacts:expire_in 설정, 또는 설정이 없는 경우 N/A.

스크립트는 다음 job 템플릿을 출력하지 않습니다:

  • . 문자로 시작하는 템플릿.

  • 아티팩트를 생성하는 런타임 job 객체로 인스턴스화되지 않은 템플릿.

export GL_GROUP_ID=56595735

# Install script dependencies
python3 -m pip install 'python-gitlab[yaml]'

python3 get_all_cicd_config_artifacts_expiry.py

| Project | Job | Artifact expiry |
|---------|-----|-----------------|
| [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 프로젝트에 있습니다.

또는 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를 참조하세요.

job 아티팩트의 기본 만료 설정#

프로젝트의 job 아티팩트에 대한 기본 만료를 설정하려면 .gitlab-ci.yml 파일에 expire_in 값을 지정하세요:

default:
    artifacts:
        expire_in: 1 week

컨테이너 레지스트리 스토리지 관리#

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

컨테이너 레지스트리 목록 조회#

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

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
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"
3401613

컨테이너 이미지 일괄 삭제#

컨테이너 이미지 태그를 일괄 삭제할 때 다음을 구성할 수 있습니다:

  • 유지(name_regex_keep)하거나 삭제(name_regex_delete)할 태그 이름 및 이미지에 대한 일치 정규식

  • 태그 이름과 일치하는 유지할 이미지 태그 수(keep_n)

  • 이미지 태그를 삭제할 수 있는 일수(older_than)

Note

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

            # Cleanup: Keep only the latest tag
            repository.tags.delete_in_bulk(keep_n=1)
            # Cleanup: Delete all tags older than 1 month
            repository.tags.delete_in_bulk(older_than="1m")
            # Cleanup: Delete all tags matching the regex `v.*`, and keep the latest 2 tags
            repository.tags.delete_in_bulk(name_regex_delete="v.+", keep_n=2)

컨테이너 정리 정책 생성#

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

속성을 본문 파라미터로 전송하려면:

  • 표준 입력에서 읽으려면 --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("Package name: {p} File name: {f} Size {s}".format(
                    p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))

                package_size =+ package_file.size

            print("Package size: {s}\n\n".format(s=render_size_mb(package_size)))

            threshold_size = 10 * 1024 * 1024

            if (package_size > float(threshold_size)):
                print("Package size {s} > threshold {t}, deleting package.".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("Package age {a} > threshold {t}, deleting package.".format(
                    a=render_age_time(package_age), t=render_age_time(threshold_age)))
                package.delete()

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

Package name: generator File name: nighly.tar.gz Size 10.0017
Package size: 10.0017
Package size 10.0017 > threshold 10.0000, deleting package.

Package name: generator File name: 1-nightly.tar.gz Size 1.0004
Package size: 1.0004

Package name: generator File name: 10-nightly.tar.gz Size 10.0018
Package name: generator File name: 20-nightly.tar.gz Size 20.0033
Package size: 20.0033
Package size 20.0033 > threshold 10.0000, deleting package.

Dependency Proxy#

정리 정책API를 사용하여 캐시를 지우는 방법을 검토하세요.

출력 가독성 향상#

타임스탬프 초를 기간 포맷으로 변환하거나 원시 바이트를 더 이해하기 쉬운 포맷으로 출력해야 할 수 있습니다. 다음 헬퍼 함수를 사용하여 가독성을 높일 수 있습니다:

# Current Unix timestamp
date +%s

# Convert `created_at` date time with timezone to Unix timestamp
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))

# Convert `created_at` date time with timezone to Unix timestamp
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()

스토리지 관리 자동화 테스트#

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

job 아티팩트 생성#

CI/CD job 매트릭스 빌드를 사용하여 가짜 아티팩트 블롭을 생성하는 테스트 프로젝트를 만드세요. 매일 아티팩트를 생성하는 CI/CD 파이프라인을 추가하세요.

  • 새 프로젝트를 만드세요.

  • job 아티팩트 생성기 구성을 포함하려면 .gitlab-ci.yml에 다음 스니펫을 추가하세요.

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

또는 MB_COUNT 변수에서 매일 생성되는 86 MB를 다른 값으로 줄일 수 있습니다.

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]

자세한 내용은 Job Artifact Generator README예제 그룹을 참조하세요.

만료가 있는 job 아티팩트 생성#

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

  • 메인 .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에서 이미지를 빌드합니다.

  • 파이프라인 스케줄을 구성하여 매일 새 이미지를 생성합니다.

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

제네릭 패키지 생성#

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

  • 임의의 텍스트 블롭을 생성하고 현재 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'

포크를 사용한 스토리지 사용량 생성#

포크의 비용 요소로 스토리지 사용량을 테스트하려면 다음 프로젝트를 사용하세요:

커뮤니티 리소스#

다음 리소스는 공식적으로 지원되지 않습니다. 되돌릴 수 없는 파괴적인 정리 명령을 실행하기 전에 스크립트와 튜토리얼을 테스트하세요.