垃圾收集
垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。垃圾收集允许系统清理如下资源:
| 序号 | 可被垃圾收集的资源 |
|---|---|
| 1 | 终止的 Pod |
| 2 | 已完成的 Job(配合 TTL 控制器,ttlSecondsAfterFinished) |
| 3 | 不再存在属主引用的对象 |
| 4 | 未使用的容器和容器镜像 |
| 5 | 动态制备的、StorageClass 回收策略为 Delete 的 PV 卷 |
| 6 | 阻滞或者过期的 CertificateSigningRequest(CSR) |
| 7 | 删除了的节点对象(当集群使用云控制器管理器运行于云端时;或使用类似于云控制器管理器的插件运行在本地环境中时) |
| 8 | 节点租约对象(Node Lease,用于节点心跳) |
官方文档参考
| 项目 | 链接 |
|---|---|
| 垃圾收集(官方中文文档) | https://kubernetes.io/zh-cn/docs/concepts/architecture/garbage-collection/ |
属主与依赖
Kubernetes 中很多对象通过属主引用(Owner Reference) 链接到彼此。属主引用可以告诉控制面哪些对象依赖于其他对象。Kubernetes 使用属主引用来为控制面以及其他 API 客户端在删除某对象时提供一个清理关联资源的机会。在大多数场合,Kubernetes 都是自动管理属主引用的。
例如:
- Deployment 创建 ReplicaSet 时,会自动在 ReplicaSet 上写入指向 Deployment 的属主引用;
- ReplicaSet 创建 Pod 时,会自动在 Pod 上写入指向 ReplicaSet 的属主引用。
属主引用与标签选择算符的区别
属主关系与某些资源所使用的标签和选择算符不同,两者解决的问题并不一样。以 Service 与 EndpointSlice 的关系为例:
| 对比项 | 标签 / 选择算符 | 属主引用 |
|---|---|---|
| 关联方式 | Service 使用标签让控制面确定哪些 EndpointSlice 对象被它使用 | 每个被 Service 托管的 EndpointSlice 还有一个属主引用属性 |
| 语义 | "按条 件匹配的一组对象",条件宽松、可被多方匹配 | "这个对象归属于谁",表示严格的从属关系 |
| 用途 | 服务发现、筛选、批量操作 | 删除属主时级联清理依赖;帮助 Kubernetes 中的不同组件避免干预并非由它们控制的对象 |
也就是说:标签是"多对多的弱关联",属主引用是"一对一的强归属"。垃圾收集机制只认属主引用。
跨名字空间属主引用的限制
根据设计,系统不允许出现跨名字空间的属主引用:
- 名字空间作用域的依赖对象可以指定集群作用域或者名字空间作用域的属主,但名字空间作用域的属主必须存在于依赖对象所在的同一名字空间;
- 如果属主位于不同名字空间,则属主引用被视为不存在,而当检查发现所有属主都已不存在时,依赖对象会被删除;
- 集群作用域的依赖对象只能指定集群作用域的属主。在 v1.20 及更高版本中,如果一个集群作用域的依赖对象指定了某个名字空间作用域的类别作为其属主,则该对象被视为拥有一个无法解析的属主引用,因而无法被垃圾收集处理。
在 v1.20 及更高版本中,如果垃圾收集器检测到非法的跨名字空间 ownerReference,或者某集群作用域的依赖对象的 ownerReference 引用某名字空间作用域的类别,系统会生成一个警告事件,其原因为 OwnerRefInvalidNamespace,involvedObject 设置为非法的依赖对象。可以用下面的命令检查是否存在这类事件:
# 检查全集群范围内的非法属主引用警告事件
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
级联删除
Kubernetes 会检查并删除那些不再拥有属主引用的对象,例如在你删除了 ReplicaSet 之后留下来的 Pod。当你 删除某个对象时,你可以控制 Kubernetes 是否去自动删除该对象的依赖对象,这个过程称为级联删除(Cascading Deletion)。
级联删除有两种类型:
| 类型 | 属主对象的行为 | 依赖对象的清理方式 |
|---|---|---|
| 前台级联删除(foreground) | 先进入"删除进行中"状态,等所有已知依赖删除完毕后才真正消失 | 由控制器在前台逐个删除,删除期间属主对象仍可见 |
| 后台级联删除(background,默认) | API 服务器立即删除属主对象 | 垃圾收集控制器在后台异步清理依赖对象 |
你也可以使用 Kubernetes Finalizers 来控制垃圾收集机制如何以及何时删除包含属主引用的资源。
下面的示意图汇总了属主引用与三种删除策略(前台、后台、遗弃)的关系:

前台级联删除
在前台级联删除中,正在被你删除的属主对象首先进入 deletion in progress(删除进行中) 状态。在这种状态下,针对属主对象会发生以下事情:
- Kubernetes API 服务器将某对象的
metadata.deletionTimestamp字段设置为该对象被标记为要删除的时间点; - Kubernetes API 服务器也会将
metadata.finalizers字段设置为foregroundDeletion; - 在删除过程完成之前,通过 Kubernetes API 仍然可以看到该对象。
当属主对象进入"删除进行中"状态后,控制器会删除其已知的依赖对象。在删除所有已知的依赖对象后,控制器才会删除属主对象,这时通过 Kubernetes API 就无法再看到该对象了。
前台级联删除过程中,唯一会阻止属主对象被删除的是那些带有 ownerReference.blockOwnerDeletion=true 字段、并且存在于垃圾收集控制器缓存中的依赖对象。需要注意的是,垃圾收集控制器缓存中可能不包含那些无法成功被列举/监视的资源类型的对象,或在属主对象删除的同时创建的对象。
下面的动图演示了前台级联删除的执行流程:

后台级联删除
在后台级联删除过程中,Kubernetes API 服务器立即删除属主对象,而垃圾收集控制器(无论是自定义的还是默认的)在后台清理所有依赖对象。如果存在 Finalizers,它会确保所有必要的清理任务完成后对象才被删除。
默认情况下,Kubernetes 使用后台级联删除,除非你手动设置了要使用前台删除,或者选择遗弃依赖对象。
被遗弃的依赖对象(孤儿)
当 Kubernetes 删除某个属主对象时,被留下来的依赖对象被称作**被遗弃的(Orphaned)**对象,也常被叫作"孤儿对象"。
默认情况下,Kubernetes 会删除依赖对象。要改变这种默认行为(即删除属主、保留依赖),可以在删除时指定孤儿删除策略:
# 删除属主对象,同时遗弃(保留)其依赖对象
kubectl delete replicaset my-rs --cascade=orphan
被遗弃的对象不再有属主引用,此后它的生命周期与原属主完全解耦,需要你自行管理。
未使用容器和镜像的垃圾收集
kubelet 对节点本地的容器与镜像执行周期性垃圾收集:
| 对象 | 垃圾收集周期 |
|---|---|
| 未使用的容器镜像 | 每 5 分钟一次 |
| 未使用的容器 | 每 1 分钟一次 |
你应该避免使用外部的垃圾收集工具(如节点上的 cron 脚本、第三方清理器直接删镜像/容器),因为外部工具可能会破坏 kubelet 的行为,移除应该保留的容器。镜像与容器的清理应当完全交给 kubelet 及其 KubeletConfiguration 配置来管理。
要配置对未使用容器和镜像的垃圾收集选项,可以使用一个配置文件,基于 KubeletConfiguration 资源类型来调整与垃圾收集相关的 kubelet 行为。
容器镜像生命周期
Kubernetes 通过其镜像管理器(Image Manager) 来管理所有镜像的生命周期,该管理器是 kubelet 的一部分,工作时与 cAdvisor 协同。
kubelet 在作出镜像垃圾收集决定时会考虑如下磁盘用量约束:
| 参数 | 含义 |
|---|---|
HighThresholdPercent | 磁盘用量百分比上限阈值;超出该值时触发垃圾收集 |
LowThresholdPercent | 磁盘用量百分比下限阈值;垃圾收集持续进行直到磁盘用量回落到该值为止 |
工作机制:
- 磁盘用量超出所配置的
HighThresholdPercent值时触发垃圾收集; - 垃圾收集器会基于镜像上次被使用的时间来按顺序删除它们,首先删除的是最近未使用的镜像;
- kubelet 会持续删除镜像,直到磁盘用量到达
LowThresholdPercent值为止。
未使用容器镜像的垃圾收集(按年龄)
除了按磁盘水位触发,你还可以指定本地镜像未被使用的最长时间——不论磁盘使用情况如何。这是一个可以为每个节点配置的 kubelet 设置:在 kubelet 配置文件中为 imageMaximumGCAge 字段设置一个值。
该值应遵循 Kubernetes 持续时间(Duration)格式,例如 12h45m 代表 12 小时 45 分钟:
# KubeletConfiguration 片段:镜像垃圾收集相关配置
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 不论磁盘用量如何,未被使用超过 12h45m 的本地镜像都可以被回收
imageMaximumGCAge: 12h45m
# 磁盘用量超过 85% 时触发镜像 GC,回收至 80% 以下停止
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
这个特性不会跟踪 kubelet 重新启动后的镜像使用情况。如果 kubelet 被重新启动,所跟踪的镜像年龄会被重置,导致 kubelet 在根据镜像年龄进行垃圾收集时需要等待完整的 imageMaximumGCAge 时长。
容器垃圾收集
kubelet 会基于如下变量对所有未使用的容器执行垃圾收集操作,这些变量都是你可以定义的:
| 变量 | 含义 | 禁用方式 |
|---|---|---|
MinAge | kubelet 可以垃圾回收某个容器时该容器的最小年龄 | 设置为 0 表示禁止使用此规则 |
MaxPerPodContainer | 每个 Pod 可以包含的已死亡容器个数上限 | 设置为小于 0 的值表示禁止使用此规则 |
MaxContainers | 集群中可以存在的已死亡容器总个数上限 | 设置为小于 0 的值表示禁止应用此规则 |
除以上变量之外,kubelet 还会垃圾收集无标识的以及已删除的容器,通常从最长时间未使用的容器开始。
MaxPerPodContainer 与 MaxContainers 的冲突处理
当保持每个 Pod 的最大容器数量(MaxPerPodContainer)会使得全局的已死亡容器个数超出上限(MaxContainers)时,两者之间可能出现冲突。在这种情况下,kubelet 会调整 MaxPerPodContainer 来解决冲突——最坏的情形是将 MaxPerPodContainer 降格为 1,并驱逐最近未使用的容器。
此外,当隶属于某已被删除的 Pod 的容器的年龄超过 MinAge 时,它们也会被删除。
kubelet 仅会回收由它所管理的容器。绕过 kubelet(例如直接用 docker/crictl 手工起容器)产生的容器不在 kubelet 的容器垃圾收集范围内,会一直堆积到手工清理为止。
配置垃圾收集
你可以通过配置特定于管理资源的控制器来调整资源的垃圾收集行为,官方提供了两个主要入口:
| 入口 | 说明 |
|---|---|
| 配置 Kubernetes 对象的级联删除 | 控制删除属主对象时依赖对象的清理方式(前台 / 后台 / 遗弃) |
| 配置已完成 Job 的清理 | 通过 TTL 控制器(ttlSecondsAfterFinished)自动清理已完成的 Job |
我的总结与避坑清单
- 垃圾收集是"一组机制"而非单一组件:终止的 Pod、完成的 Job、无属主引用的对象、未用镜像/容器、Delete 回收策略的 PV、过期 CSR、云端被删的节点、节点 Lease 都各有对应的清理机制,排障时先分清是哪一类。
- 删除 Pod/资源前先看属主引用:直接删属主(如 Deployment、ReplicaSet)会级联删掉下游对象;只想"换掉管控方"时要用
--cascade=orphan遗弃依赖,否则可能整链被清空。 - 标签 ≠ 属主:Service 与 EndpointSlice 靠标签筛选、靠属主引用归属;不要指望给对象打上标签就能让它跟随某个属主被级联删除。
- 跨名字空间属主引用非法:属主引用会被视为不存在,等所有属主"消失"后依赖可能被意外删除;集群级对象更不能引用名字空间级属主。用
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace定期排查。 - 默认是后台级联删除:属主立即消失、依赖异步清理;需要"先清依赖再删属主"的强一致语义时,显式使用前台删除(
--cascade=foreground)。 - 前台删除期间属主仍可见:它带着
deletionTimestamp和foregroundDeletionfinalizer 处于"删除进行中"状态,脚本里不要误以为"还能查到就是没删成"。 blockOwnerDeletion=true会卡住前台删除:这类依赖删除不掉,属主就永远停在删除进行中;排查"删不掉的 Deployment"时优先检查此类引用和自定义 finalizer。- 镜像 GC 双通道:磁盘水位通道(High/LowThresholdPercent,超上限回收、落到下限停止,先删最久未用的镜像)+ 时间通道(
imageMaximumGCAge,不论磁盘水位)。 - kubelet 重启会重置镜像年龄:
imageMaximumGCAge的计时在 kubelet 重启后清零,频繁重启 kubelet 会让按年龄回收长期不生效,别用它做"强时效"清理。 - 容器 GC 三参数要理解冲突处理:
MinAge(最小年龄)、MaxPerPodContainer(单 Pod 死容器上限)、MaxContainers(全局死容器上限);两者冲突时 kubelet 会压缩MaxPerPodContainer,最坏降到 1。 - 不要装外部清理工具:外部 GC 可能删掉 kubelet 要保留的容器/镜像,破坏节点行为;清理需求一律通过
KubeletConfiguration表达。 - kubelet 只回收它管理的容器:手工在节点上 docker/crictl 起的容器不会被 kubelet GC,堆积问题要自行负责。

