第 7/18 天
引言
在前面六天的实战中,我们完成了全链路架构设计、K8s 集群部署、Harbor 私有镜像仓库搭建、GitLab CE 自托管部署、GitLab Runner 配置以及 GitLab CI 流水线搭建。流水线已经能通过 Kaniko 将构建好的容器镜像推送到 Harbor 仓库。然而,随着团队规模扩大和发布频率提升,一个关键问题浮出水面:镜像 Tag 该怎么管?
如果每次构建都用 latest,线上回滚时无从追溯;如果每个开发分支都随意打 Tag,仓库很快会变成垃圾场。今天我们将深入 Harbor 镜像版本管理,设计一套科学的 Tag 策略,配置 Harbor 的 Tag 保留规则(Tag Retention)与垃圾回收机制,确保镜像仓库在长期运行中保持整洁、可追溯、可回滚。

核心概念:镜像 Tag 的本质与挑战
Docker 镜像 Tag 不是版本号
很多团队误把 Docker Tag 当作语义化版本号(SemVer)使用,但实际上 Tag 只是一个指向镜像 manifest 的可变标签。同一个 Tag(如 v1.2.0)可以被覆盖推送多次,底层 digest(SHA256)才是镜像的真正唯一标识。
# 查看镜像的 digest(不可变唯一标识)
docker inspect –format='{{index .RepoDigests 0}}' harbor.stellardata.top/app/frontend:v1.2.0
# 输出示例: harbor.stellardata.top/app/frontend@sha256:a1b2c3d4e5…
# 同一个 Tag 推送两次后 digest 不同
docker pull harbor.stellardata.top/app/frontend:v1.2.0
docker image inspect –format='{{.Id}}' harbor.stellardata.top/app/frontend:v1.2.0
# 每次重新构建后 .Id 会变化
常见 Tag 策略对比
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| latest | app:latest |
简单 | 不可追溯,无法回滚 |
| 语义版本 | app:1.2.0 |
语义清晰 | 需手动维护版本号 |
| Git Commit | app:a1b2c3d |
自动化,唯一 | 不可读 |
| 构建号 | app:build-42 |
自动递增 | 跨分支可能冲突 |
| 混合策略 | app:1.2.0-a1b2c3d |
兼具语义与唯一性 | Tag 较长 |
在 CI/CD 流水线中,最佳实践是采用混合策略:主版本号 + Git Commit 短哈希 + 构建时间戳,既保证可读性又保证唯一性,同时配合 Harbor 的不可变 Tag 功能防止覆盖。
实战步骤:Tag 策略设计与 Harbor 配置
第一步:设计生产级 Tag 命名规范
我们定义一套适用于 GitLab CI 的 Tag 生成方案。在 .gitlab-ci.yml 中动态生成镜像 Tag:
# .gitlab-ci.yml 片段 — 构建阶段的 Tag 生成逻辑
variables:
HARBOR_REGISTRY: "harbor.stellardata.top"
IMAGE_NAME: "app/frontend"
# 短 commit hash (前 8 位)
SHORT_SHA: "${CI_COMMIT_SHORT_SHA}"
# 构建日期 (用于时间排序)
BUILD_DATE: "${CI_PIPELINE_CREATED_AT}"
build_image:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.23.0-debug
entrypoint: [""]
script:
# 生成多维度 Tag: 语义版本-commit hash
– TAG_VERSION="${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}"
– /kaniko/executor
–context="${CI_PROJECT_DIR}"
–dockerfile="${CI_PROJECT_DIR}/Dockerfile"
–destination="${HARBOR_REGISTRY}/${IMAGE_NAME}:${TAG_VERSION}"
–destination="${HARBOR_REGISTRY}/${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}"
–destination="${HARBOR_REGISTRY}/${IMAGE_NAME}:latest"
–build-arg="BUILD_DATE=${BUILD_DATE}"
–build-arg="GIT_COMMIT=${CI_COMMIT_SHA}"
–cache=true
–cache-repo="${HARBOR_REGISTRY}/${IMAGE_NAME}:cache"
rules:
– if: $CI_COMMIT_BRANCH == "main"
variables:
TAG_VERSION: "prod-${CI_COMMIT_SHORT_SHA}"
– if: $CI_COMMIT_BRANCH == "develop"
variables:
TAG_VERSION: "dev-${CI_COMMIT_SHORT_SHA}"
上面的配置同时推送三个 Tag:完整的 分支-commit Tag 用于精确引用,纯 commit hash Tag 用于快速查找,latest 标记最新构建。这样无论是 Argo CD 做 GitOps 部署还是人工排查问题,都能快速定位到具体镜像。
第二步:在 Harbor 中启用 Tag 不可变(Immutable Tag)
Harbor 企业版(以及开源版 v2.4+)支持将特定 Tag 标记为不可变,防止构建流水线覆盖已发布的镜像版本。这在生产环境中至关重要。
# 通过 Harbor REST API 设置 Tag 不可变规则
# 规则:匹配 prod- 前缀的 Tag 设为不可变
curl -X POST
"https://harbor.stellardata.top/api/v2.0/projects/app/repositories/frontend/immutablerules"
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)"
-H "Content-Type: application/json"
-d '{
"tag_matching": "prod-*",
"repo_matching": "**",
"tag_excluding": "",
"repo_excluding": "",
"disabled": false
}'
# 验证不可变规则已生效
curl -s "https://harbor.stellardata.top/api/v2.0/projects/app/repositories/frontend/immutablerules"
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)" | python3 -m json.tool
设置后,任何尝试用相同 Tag 覆盖推送 prod-* 前缀镜像的操作都会被 Harbor 拒绝,返回 HTTP 409 Conflict。这从仓库层面杜绝了”Tag 漂移”问题。
第三步:配置 Tag 保留策略(Tag Retention)
随着持续构建,镜像数量会无限增长。如果不做清理,磁盘空间很快耗尽。Harbor 的 Tag Retention 功能可以按规则自动保留若干版本的 Tag,其余的自动删除。
#!/usr/bin/env python3
"""
Harbor Tag Retention 策略配置脚本
为指定项目创建保留规则:
– prod-* Tag: 保留最近 30 个
– dev-* Tag: 保留最近 10 个
– 其他: 保留最近 5 个
"""
import requests
import base64
import json
HARBOR_URL = "https://harbor.stellardata.top"
USERNAME = "admin"
PASSWORD = "Harbor12345"
PROJECT_ID = 3 # app 项目 ID
auth = base64.b64encode(f"{USERNAME}:{PASSWORD}".encode()).decode()
headers = {"Authorization": f"Basic {auth}", "Content-Type": "application/json"}
# 创建保留策略
retention_policy = {
"algorithm": "or",
"rules": [
{
"action": "retain",
"scope_selectors": {
"repositories": [
{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}
]
},
"tag_selectors": [
{"kind": "doublestar", "decoration": "matches", "pattern": "prod-*"}
],
"params": {"latest": 30}
},
{
"action": "retain",
"tag_selectors": [
{"kind": "doublestar", "decoration": "matches", "pattern": "dev-*"}
],
"params": {"latest": 10}
},
{
"action": "retain",
"tag_selectors": [
{"kind": "doublestar", "decoration": "matches", "pattern": "**"}
],
"params": {"latest": 5}
}
],
"trigger": {"kind": "Schedule", "settings": {"cron": "0 0 3 * * *"}, "references": []}
}
resp = requests.post(
f"{HARBOR_URL}/api/v2.0/retentions",
headers=headers,
json={"scope_selectors": {}, "rules": retention_policy["rules"],
"trigger": retention_policy["trigger"]}
)
print(f"Retention policy created: {resp.status_code}")
print(json.dumps(resp.json(), indent=2) if resp.status_code in (200, 201) else resp.text)
上面的 Python 脚本通过 Harbor v2.0 REST API 创建了三条保留规则:生产镜像保留 30 个版本、开发镜像保留 10 个版本、其余保留 5 个版本。定时任务每天凌晨 3 点执行清理。算法使用 or 逻辑,意味着只要满足任一规则 Tag 就会被保留,不会被误删。
第四步:配置垃圾回收(Garbage Collection)
Tag Retention 删除的只是 Tag 引用,底层的 Blob 数据并不会立即释放。需要配合 Harbor 的垃圾回收机制才能真正回收磁盘空间。
# 在 Harbor 部署目录执行垃圾回收(需先停止 Harbor)
cd /opt/harbor
docker-compose stop
# 执行垃圾回收(不带 –dry-run 为真实执行)
docker run -it –name gc –rm
-v /data/harbor/registry:/storage
-e "REGISTRY_REDIS_ADDR=redis:6379"
goharbor/registry-photon:v2.12.0 garbage-collect
–dry-run /etc/registry/config.yml
# 确认无误后执行真实回收
docker run -it –name gc –rm
-v /data/harbor/registry:/storage
-e "REGISTRY_REDIS_ADDR=redis:6379"
goharbor/registry-photon:v2.12.0 garbage-collect
/etc/registry/config.yml
# 重启 Harbor
docker-compose up -d
对于在线垃圾回收(Harbor v2.0+ 支持),可以在 Web 界面”配置管理 → 垃圾回收”中设置定时任务,无需停机:
# Harbor values.yaml 中的 GC 配置(Helm 部署方式)
harbor:
gc:
enabled: true
schedule: "0 0 4 * * *" # 每天凌晨 4 点执行
forceSecureGc: false # 不强制停止 Harbor
persistence:
persistentVolumeClaim:
registry:
size: 200Gi
storageClass: "nfs-client"
第五步:验证 Tag 策略与镜像溯源
部署完成后,我们需要验证整套 Tag 策略是否按预期工作。模拟一次完整构建并检查 Harbor 中的镜像版本记录:
# 1. 模拟 GitLab CI 构建推送(main 分支)
docker tag app/frontend:local harbor.stellardata.top/app/frontend:prod-a1b2c3d4
docker push harbor.stellardata.top/app/frontend:prod-a1b2c3d4
# 2. 查看 Harbor 仓库中的所有 Tag
curl -s "https://harbor.stellardata.top/api/v2.0/projects/app/repositories/frontend/artifacts?with_tag=true"
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)"
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for artifact in data:
digest = artifact['digest'][:19]
tags = [t['name'] for t in artifact.get('tags', [])]
push_time = artifact.get('push_time', 'N/A')
print(f' Digest: {digest}… Tags: {tags} Pushed: {push_time}')
"
# 3. 验证不可变 Tag 规则 — 尝试覆盖推送应被拒绝
docker tag nginx:latest harbor.stellardata.top/app/frontend:prod-a1b2c3d4
docker push harbor.stellardata.top/app/frontend:prod-a1b2c3d4
# 预期输出: error: tag prod-a1b2c3d4 is immutable
# 4. 查看保留策略执行日志
curl -s "https://harbor.stellardata.top/api/v2.0/retentions/1/tasks"
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)"
| python3 -m json.tool | head -50
通过以上验证,我们可以确认:每次构建都会生成包含 commit hash 的唯一 Tag,生产 Tag 不可被覆盖,超出保留数量的旧 Tag 会被自动清理,垃圾回收会定期释放磁盘空间。这构成了一个闭环的镜像版本管理体系。
常见问题
Q1:Tag 保留策略执行后磁盘空间没有释放?
Tag Retention 只删除 Tag 引用,不删除底层 Blob。必须执行垃圾回收才能释放空间。如果使用在线 GC,请确认 Harbor 版本 ≥ v2.0 且 gc.forceSecureGc 设置为 false。离线 GC 需要先停止 Harbor 再执行,否则可能导致数据不一致。
Q2:不可变 Tag 规则误拦截了正常推送?
检查 Tag 匹配模式。prod-* 会匹配所有以 prod- 开头的 Tag,如果 CI 流水线在开发阶段也使用该前缀就会被拦截。建议区分前缀:prod-*(生产)、staging-*(预发布)、dev-*(开发),并为每种环境设置不同的不可变规则和保留数量。
Q3:Argo CD GitOps 如何配合 Tag 策略做回滚?
Argo CD 通过追踪 Git 仓库中的镜像 Tag 来决定部署版本。当需要回滚时,只需在 Git 中将部署清单的镜像 Tag 改回之前的 commit hash Tag(如 prod-a1b2c3d4),Argo CD 会自动检测到变更并执行回滚。由于我们保留了 30 个生产版本,90 天内的任意版本都可以快速回滚。也可以配合 Argo CD Image Updater 自动选择最新通过扫描的镜像 Tag。
Q4:多架构镜像(multi-arch)的 Tag 如何管理?
使用 docker buildx 构建多架构镜像时,同一个 Tag 会指向一个 manifest list,其中包含不同架构(amd64/arm64)的子镜像。Harbor v2.0+ 完整支持 manifest list,保留策略和不可变规则同样适用于多架构镜像的 Tag。建议在 CI 中统一使用 buildx 构建,确保同一个 Tag 在所有架构下行为一致。
总结
今天我们围绕 Harbor 镜像版本管理这个核心主题,完成了一套生产可用的 Tag 策略设计:
- Tag 命名规范:采用
分支名-commit hash的混合策略,兼顾可读性与唯一性,在 GitLab CI 中自动生成 - 不可变 Tag:通过 Harbor REST API 将
prod-*前缀的 Tag 设为不可变,从仓库层面防止 Tag 覆盖 - Tag 保留策略:使用 Python 脚本通过 Harbor API 配置自动清理规则,生产保留 30 版、开发保留 10 版、其余保留 5 版
- 垃圾回收:配置在线/离线 GC 定时任务,真正释放磁盘空间
- 验证溯源:通过 REST API 查询镜像版本记录,验证不可变规则与保留策略的执行效果
这套方案让镜像仓库在持续高频构建的压力下依然保持整洁有序,每个镜像版本都可追溯、可回滚、可审计,为后续 Argo CD GitOps 部署提供了可靠的镜像版本基础。
下期预告
明天我们将进入 GitOps 的核心环节——第 8 天:Argo CD 部署与 GitOps 配置仓库创建。将安装 Argo CD 到 K8s 集群,创建 GitOps 配置仓库结构,打通从 Git 到集群的自动化部署通道。敬请期待!
系列大纲
- 全链路架构总览:从代码提交到灰度发布的完整管道设计
- K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件
- Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送
- GitLab CE 自托管部署:代码仓库与项目管理平台
- GitLab Runner 配置:K8s Executor 与 RBAC 权限设置
- GitLab CI 流水线搭建:.gitlab-ci.yml 与 Kaniko 无守护进程构建
- Harbor 镜像版本管理与 Tag 策略配置(今天)
- Argo CD 部署与 GitOps 配置仓库创建
- CI 与 CD 联动:从代码提交到部署的全自动闭环
- Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集
- Grafana 看板搭建:K8s 标准面板与自定义业务看板
- 告警规则与 Alertmanager:钉钉/邮件通知渠道配置
- Argo Rollouts 安装与基础概念:CRD 与 Rollout 资源定义
- 金丝雀发布实战:setWeight 流量分割与 pause 步骤
- AnalysisTemplate 指标分析与自动回滚机制
- 全链路端到端演示:代码提交→构建→灰度→监控→告警完整流程
- 生产化加固要点:Harbor/GitLab/Argo CD 高可用方案
- 供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签

















暂无评论内容