k8s-垃圾回收

Garbage Collector 即垃圾回收,通常简称 GC,在 Kubelet 中非常重要,它不仅可以清理无用的容器,还可以清理未使用的镜像以达到节省空间的目的。(清理的这些容器都是 Kubernetes 自己创建的容器)

主要分为镜像回收 和容器回收

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@k8s01 system]# find / |grep kubeadm
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/from_repo
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/reason
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/releasever
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/var_uuid
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/var_contentdir
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/var_infra
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/command_line
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/checksum_type
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/checksum_data
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/origin_url
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/from_repo_revision
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/from_repo_timestamp
/var/lib/yum/yumdb/k/a447ee9308ee530f64582f36692a50045a987853-kubeadm-1.18.6-0-x86_64/installed_by
/var/lib/kubelet/kubeadm-flags.env
/usr/bin/kubeadm
/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf # 配置文件
[root@k8s01 system]# cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf # 默认配置
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/sysconfig/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS ## 在这个后面附加参数

# 生效
# systemctl daemon-reload
# systemctl restart kubelet

镜像回收

镜像回收

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
2
3
4
5
6
7
8
[{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"name": "nginx-6799fc88d8",
"uid": "a1b2c3...",
"controller": true,
"blockOwnerDeletion": true
}]

于是 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
2
kubectl delete deployment nginx --cascade=foreground
kubectl delete replicaset nginx-6799 --cascade=orphan

finalizer:删除为什么会卡住

metadata.finalizers 是一个字符串列表。只要它非空,对象就删不掉——API Server 收到删除请求后只会写上 deletionTimestamp,然后等:等某个控制器看到这个时间戳、做完自己的清理、再把自己那条 finalizer 从列表里摘掉。列表空了,对象才真正从 etcd 消失。

这就是”namespace 卡在 Terminating”的全部原因:

1
2
3
4
kubectl get ns rook-ceph -o jsonpath='{.spec.finalizers}{"\n"}{.status.conditions}'
# 找出到底还有哪些资源没清掉
kubectl api-resources --verbs=list --namespaced -o name \
| xargs -n1 kubectl get -n rook-ceph --show-kind --ignore-not-found

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 的候选。