스토리지 관리 자동화

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

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

또한, 파이프라인 효율성을 향상시킴으로써 스토리지 사용량을 관리할 수도 있습니다.

API 자동화에 대한 추가 도움이 필요하면 GitLab 커뮤니티 포럼 및 Discord를 참조하십시오.

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

API 요구 사항

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

API 인증 스코프

API를 사용하여 다음과 같은 스코프를 사용하여 인증합니다:

  • 스토리지 분석:
    • read_api 스코프를 사용한 API 액세스 읽기
    • 모든 프로젝트에 대해 최소한의 개발자 역할
  • 스토리지 정리:
    • api 스코프를 사용한 완전한 API 액세스
    • 모든 프로젝트에 대해 최소한의 관리자 역할

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

명령 줄 도구

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

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

JSON 응답 포맷을 사용하려면 jq를 설치하십시오. 자세한 내용은 효율적인 DevOps 워크플로: jq 및 CI/CD linting 자동화로 JSON 포맷팅 팁에서 확인하십시오.

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 매개변수를 통해 전달된 키=값 쌍으로 작성할 수 있습니다.

자세한 내용은 GitLab CLI 엔드포인트 설명서를 참조하십시오.

API 클라이언트 라이브러리

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

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

python-gitlab 라이브러리의 사용 사례에 대한 자세한 내용은 효율적인 DevSecOps 워크플로: python-gitlab을 활용한 API 자동화를 참조하십시오.

다른 API 클라이언트 라이브러리에 대한 자세한 내용은 서드파티 클라이언트를 참조하십시오.

참고: 코드를 더 효율적으로 작성하려면 GitLab Duo Code Suggestions을 사용하십시오.

스토리지 분석

스토리지 유형 식별

프로젝트 API 엔드포인트는 GitLab 인스턴스의 프로젝트에 대한 통계를 제공합니다. 프로젝트 API 엔드포인트를 사용하려면 statistics 키를 불리언 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. 분석할 저장소 유형을 식별하고, 프로젝트 통계 및 작업 artifact와 같은 프로젝트 속성에서 정보를 수집합니다.
  5. 그룹별로 모든 프로젝트의 개요 및 저장소 정보를 출력합니다.

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

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

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

GitLab CLI
export GROUP_NAME="gitlab-de"

# 하위 그룹 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

# 프로젝트(아이디 48349590)에서 저장소 유형 가져오기: `artifacts` 키에 작업 artifact
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 형식의 리스트로 프로젝트 작업 artifact를 출력합니다:

[
    {
        "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 endpoint를 사용하여 작업 아티팩트 목록을 검색할 수 있습니다. 이 엔드포인트는 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 속성.

크기가 큰 파이프라인 및 작업 목록을 반복하려면 키세트 페이지네이션을 사용할 수 있습니다.

   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|-|-|-|-|-|") #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("아티팩트를 찾을 수 없습니다.")

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

$ python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py

|프로젝트|작업|아티팩트 이름|아티팩트 유형|아티팩트 크기|
|-|-|-|-|-|
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | artifacts.zip | archive | 50.0154 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | metadata.gz | metadata | 0.0001 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | job.log | trace | 0.0030 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | artifacts.zip | archive | 20.0063 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | metadata.gz | metadata | 0.0001 |
| [gitlab-de/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년 8월 8일 22: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()
            # 더 짧게: 함수 사용
            # age = calculate_age(job.created_at)

            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 키는 타임스탬프를 유닉스 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.shGitLab 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 days

    if [ $AGE -gt $AGE_THRESHOLD ];
    then
        echo "파이프라인 연령 $AGE이(가) 임계값 $AGE_THRESHOLD보다 오래되었으므로 삭제되어야 함."
        # TODO 위의 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()

오래된 파이프라인의 자동 삭제가 이슈 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"ci_pipelines에서 아티팩트를 찾는 중 예외 발생: {e}".format(e=e))

    if len(ci_job_artifacts_expiry) > 0:
        print("|프로젝트|작업|아티팩트 만료|") # Markdown 친화적인 표 시작
        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-de/playground/artifact-gen-group/gen-job-artifacts-4) | generator | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-de/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-de/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-de/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-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | generator | 30 days |
| [Gen Job Artifacts 2](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-2) | generator | ❌ N/A |
| [Gen Job Artifacts 1](https://gitlab.com/gitlab-de/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-de/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"

컨테이너 이미지 삭제에 대한 GitLab의 도움을 위한 인벤토리 접근 방식에 대한 자세한 정보는 How GitLab can help mitigate deletion of open source container images on Docker Hub를 참조하십시오.

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

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

default:
    artifacts:
        expire_in: 1 week

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

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

컨테이너 레지스트리 목록

프로젝트에서 컨테이너 레지스트리를 목록으로 표시하려면:

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-de/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-de/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-de/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-de/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 자동화 스크립트가 필요하지 않습니다.

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

  • --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-de/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("패키지 크기: {s}\n\n".format(s=render_size_mb(package_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()

저장소 관리 자동화를 위한 테스트

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

작업 아티팩트 생성

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

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

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

또는, 매일 생성되는 86MB 아티팩트 크기를 MB_COUNT 변수의 다른 값으로 줄이세요.

include:
    - remote: https://gitlab.com/gitlab-de/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예제 그룹을 참조하세요.

만료 기한이 포함된 작업 아티팩트 생성

프로젝트 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은 다음과 같은 프로젝트를 제공합니다:

  • 무작위 텍스트 덩어리를 생성하고 현재 Unix 타임스탬프를 릴리스 버전으로 하는 tar볼을 만듭니다.
  • Unix 타임스탬프를 릴리스 버전으로 사용하여 tar볼을 일반적 패키지 레지스트리에 업로드합니다.

일반적 패키지를 생성하려면 다음 독립형 .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'

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

포크를 위한 비용 요소로 저장소 사용량을 테스트하려면 다음 프로젝트를 사용하세요:

커뮤니티 리소스

다음 리소스는 공식적으로 지원되지 않습니다. 파괴적인 정리 명령을 실행하기 전에 스크립트와 자습서를 테스트하여 복구할 수 없는 결과가 나오지 않도록 주의하세요.