自动化存储管理
- 层级:免费版、高级版、旗舰版
- 提供方式:GitLab.com、GitLab 自托管、GitLab 专用实例
本页介绍如何通过 GitLab REST API 自动化存储分析和清理,以管理您的存储使用情况。
您还可以通过提升管道效率来管理存储使用情况。
如需更多关于 API 自动化的帮助,您也可以使用 GitLab 社区论坛和 Discord。
本页中的脚本示例仅用于演示目的,不应在生产环境中使用。您可以使用这些示例设计和测试自己的存储自动化脚本。
API 要求
要自动化存储管理,您的 GitLab.com SaaS 或 GitLab 自托管实例必须能够访问 GitLab REST API。
API 鉴权范围
使用以下作用域对 API 进行鉴权:
- 存储分析:
- 使用
read_api作用域获取读取 API 访问权限。 - 所有项目至少具备开发者角色。
- 使用
- 存储清理:
- 使用
api作用域获取完全 API 访问权限。 - 所有项目至少具备维护者角色。
- 使用
您可以使用命令行工具或编程语言与 REST API 交互。
命令行工具
要发送 API 请求,请安装以下任一工具:
- 通过您偏好的包管理器安装 curl。
- GitLab CLI 并使用
glab api子命令。
要格式化 JSON 响应,请安装 jq。更多信息请参见《高效 DevOps 工作流技巧:使用 jq 格式化 JSON 及 CI/CD 代码检查自动化》。
使用这些工具与 REST API 交互:
export GITLAB_TOKEN=xxx
curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/user" | jqglab 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 库用例的更多信息,请参见《高效 DevSecOps 工作流:实践 python-gitlab API 自动化》。
其他 API 客户端库的信息,请参见 第三方客户端。
使用 GitLab Duo 代码建议 更高效地编写代码。
存储分析
识别存储类型
项目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: 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保存到列表中。
- 确定要分析的存储类型,并从项目属性(如项目统计信息和作业制品)中收集信息。
- 打印所有项目的概览,按组分组,及其存储信息。
使用glab的Shell方法可能更适合较小的分析。对于较大的分析,您应该使用利用API客户端库的脚本。这类脚本可提升可读性、数据存储、流程控制、测试及复用性。
为确保脚本不会触发API速率限制,以下示例代码未针对并行API请求做优化。
若要实现该算法:
export GROUP_NAME="gitlab-da"
# 返回子组ID
glab api groups/$GROUP_NAME/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
12034712
67218622
67162711
67640130
16058698
12034604
# 遍历所有子组以获取子组,直至结果集为空。示例组:12034712
glab api groups/12034712/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
56595735
70677315
67218606
70812167
# 最低组层级
glab api groups/56595735/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
# 结果为空,返回并继续分析
# 从所有收集的组中获取项目。示例组:56595735
glab api groups/56595735/projects | jq --compact-output '.[]' | jq --compact-output '.id'
48349590
48349263
38520467
38520405
# 从项目(ID 48349590)获取存储类型:作业工件位于 `artifacts` 键中
glab api projects/48349590/jobs | jq --compact-output '.[]' | jq --compact-output '.id, .artifacts'
4828297946
[{"file_type":"archive","size":52444993,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":156,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3140,"filename":"job.log","file_format":null}]
4828297945
[{"file_type":"archive","size":20978113,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3147,"filename":"job.log","file_format":null}]
4828297944
[{"file_type":"archive","size":10489153,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":158,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3146,"filename":"job.log","file_format":null}]
4828297943
[{"file_type":"archive","size":5244673,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3145,"filename":"job.log","file_format":null}]
4828297940
[{"file_type":"archive","size":1049089,"filename":"artifacts.zip","file_format":"zip"},{"file_type":"metadata","size":157,"filename":"metadata.gz","file_format":"gzip"},{"file_type":"trace","size":3140,"filename":"job.log","file_format":null}] #!/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'])该脚本以JSON格式列表输出项目的作业工件:
[
{
"file_type": "archive",
"size": 1049089,
"filename": "artifacts.zip",
"file_format": "zip"
},
{
"file_type": "metadata",
"size": 157,
"filename": "metadata.gz",
"file_format": "gzip"
},
{
"file_type": "trace",
"size": 3146,
"filename": "job.log",
"file_format": null
}
]管理CI/CD流水线存储
作业工件消耗了大部分流水线存储,而作业日志也可能生成数百千字节的数据。你应该先删除不必要的作业工件,然后在分析后清理作业日志。
删除作业日志和工件是一项破坏性操作,无法撤销。请谨慎使用。删除某些文件(包括报告工件、作业日志和元数据文件)会影响使用这些文件作为数据源的GitLab功能。
列出作业工件
要分析流水线存储,你可以使用作业API端点来获取作业工件列表。该端点在artifacts属性中返回作业工件的file_type键。file_type键表示工件类型:
archive用于生成的作业工件作为zip文件。metadata用于Gzip文件中的额外元数据。trace用于原始文件的job.log。
作业工件提供了一个可写入磁盘缓存文件的数据结构,你可以用它来测试实现。
基于获取所有项目的示例代码,你可以扩展Python脚本来做更多分析。
以下示例显示了对项目中作业工件查询的响应:
[
{
"file_type": "archive",
"size": 1049089,
"filename": "artifacts.zip",
"file_format": "zip"
},
{
"file_type": "metadata",
"size": 157,
"filename": "metadata.gz",
"file_format": "gzip"
},
{
"file_type": "trace",
"size": 3146,
"filename": "job.log",
"file_format": null
}
]根据你实现脚本的方式,你可以选择:
- 收集所有作业工件并在脚本末尾打印摘要表格。
- 立即打印信息。
在以下示例中,作业工件被收集到ci_job_artifacts列表中。脚本遍历所有项目并获取:
- 包含所有属性的
project_obj对象变量。 - 来自
job对象的artifacts属性。
你可以使用keyset分页来迭代大型流水线和作业列表。
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.")脚本末尾,作业工件以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 |批量删除作业工件
你可以使用一个 Python 脚本来过滤要批量删除的作业工件类型。
过滤 API 查询结果以进行比较:
- 使用
created_at值计算工件的年龄。 - 使用
size属性确定工件是否符合大小阈值。
典型请求:
- 删除早于指定天数的作业工件。
- 删除超过指定存储量的作业工件。例如,100 MB。
在下面的示例中,脚本遍历作业属性并标记它们以供删除。当集合循环移除对象锁时,脚本会删除标记为删除的作业工件。
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 天,10 MB 阈值(TODO:使其可配置)
threshold_age = 90 * 24 * 60 * 60
threshold_size = 10 * 1024 * 1024
# 作业年龄,需解析 API 格式:2023-08-08T22:41:08.270Z
created_at = datetime.datetime.strptime(job.created_at, '%Y-%m-%dT%H:%M:%S.%fZ')
now = datetime.datetime.now()
age = (now - created_at).total_seconds()
# 更简短:使用函数
# 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 请求。
默认情况下,会保留最近成功作业的工件。
要删除项目的所有作业工件:
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()删除作业日志
当你删除作业日志时,也会擦除整个作业。
使用 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.erase() 代替 job.delete_artifacts()。为了避免这个 API 调用被阻止,请将脚本设置为在删除作业工件的调用之间休眠很短的时间:
for job in jobs_marked_delete_artifacts:
# 删除工件和作业日志
print("DEBUG", job)
#job.delete_artifacts()
job.erase()
# 休眠 1 秒
time.sleep(1)支持创建作业日志保留策略的建议已在 issue 374717 中提出。
删除旧流水线
流水线不会增加整体存储使用量,但如果需要,你可以自动化删除它们。
若要根据特定日期删除流水线,请指定 created_at 键。
你可以用该日期计算当前日期与流水线创建时间的差值。如果年龄超过阈值,则删除该流水线。
created_at 键必须从时间戳转换为 Unix 时间戳(epoch time),例如使用 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。 - 导出的环境变量
CI_SERVER_HOST指向 GitLab 实例的 URL。
完整脚本 get_cicd_pipelines_compare_age_threshold_example.sh 位于 GitLab API with Linux Shell 项目中。
#!/bin/bash
# 所需程序:
# - GitLab CLI (glab): https://docs.gitlab.com/ee/editor_extensions/gitlab_cli/
# - jq: https://jqlang.github.io/jq/
# 所需变量:
# - 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
# 常量
DEFAULT_AGE_THRESHOLD=90
SECONDS_PER_DAY=$((24 * 60 * 60))
# 函数
log_info() {
echo "[信息] $1"
}
log_error() {
echo "[错误] $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 "已删除流水线 ID $pipeline_id"
else
log_error "删除流水线 ID $pipeline_id 失败"
fi
}
# 主脚本
main() {
# 认证
if ! glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT"; then
log_error "认证失败"
exit 1
fi
# 设置变量
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}
# 获取流水线
PIPELINES=$(glab api --method GET "projects/$GL_PROJECT_ID/pipelines")
if [ -z "$PIPELINES" ]; then
log_error "获取流水线失败或未找到流水线"
exit 1
fi
# 处理流水线
echo "$PIPELINES" | jq -r '.[] | [.id, .created_at] | @tsv' | while IFS=$'\t' 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 "流水线 ID $id 创建于 $created_at,超过阈值 $AGE_THRESHOLD 天,正在删除..."
delete_pipeline "$GL_PROJECT_ID" "$id"
else
log_info "流水线 ID $id 创建于 $created_at,未超过阈值 $AGE_THRESHOLD 天。忽略。"
fi
done
}
main完整脚本 cleanup-old-pipelines.sh 位于 GitLab API with Linux Shell 项目中。
#!/bin/bash
set -euo pipefail
# 所需环境变量:
# PAT: Project Access Token with API scope and Owner role, or Personal Access Token with API scope.
# 可选环境变量:
# 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.
# 显示错误消息并退出的函数
error_exit() {
echo "错误: $1" >&2
exit 1
}
# 验证所需环境变量
[[ -z "${PAT:-}" ]] && error_exit "PAT (项目访问令牌或个人访问令牌) 未设置。"
[[ -z "${CI_SERVER_HOST:-}" ]] && error_exit "CI_SERVER_HOST 未设置。"
# 设置并验证 AGE_THRESHOLD
AGE_THRESHOLD=${AGE_THRESHOLD:-90}
[[ ! "$AGE_THRESHOLD" =~ ^[0-9]+$ ]] && error_exit "AGE_THRESHOLD 必须是一个正整数。"
AGE_THRESHOLD_IN_HOURS=$((AGE_THRESHOLD * 24))
echo "正在删除超过 $AGE_THRESHOLD 天的流水线"
# 使用 GitLab 进行身份验证
glab auth login --hostname "$CI_SERVER_HOST" --token "$PAT" || error_exit "身份验证失败"
# 删除旧的流水线
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 "流水线删除失败"
echo "流水线清理完成。"您也可以使用 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()列出作业工件的过期设置
要管理工件存储,您可以更新或配置当工件过期时的时间。
工件的过期设置是在每个作业配置中的 .gitlab-ci.yml 中配置的。
如果有多个项目,且基于CI/CD配置中作业定义的组织方式,可能难以定位过期设置。您可使用脚本来搜索整个CI/CD配置。这包括访问继承值(如 extends 或 !reference)后解析的对象。
该脚本会获取合并后的CI/CD配置文件,并搜索 artifacts 键以:
- 识别未配置过期设置的作业。
- 返回已配置工件过期的作业的过期设置。
以下流程说明脚本如何搜索工件过期设置:
- 为生成合并的CI/CD配置,脚本遍历所有项目并调用
ci_lint()方法。 yaml_load函数将合并配置加载至Python数据结构以供进一步分析。- 包含
script键的字典会被识别为作业定义,其中可能存在artifacts键。 - 若存在,脚本会解析子键
expire_in并存储详情,后续用于打印至Markdown表格摘要。
ci_job_artifacts_expiry = {}
# 遍历项目,获取 .gitlab-ci.yml,运行linter以获取完整转换后的配置,并提取 `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"Exception searching artifacts on ci_pipelines: {e}".format(e=e))
if len(ci_job_artifacts_expiry) > 0:
print("| 项目 | 作业 | 工件过期时间 |\n|---------|-----|-----------------|") # 开始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安装脚本依赖
python3 -m pip install ‘python-gitlab[yaml]’
python3 get_all_cicd_config_artifacts_expiry.py
| 项目 | 任务 | 工件过期时间 |
|---|---|---|
| Gen Job Artifacts 4 | generator | 30 天 |
| Gen Job Artifacts with expiry and included jobs | included-job10 | 10 天 |
| Gen Job Artifacts with expiry and included jobs | included-job1 | 1 天 |
| Gen Job Artifacts with expiry and included jobs | included-job30 | 30 天 |
| Gen Job Artifacts with expiry and included jobs | generator | 30 天 |
| Gen Job Artifacts 2 | generator | ❌ N/A |
| 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"有关库存方法的更多信息,请参阅 GitLab 如何帮助缓解 Docker Hub 上开源容器镜像的删除。
设置作业工件的默认过期时间
要在项目中设置作业工件的默认过期时间,请在 .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"
3401613export 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)
在 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-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类似的自动化 Shell 脚本在删除旧流水线部分中创建。
以下脚本示例使用 python-gitlab 库循环获取所有包,
并遍历其包文件以打印 file_name 和 size 属性。
packages = project.packages.list(order_by="created_at")
for package in packages:
package_files = package.package_files.list()
for package_file in package_files:
print("Package name: {p} File name: {f} Size {s}".format(
p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))删除软件包
在软件包中删除文件可能会损坏该软件包。在进行自动化清理维护时,你应该删除该软件包。
要删除软件包,请使用 GitLab CLI 将 --method 参数更改为 DELETE:
glab api --method DELETE projects/$GL_PROJECT_ID/packages/16669383若要计算软件包大小并与阈值比较,你可以使用 python-gitlab 库扩展 列出软件包及文件 部分描述的代码。
以下代码示例还会计算软件包的年龄,并在条件匹配时删除软件包:
packages = project.packages.list(order_by="created_at")
for package in packages:
package_size = 0.0
package_files = package.package_files.list()
for package_file in package_files:
print("软件包名称: {p} 文件名: {f} 大小 {s}".format(
p=package.name, f=package_file.file_name, s=render_size_mb(package_file.size)))
package_size =+ package_file.size
print("软件包大小: {s}\n\n".format(s=render_size_mb(package_size)))
threshold_size = 10 * 1024 * 1024
if (package_size > float(threshold_size)):
print("软件包大小 {s} > 阈值 {t}, 正在删除软件包。".format(
s=render_size_mb(package_size), t=render_size_mb(threshold_size)))
package.delete()
threshold_age = 90 * 24 * 60 * 60
package_age = created_at = calculate_age(package.created_at)
if (float(package_age > float(threshold_age))):
print("软件包年龄 {a} > 阈值 {t}, 正在删除软件包。".format(
a=render_age_time(package_age), t=render_age_time(threshold_age)))
package.delete()该代码会生成如下输出,你可将其用于进一步分析:
软件包名称: generator 文件名: nighly.tar.gz 大小 10.0017
软件包大小: 10.0017
软件包大小 10.0017 > 阈值 10.0000, 正在删除软件包。
软件包名称: generator 文件名: 1-nightly.tar.gz 大小 1.0004
软件包大小: 1.0004
软件包名称: generator 文件名: 10-nightly.tar.gz 大小 10.0018
软件包名称: generator 文件名: 20-nightly.tar.gz 大小 20.0033
软件包大小: 20.0033
软件包大小 20.0033 > 阈值 10.0000, 正在删除软件包。依赖代理
查看cleanup policy 以及如何通过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 流水线以每日生成工件。
-
创建一个新项目。
-
将以下片段添加到
.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,其中包含一个 示例组。
带有过期时间的作业工件生成
项目的 CI/CD 配置指定了作业定义的位置:
- 主
.gitlab-ci.yml配置文件中。 artifacts:expire_in设置中。- 项目文件和模板中。
为了测试分析脚本,gen-job-artifacts-expiry-included-jobs 项目提供了一个示例配置。
# .gitlab-ci.yml
include:
- include_jobs.yml
default:
artifacts:
paths:
- '*.txt'
.gen-tmpl:
script:
- dd if=/dev/urandom of=${$MB_COUNT}.txt bs=1048576 count=${$MB_COUNT}
generator:
extends: [.gen-tmpl]
parallel:
matrix:
- MB_COUNT: [1, 5, 10, 20, 50]
artifacts:
untracked: false
when: on_success
expire_in: 30 days
# include_jobs.yml
.includeme:
script:
- dd if=/dev/urandom of=1.txt bs=1048576 count=1
included-job10:
script:
- echo "Servus"
- !reference [.includeme, script]
artifacts:
untracked: false
when: on_success
expire_in: 10 days
included-job1:
script:
- echo "Gruezi"
- !reference [.includeme, script]
artifacts:
untracked: false
when: on_success
expire_in: 1 days
included-job30:
script:
- echo "Grias di"
- !reference [.includeme, script]
artifacts:
untracked: false
when: on_success
expire_in: 30 days生成容器镜像
示例组 container-package-gen-group 提供的项目可以:
- 使用 Dockerfile 中的基础镜像构建新镜像。
- 包含
Docker.gitlab-ci.yml模板,以便在 GitLab.com SaaS 上构建镜像。 - 配置流水线调度以每日生成新镜像。
可分叉的示例项目:
生成通用包
示例项目 generic-package-generator 提供的项目可以:
- 生成随机文本 blob,并使用当前 Unix 时间戳作为发布版本创建 tarball。
- 使用 Unix 时间戳作为发布版本,将 tarball 上传到通用包注册表。
要生成通用包,可以使用此独立的 .gitlab-ci.yml 配置:
generate-package:
parallel:
matrix:
- MB_COUNT: [1, 5, 10, 20]
before_script:
- apt update && apt -y install curl
script:
- dd if=/dev/urandom of="${MB_COUNT}.txt" bs=1048576 count=${MB_COUNT}
- tar czf "generated-$MB_COUNT-nighly-`date +%s`.tar.gz" "${MB_COUNT}.txt"
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "generated-$MB_COUNT-nighly-`date +%s`.tar.gz" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/generator/`date +%s`/${MB_COUNT}-nightly.tar.gz"'
artifacts:
paths:
- '*.tar.gz'使用分支测试存储用量
使用以下项目测试带有 分支成本因素 的存储用量:
- 将
gitlab-org/gitlab分支到一个新的命名空间或组(包含 LFS 和 Git 仓库)。 - 将
gitlab-com/www-gitlab-com分支到一个新的命名空间或组。
社区资源
以下资源未得到官方支持。在运行可能无法恢复的破坏性清理命令之前,请务必测试脚本和教程。
- 论坛主题:存储管理自动化资源
- 脚本:GitLab 存储分析器,由 GitLab 开发者布道团队 创建的非官方项目。你可以在本篇文档的“操作指南”中找到类似的代码示例。