k8s-垃圾回收
Garbage Collector 即垃圾回收,通常简称 GC,在 Kubelet 中非常重要,它不仅可以清理无用的容器,还可以清理未使用的镜像以达到节省空间的目的。(清理的这些容器都是 Kubernetes 自己创建的容器)
主要分为镜像回收 和容器回收
配置文件
1 | [root@k8s01 system]# find / |grep kubeadm |
镜像回收

image-gc-high-threshold:磁盘使用率上限,有效范围 [0-100],默认 85
image-gc-low-threshold:磁盘使用率下限,有效范围 [0-100],默认 80
minimum-image-ttl-duration:镜像最短应该生存的年龄,默认 2 分钟
对镜像的 GC 操作,就是逐个删除最久最少使用(Least Recently Used)的镜像
容器回收

--minimum-container-ttl-duration 表示已停止的容器在被清理之前最小的存活时间,默认值是 1 分钟,即容器停止超过 1 分钟才会被标记可被 GC 清理;
--maximum-dead-containers-per-container 表示同一个容器(按容器名)最多保留几个已退出的旧实例,默认值是 2;节点上已退出容器的总量上限则由 --maximum-dead-containers 控制。有时候 Pod 内运行失败的容器,比如容器自身的问题,或者健康检查失败,会被 kubelet 自动重启,这将产生一些停止的容器;
--maximum-dead-containers 表示在本节点上可以保留的已停止容器的最大数量,默认值是240。毕竟这些容器也会消耗额外的磁盘空间,所以超过这个上限阈值后,就会触发 Kubelet 的 GC 操作,来帮你自动清理这些已停止的容器,释放磁盘空间。
关闭容器的 GC 操作,只需要将 --minimun-container-ttl-duration 设置为0,把 --maximum-dead-containers-per-container 和--maximum-dead-containers 都设置为负数即可。
在有些场景中,容器的日志需要保留在本地,如果直接清理掉这些容器会丢失日志。 强烈建议你将 --maximum-dead-containers-per-container 设置为一个足够大的值,以便每个容器至少有一个退出的实例
API 对象的垃圾回收
上面两节讲的都是 kubelet 在节点上回收镜像和容器。但日常遇到的”垃圾回收”问题,更多是 API 层的:kubectl delete deployment 为什么会把 Pod 一起带走?namespace 为什么卡在 Terminating 删不掉?这一层由 controller-manager 里的 garbage collector 负责,机制完全不同。
ownerReferences:对象之间的父子关系
每个被”别人创建出来”的对象,metadata 里都记着它的主人:
1 | kubectl get pod <pod> -o jsonpath='{.metadata.ownerReferences}' | jq |
1 | [{ |
于是 Deployment → ReplicaSet → Pod 形成一棵树。GC 的工作就是:当一个对象的所有 owner 都消失了,就把它删掉(靠 uid 匹配,所以同名重建的新 owner 不会认领旧的孩子——这也是”删了 Deployment 立刻重建同名的,旧 Pod 还是会被清掉”的原因)。
blockOwnerDeletion: true 的作用见下面的 Foreground 模式。
三种级联策略
kubectl delete 的 --cascade 和 API 的 propagationPolicy 对应三种行为:
| 策略 | 行为 | 何时用 |
|---|---|---|
| Background(默认) | owner 立即被删除,GC 在后台慢慢删孩子 | 日常删除。所以 kubectl delete deploy 一瞬间就返回,但 Pod 还要几秒才消失 |
| Foreground | owner 先进入 Terminating(挂上 foregroundDeletion finalizer),等所有 blockOwnerDeletion: true 的孩子删完,owner 才真正消失 |
需要确认”清理干净了”再继续的场景,比如脚本里删完立刻重建 |
| Orphan | 只删 owner,孩子留下并被摘掉 ownerReference | 想换一个控制器接管现有 Pod;或 kubectl delete rs --cascade=orphan 保留 Pod 排查问题 |
1 | kubectl delete deployment nginx --cascade=foreground |
finalizer:删除为什么会卡住
metadata.finalizers 是一个字符串列表。只要它非空,对象就删不掉——API Server 收到删除请求后只会写上 deletionTimestamp,然后等:等某个控制器看到这个时间戳、做完自己的清理、再把自己那条 finalizer 从列表里摘掉。列表空了,对象才真正从 etcd 消失。
这就是”namespace 卡在 Terminating”的全部原因:
1 | kubectl get ns rook-ceph -o jsonpath='{.spec.finalizers}{"\n"}{.status.conditions}' |
99% 的情况是某个 CRD 对象上挂着 finalizer,而负责摘除它的 operator 已经被先删掉了——没人来摘,就永远卡着。(本站 rook-ceph 那篇的卸载顺序问题正是这一类:先删 operator 再删 CephCluster,必然卡死。)
正确做法是把 operator 装回去让它自己摘干净。手工强删 finalizer 是最后手段,代价是资源在外部系统(磁盘、云盘、DNS 记录)里的残留没人清理了:
1 | kubectl patch cephcluster rook-ceph -n rook-ceph -p '{"metadata":{"finalizers":[]}}' --type=merge |
两层 GC 的对照
| kubelet GC(前两节) | API 对象 GC(本节) | |
|---|---|---|
| 执行者 | 每个节点的 kubelet | controller-manager 里的 garbage collector |
| 回收对象 | 镜像、已退出的容器 | etcd 里的 API 对象 |
| 触发条件 | 磁盘水位、数量上限、TTL | owner 消失 |
| 调参方式 | kubelet 启动参数 | propagationPolicy / finalizer |
| 典型故障 | 磁盘被镜像占满 | 卡在 Terminating |
两者唯一的交集是:Pod 对象在 API 层被 GC 删掉之后,节点上对应的容器才会成为 kubelet GC 的候选。