一、问题背景:清理策略执行后空间为什么还是满的

很多开发者在使用腾讯云容器镜像服务(TCR)时都会遇到一个让人困惑的问题:明明已经设置了镜像清理策略,系统提示策略已经触发执行,但回头一看,存储用量还是纹丝不动,甚至继续往上涨。这就像你给电脑设置了自动清理垃圾文件的功能,按钮也按了,提示也弹了,但磁盘空间照样满。

这个问题出现的原因并不单一,往往涉及多个配置层面的叠加。有些人认为是系统bug,有些人觉得是策略没生效,但真正的原因大多藏在GC机制(垃圾回收)与不可变标签(Immutable Tag)的配置细节里。这两个环节看似简单,实际上有不少容易被忽视的坑。

下面我们就一步步来拆解这个问题,看看清理策略为什么"执行了却没用",以及那些藏在细节里的陷阱。

二、理解TCR清理策略的基本运作逻辑

2.1 清理策略的工作流程

TCR的清理策略其实就是一条规则链:你先定义什么情况下要删镜像,然后系统会按照你定义的规则去扫描、匹配、标记、删除。这个过程并不是实时的,它会按照你设置的执行频率来跑。

举个通俗的例子,清理策略就像一个清洁工排班表。你告诉清洁工"每天早上8点来清理",清洁工就每天8点来。但清洁工来了之后具体怎么清理,取决于你给他的任务清单。任务清单写得不清楚,或者某些地方有特殊保护,清洁工就会跳过。

清理策略的核心配置项主要包括触发频率、保留规则、匹配模式。触发频率决定了多久扫一次,保留规则决定了哪些镜像要留下哪些要删,匹配模式决定了标签的识别方式。

2.2 策略触发后的执行链路

策略触发后,系统会经历以下几个步骤:

第一步是扫描仓库中的所有镜像标签和镜像版本,第二步是将扫描结果与你的保留规则进行比对,第三步是对不符合保留条件的镜像进行删除标记,第四步是执行实际的删除操作释放存储空间。

这个链路中,最容易出问题的环节在第二步和第三步。因为比对规则和标记删除之间,有一层非常重要的机制在起作用,那就是GC机制。

三、GC机制:被忽视的核心环节

3.1 GC机制到底是什么

GC机制的全称是Garbage Collection,也就是垃圾回收机制。在TCR的语境下,GC负责的是镜像层(Image Layer)的回收,而不是整个镜像(Image)的删除。

这里有个关键的概念需要区分清楚:镜像和镜像层不是一回事。一个镜像由多个层组成,不同镜像之间可能会共享同一个镜像层。当你删除一个镜像时,系统只是删除了这个镜像的引用关系,但底层的那些层数据可能还在,因为它们可能还被其他镜像使用着。

这就好比你删掉了一份文档,但构成这份文档的素材文件(比如图片、模板)可能还在文件夹里,因为别的文档也在用。只有当所有引用某个层的镜像都被删掉了,GC才会去回收那个层的数据。

3.2 GC机制的触发条件

GC不会在每次删除镜像后立即执行,它有自己的触发条件。常见的触发方式有两种:

第一种是手动触发。你可以通过控制台或者API主动去触发一次GC扫描。

第二种是策略性触发。当清理策略执行完毕、删除了一批镜像之后,系统会根据GC策略来决定是否启动一轮垃圾回收。

如果在清理策略的配置中,没有正确启用或关联GC策略,就会出现这样的场景:策略把不需要的镜像都标记删除了,但底层的层数据没有被回收,存储空间的数字因此没有下降。

3.3 GC与清理策略的配合关系

清理策略负责的是"决定删哪些镜像",GC负责的是"回收被删镜像占用的底层数据"。两者是上下游的关系,缺一不可。

想象一下这样的场景:你有一间仓库,里面堆满了纸箱。清理策略相当于你告诉管理员"把过期的纸箱搬出去",管理员确实把纸箱都搬出去了。但这些纸箱里的东西(底层数据层)可能还没来得及处理,如果GC没有触发,那些东西还占着仓库的空间。

技术优缺点分析:GC机制的优点在于它避免了镜像层数据的重复存储,当多个镜像共享同一层时,系统只需要存一份,省空间。缺点就在于,镜像删除和层回收之间有时间差,如果GC配置不当,就会出现"删了镜像但空间没释放"的现象。

3.4 检查GC配置的实际示例

技术栈:Shell + JSON(通过TCR API与配置)

首先,我们可以通过控制台或API来检查仓库的GC策略是否正确配置。以下是一个通过命令行查看GC策略的完整示例:


# 首先确认当前登录的云账号和地域是否正确
# 使用 tencentcloud-cli 查看当前配置
tencentcloud-cli configure list

# 查看指定仓库空间的镜像仓库列表
# --region 参数指定地域,这里以广州区域为例
# --registry-name 参数指定仓库空间名称
tencentcloud-cli tcr DescribeRepositories \
  --region ap-guangzhou \
  --registry-name "my-registry-space"

# 查看某个具体仓库的GC策略配置
# --repository-name 参数指定仓库名称
# --repository-namespace 参数指定命名空间
tencentcloud-cli tcr GetRepositoryGCPolicy \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image"

# 如果返回为空或者无策略,说明该仓库没有配置GC策略
# 这就是清理后空间不释放的一个常见原因

# 也可以查询所有的清理策略列表
tencentcloud-cli tcr DescribeRepoPolicies \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default"

接下来,如果确认GC策略未配置,可以通过以下JSON格式的策略文件来手动创建一个GC策略。以下是一个完整的GC策略配置示例:


{
  "PolicyName": "gc-policy-daily",
  "PolicyDescription": "每日执行镜像层垃圾回收策略",
  "TriggerType": "SCHEDULE",
  "TriggerConfig": {
    "Period": "DAILY",
    "CronExpr": "0 8 * * ?"
  },
  "PolicyConfig": {
    "Enabled": true,
    "DeleteUntaggedLayers": true,
    "DeleteOrphanLayers": true,
    "RetentionDays": 30
  }
}

# 使用上述策略配置创建GC策略
# --repo-gc-policy 参数传入策略JSON内容(需URL编码或直接传文件)
tencentcloud-cli tcr CreateRepositoryGCPolicy \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image" \
  --repo-gc-policy "$(cat gc-policy.json)"

# 创建完成后,手动触发一次GC执行来验证
tencentcloud-cli tcr RunRepositoryGC \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image"

# 查看GC执行日志,确认回收了多少层数据
tencentcloud-cli tcr DescribeRepoGCLogs \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image"

这些命令和配置帮助你确认GC策略是否已经正确启用并与仓库关联。如果仓库层面没有绑定GC策略,清理策略删除镜像后,底层的层数据就不会被自动回收,存储空间自然不会减少。

四、不可变标签配置:另一个隐藏的坑

4.1 什么是不可变标签

不可变标签(Immutable Tag)是一个保护机制,开启后,仓库中某个指定标签的镜像版本不允许被覆盖。也就是说,如果仓库里已经有一个叫"v1.0"的镜像,你再推送一个也叫"v1.0"的镜像就会被拒绝。

这个机制的设计初衷是保证镜像的可追溯性和安全性,避免有人或某个流程意外覆盖了正在被使用的镜像版本。对于生产环境来说,这是一个非常好的保护。

但问题在于,当你开启了不可变标签之后,某些标签下的镜像版本会永远无法被自动清理。因为清理策略在匹配到这些标签时,会因为不可变保护而无法执行删除操作。

4.2 不可变标签与清理策略的冲突

举个例子:你配置了一条清理规则,规则是"保留最近10个版本,超出部分删除"。你的标签匹配模式设的是"v*",也就是所有以v开头的标签。

但你的仓库中,v1.0、v1.1、v2.0 等标签都被设置了不可变保护。当清理策略扫描到这些标签时,它会发现这些标签的镜像无法被覆盖或删除,于是跳过。最终的结果是,清理策略确实执行了,但所有关键版本都被保护住了,没有任何镜像被真正删除。

这就是为什么"策略触发了,空间没释放"的另一个重要原因。

4.3 检查不可变标签配置的示例

技术栈:Shell(通过TCR API检查与操作不可变标签配置)

以下示例展示了如何检查仓库中哪些标签被设置了不可变保护,以及如何调整配置:


# 查看仓库的基本配置信息,包括是否开启了不可变标签保护
# 这一步可以先确认仓库层面的默认设置
tencentcloud-cli tcr GetRepository \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image"

# 查看仓库下所有标签列表,确认当前存在的标签
# --page-size 参数控制每次返回的记录数
tencentcloud-cli tcr ListImageTags \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image" \
  --page-size 50

# 查看仓库的标签管理策略(TagPolicy),
# 这里会返回哪些标签模式被配置了不可变保护
tencentcloud-cli tcr GetRepoTagPolicy \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image"

如果上述命令返回了包含不可变标签规则的内容,说明仓库中存在受保护的标签。以下是一个典型的不可变标签策略配置,格式为JSON:


{
  "PolicyType": "TAG_PROTECTION",
  "PolicyConfig": {
    "Enabled": true,
    "RuleList": [
      {
        "MatchPattern": "release-*",
        "Description": "保护所有release开头的标签,不允许覆盖"
      },
      {
        "MatchPattern": "v[0-9]+\\.[0-9]+\\.[0-9]+",
        "Description": "保护所有语义化版本标签,如v1.0.0"
      },
      {
        "MatchPattern": "stable",
        "Description": "保护stable标签,确保稳定版不可变"
      }
    ]
  }
}

# 如果确认不可变标签保护导致了清理策略无法删除镜像
# 你可以通过修改TagPolicy来调整保护范围
# 例如:将过于宽泛的保护规则缩小范围

# 修改标签保护策略,放宽某些标签的限制
tencentcloud-cli tcr UpdateRepoTagPolicy \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image" \
  --tag-policy "$(cat new-tag-policy.json)"

# 修改完成后,重新执行清理策略来验证效果
tencentcloud-cli tcr RunRepoPolicy \
  --region ap-guangzhou \
  --registry-name "my-registry-space" \
  --repository-namespace "default" \
  --repository-name "my-app-image" \
  --policy-type "CLEANUP"

4.4 不可变标签的最佳实践

不可变标签不是"不能用",而是要用对地方。建议只对真正需要保护的发布版本开启不可变保护,比如正式发版的版本号标签。对于临时构建产生的标签(比如按时间戳或commit hash命名的标签),不需要开启不可变保护,这样清理策略就可以正常发挥作用。

一个实用的做法是:将标签分为两类管理。一类是"发布标签",格式规范(如v1.2.3),开启不可变保护,这些标签由发布流程严格控制。另一类是"临时标签",格式宽松(如build-20240101-abc123),不设置保护,允许被清理策略自动回收。

五、常见应用场景与问题分析

5.1 场景一:CI/CD流水线产生的大量临时镜像

在日常的开发流程中,CI/CD流水线会频繁地向镜像仓库推送镜像。每次代码提交都会构建一个新的镜像版本,如果标签命名不当(比如每次都推"latest"),或者流水线没有主动清理历史版本,仓库中很快就会堆积大量无用镜像。

这种场景下,如果你配置了清理策略但GC没启用,或者临时标签被不可变保护覆盖,就会出现镜像堆积、空间满的问题。

解决思路是:首先确认临时标签没有被不可变保护规则匹配到,其次确保仓库绑定了GC策略,最后验证清理策略的匹配模式能覆盖这些临时标签。

5.2 场景二:按时间清理但保留规则过于保守

有些团队配置的清理策略是"保留最近90天的镜像"。这个规则本身没问题,但如果仓库中的镜像层非常多,而且不同镜像之间共享了大量基础层,GC的回收效率就会比较低。

在这种情况下,即使清理策略删除了旧的镜像,那些被多个镜像共享的层数据可能因为还有其他引用而暂时无法回收。只有当GC执行且确认某个层没有任何引用时,才会真正释放空间。

5.3 场景三:跨区域或多仓库空间的管理盲区

如果团队在多个地域或多个仓库空间中部署了镜像仓库,很容易出现配置不一致的情况。某个仓库空间配置了GC策略,另一个却没有;某个仓库设置了不可变标签,另一个没有。这种不一致性会导致全局视角下某些仓库的空间始终无法被有效清理。

技术优缺点对比:使用自动化清理策略和GC机制的优点是减少了人工干预,让存储空间管理变得可预期。缺点是配置复杂度高,一旦某个环节遗漏就会出现"清理无效"的假象,排查起来比较耗时。

5.4 注意事项汇总

在配置和管理TCR的清理策略和GC机制时,需要特别注意以下几点。第一,清理策略和GC策略是两个独立的功能模块,必须分别配置并确认相互关联。第二,不可变标签保护规则的匹配模式写得过于宽泛时,会意外保护大量不需要保护的标签。第三,策略的执行频率不要设置得太低,如果仓库使用频率很高,一周才执行一次清理可能会来不及。第四,建议定期查看仓库的存储用量趋势和GC执行日志,及时发现异常。第五,在修改不可变标签策略时,先做好备份,避免误操作导致已发布版本被意外覆盖或删除。

六、文章总结

回到最初的问题:为什么TCR清理策略触发后镜像仍然占满存储空间?核心答案有两点。第一,GC机制没有被正确启用或没有在清理策略执行后及时触发,导致已删除镜像的底层层数据没有被回收。第二,不可变标签配置中的匹配规则过于宽泛,导致大量本应被清理的标签被保护住,清理策略无法对其执行删除操作。

这两个问题单独出现时就已经够让人头疼了,叠加在一起就更难排查。解决问题的关键在于养成一个完整的检查习惯:先确认清理策略是否真的生效,再确认GC策略是否已启用并执行,最后检查不可变标签配置是否意外拦截了清理范围。按照这个顺序逐一排查,绝大多数"清理无效"的问题都能找到根源。