ownerReferences、Finalizers与garbage collector

ownerReferences

ownerReferences指明了资源的属主,即当前对象依赖于ownerReferences指向的对象。比如创建一个deployment,pod的属主为该pod对应的replicaSet(简称rs),同样这个rs也有一个属主引用,指向一个对应的deployment,以一个nginx-deployment为例,pod和rs的ownerReferences如下:

podownerReferences字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
metadata:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2022-03-29T08:06:34Z"
generateName: nginx-deployment-6c575444d8-
labels:
app: nginx
pod-template-hash: 6c575444d8
name: nginx-deployment-6c575444d8-5424w
namespace: test-cxz
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: nginx-deployment-6c575444d8
uid: 646cd157-df56-46d5-9432-d0b5f9557f5a
resourceVersion: "40445528"
uid: c904db91-5082-4ff8-8a01-a35a5206e5ea

rs的ownerReferences字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: ReplicaSet
metadata:
generation: 1
labels:
app: nginx
pod-template-hash: 6c575444d8
name: nginx-deployment-6c575444d8
namespace: test-cxz
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: Deployment
name: nginx-deployment
uid: 02a5d50a-8832-4a3e-9498-eb907b04a7a1
resourceVersion: "40452750"
uid: 646cd157-df56-46d5-9432-d0b5f9557f5a

可以看到pod和rs各自ownerReferences对应的属主对象,pod的属主对象是rs,rs的属主对象是deployment。ownerReferences包含了属主对象的一些属性,包括apiversion、kind、uid、name等通用属性,controller字段表明属主对象是否是一个控制器,blockOwnerDeletion为true则表明删除属主对象时,会阻塞删除操作,因为有别的对象依赖它,这里删除replicaSet会阻塞,要等到它选中的pod也删除之后这个rs才会最终被删除。具体删除流程下文会具体介绍,这里先理解ownerReferences这一基本概念。

Finalizers

finalizers通常用来阻止资源的删除,其值是一个字符串slice,是依赖和被依赖对象间可以理解的一组key,当一个资源的finalizers字段不为空时,在删除该对象时,只会设置该对象的deletionTimestamp字段,代表正在删除中,只有当条件满足finalizers字段被移除,该对象才可以被最终删除。例如一个pod使用了pv,则该pv会被设置一个key为pv-protection的finalizers,表示不能直接删除这个pv,因为有pod在使用它,只有当pod不再使用该pv,这个finalizers字段才会被移除,此时这个pv才会被最终删除。

以删除configMap为例,首先创建一个包含finalizerconfigMap,key为“kubernetes”。

1
2
3
4
5
6
7
8
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap
finalizers:
- kubernetes
EOF

然后使用kubectl删除该configMap,并让其后台运行,如果没有其他任何操作,该命令会在后台一直运行,这是因为kubectl默认会等到configMap这个资源从etcd中删除后才会退出,而由于该configMap带有不为空的finalizer,所以并没有从etcd存储中删除,删除请求到kube-apiserver后,kube-apiserver只是更新了configMap的删除时间戳deletionTimestamp,可以重新get该资源来确认这一点。

1
2
3
4
kubectl delete configmap/mymap &
configmap "mymap" deleted
jobs
[1]+ Running kubectl delete configmap/mymap
1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl get configmap/mymap -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: "2022-03-29T11:24:39Z"
deletionGracePeriodSeconds: 0
deletionTimestamp: "2022-03-29T11:24:53Z"
finalizers:
- kubernetes
name: mymap
namespace: default
resourceVersion: "40468237"
uid: 73525bff-6888-466f-b6c3-8c50227175f0

然后我们尝试更新该configMap,删除其finalizer字段

1
2
3
4
5
kubectl patch configmap/mymap \
--type json \
--patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'
configmap/mymap patched
[1]+ Done kubectl delete configmap/mymap

更新完后,上一步的kubectl删除操作此时才会结束退出,这虽然是一个更新操作,但到达kube-apiserver后,kube-apiserver发现该configMapdeletionTimestamp不为空即已经被标记了删除,同时更新完finalizer字段也变空,所以会直接从etcd中删除该configMap,而kubectl一直在watch等待该资源被删除,等watch到删除事件,kubectl退出,删除操作完成。

级联删除与garbage collector

级联删除cascade

kubernetes早期版本(1.3之前),当使用kubectl删除时,级联删除的操作都在客户端完成,比如删除replicaSet,客户端先删除pod,然后再去删除replicaSet,这样导致客户端逻辑复杂,不利于扩展,之后实现了garbage collector垃圾收集器这一控制器,在server端实现级联删除的逻辑。

当使用kubectl delete删除资源时,可以设置--cascade参数指定级联删除策略,共有三种模式

  • background:默认策略,后台删除
  • orphan:孤立删除
  • foreground:前台删除

这三种策略都需要garbage collector配合完成,以删除deployment为例,三者具体区别在于:

  • 使用background删除策略时,删除deployment的请求到达kube-apiserverdeployment先被删除,然后garbage collector删除replicaSet,然删除replicaSet对应的pod;
  • 使用orphan删除策略,请求到达kube-apiserverdeployment被设置deletionTimestamp标记删除,并设置key为orphan的finalizer,garbage collector移除replicaSetownerReferences的字段,解除deploymentreplicaSet的关系,同时删除deploymentfinalizer字段,然后deployment被最终删除,此时replicaSet和pod仍然存在不受影响,因此称为孤立删除,即只删除deployment,不删除对应的replicaSet和pod;
  • 使用foreground删除策略,deployment被设置kube-apiserver设置key为foregroundfinalizer,garbage collector发现有replicaSet依赖deployment,同时pod依赖replicaSet,于是将replicaSet同样设置key为foreground的finalizer,然后找到依赖于replicaSet的pod,使用background策略直接删除进而删除replicaSetdeployment,与background的删除策略主要不同在于先删除pod,再删除replicaSet,最后删除deployment,并通过设置 finalizer控制删除顺序。

garbage collector原理

级联删除的主要逻辑都由garbage collector这一控制器来控制,garbage collector由两部分组成:graph_buildergarbagecollector

graph_builder用来维护集群中资源的依赖拓扑图,每种资源被视为一个节点,这个节点同时存储了该节点资源对应的owner以及依赖,比例replicaSet存储了其owner deployment,同时存储了其依赖的pod,这样就可以通过每个节点直接找到其属主owner和依赖。graph_builder通过metaInformer监听集群meta数据变化,动态更新资源间的依赖关系。然后将需要删除的资源的放入队列,根据不同的删除策略orphan或者foreground分别放入不同的队列。

garbagecollector是上述两个队列的消费者,对于orphan队列里的待删除资源,主要操作为删除依赖对象的ownerReferences,使其孤立,并通过删除owner对象的finalizer使kube-apiserver删除owner对象。对于foreground队列里的待删除资源,则是不断寻找被删除资源的依赖资源,类似于递归操作,当一个最底层的对象不再被其他对象依赖时,通过指定其删除策略为background直接从存储层删除吗,对于owner对象则通过移除其值为foregroundfinalizer来删除该对象。

参考

  1. https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/
  2. https://kubernetes.io/docs/concepts/architecture/garbage-collection/
  3. https://github.com/kubernetes/design-proposals-archive/blob/main/api-machinery/garbage-collection.md