关于Kubernetes里边的Finalizers的那些事

2023-12-21 16:46:25 浏览数 (1)

最近因为业务原因,接触到了k8s的GC机制,特地看了一些k8s的官方文档以及网上的一些博客和资料,梳理了有关Finalizers和级联删除的一些知识点。

垃圾收集

垃圾收集(Garbage Collection,GC)是 Kubernetes 用于清理集群资源的各种机制的统称。

垃圾收集允许系统清理如下资源:

  • 终止的 Pod
    • 对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API server上, 直到用户或者controller进程显式地将其删除。
  • 已完成的 Job
  • 不再存在Owner Reference的对象
  • 未使用的容器和容器镜像
  • 动态制备的、StorageClass 回收策略为 Delete 的 PV 卷
  • 阻滞或者过期的 CertificateSigningRequest (CSRs)
  • 在以下情形中删除了的节点对象:
    • 当集群使用云控制器管理器运行于云端时;
    • 当集群使用类似于云控制器管理器的插件运行在本地环境中时。
  • 节点租约对象

Owner Reference

在kubernetes中,一些对象是其他对象的Owner,具有Owner的对象是Owner的Dependent。

附属对象有一个 metadata.ownerReferences 字段,用于引用其Owner对象。一个有效的Owner Reference,包含与附属对象同在一个namespace下的对象名称和一个 UID。Kubernetes 自动为一些对象的附属资源设置Owner Reference的值, 这些对象包含 ReplicaSet、DaemonSet、Deployment、Job、CronJob、ReplicationController 等。用户也可以通过改变这个字段的值,来手动配置这些关系。

附属对象还有一个 ownerReferences.blockOwnerDeletion 字段,该字段使用布尔值, 用于控制特定的附属对象是否可以阻止垃圾收集删除其Owner对象。如果控制器(例如 Deployment 控制器) 设置了 metadata.ownerReferences 字段的值,Kubernetes 会自动设置 blockOwnerDeletion 的值为 true。你也可以手动设置 blockOwnerDeletion 字段的值,以控制哪些附属对象会阻止垃圾收集。

Kubernetes 准入控制器根据Owner的删除权限控制用户访问,以便为附属资源更改此字段。这种控制机制可防止未经授权的用户延迟Owner对象的删除。

kubernetes中的绝大多数对象通过Owner Reference来链接到彼此。Owner Reference可以告诉控制面对象之间存在的依赖关系。Owner Reference比较广泛的一个应用就是——通过Owner Reference来为控制面以及其他API客户端在删除某一个对象时提供一个清理关联资源的机会。需要注意的是,系统中不允许出现跨namespace的Owner Reference。

Finalizers

Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后, 再完全删除被标记为删除的资源。Finalizer 提醒Controller清理被删除的对象拥有的资源。

当你告诉 Kubernetes 删除一个指定了 Finalizer 的对象时, Kubernetes API 通过填充 .metadata.deletionTimestamp 来标记要删除的对象, 并返回 202 状态码(HTTP "已接受") 使其进入只读状态。此时控制平面或其他组件会采取 Finalizer 所定义的行动, 而目标对象仍然处于终止中(Terminating)的状态。这些行动完成后,控制器会删除目标对象相关的 Finalizer。当 metadata.finalizers 字段为空时,Kubernetes 认为删除已完成并删除对象。

你可以使用 Finalizer 控制资源的垃圾收集。例如,你可以定义一个 Finalizer,在删除目标资源前清理相关资源或基础设施。

你可以通过使用 Finalizers 提醒控制器 在删除目标资源前执行特定的清理任务, 来控制资源的垃圾收集

Finalizers 通常不指定要执行的代码。相反,它们通常是特定资源上的键的列表,类似于注解。Kubernetes 自动指定了一些 Finalizers,但你也可以指定你自己的。

Finalizers工作原理:

当你使用清单文件创建资源时,你可以在 metadata.finalizers 字段指定 Finalizers。当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers 字段中的值, 并进行以下操作:

  • 修改对象,将你开始执行删除的时间添加到 metadata.deletionTimestamp 字段。
  • 禁止对象被删除,直到其 metadata.finalizers 字段为空。
  • 返回 202 状态码(HTTP "Accepted")。

管理 finalizer 的控制器注意到对象上发生的更新操作,对象的 metadata.deletionTimestamp 被设置,意味着已经请求删除该对象。然后,控制器会试图满足资源的 Finalizers 的条件。每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers 字段中删除该键。当 finalizers 字段为空时,deletionTimestamp 字段被设置的对象会被自动删除。你也可以使用 Finalizers 来阻止删除未被管理的资源。

一个常见的 Finalizer 的例子是 kubernetes.io/pv-protection, 它用来防止意外删除 PersistentVolume 对象。当一个 PersistentVolume 对象被 Pod 使用时, Kubernetes 会添加 pv-protection Finalizer。如果你试图删除 PersistentVolume,它将进入 Terminating 状态, 但是控制器因为该 Finalizer 存在而无法删除该资源。当 Pod 停止使用 PersistentVolume 时, Kubernetes 清除 pv-protection Finalizer,控制器就会删除该卷。

级联删除策略

级联删除策略可以看成是Finalizers和Owner Reference的联合使用。级联删除策略有三种,分别是:

  • foreground(前台级联删除):先删子级资源,再删父级资源
  • background(后台级联删除):先删父级资源,再删子级资源
  • orphan(孤立删除):忽略Owner Reference

需要注意的是:

  • 从 kubectl v1.20 开始,级联的默认值为background.
  • 不能使用 kubectl 在命令行上指定级联删除策略。我们必须使用自定义 API 调用来指定它。

实战

Finalizers删除

我们创建一个configmap并给它添加一个Finalizers:

代码语言:javascript复制
#cat <<EOF | kubectl create -f -
> apiVersion: v1
> kind: ConfigMap
> metadata:
>   name: mymap
>   finalizers:
>   - kubernetes
> EOF

然后我们尝试直接删除这个configmap:

代码语言:javascript复制
#kubectl delete configmap/mymap
configmap "mymap" deleted

Kubernetes 会报告该对象已被删除,但是,它并没有在传统意义上被删除。相反,它正在删除过程中。当我们再次尝试get该对象时,我们发现该对象已被修改为包含删除时间戳。

代码语言:javascript复制
kubectl edit configmap/mymap

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: "2022-10-13T09:30:11Z"
  deletionGracePeriodSeconds: 0
  deletionTimestamp: "2022-10-13T09:30:22Z"
  finalizers:
  - kubernetes
  name: mymap
  namespace: default
  resourceVersion: "9168141"
  uid: 99c397e3-ddd6-4e61-864f-ee34803e6ddf

我们可以发现此时多了一个deletionTimestamp: "2022-10-13T09:30:22Z"。这说明发生的事情是对象被更新了,而不是被删除了。这是因为 Kubernetes 看到该对象包含Finalizers并阻止从 etcd 中删除该对象。删除时间戳表示已请求删除,但在我们编辑对象并删除Finalizers之前,删除不会完成。

patch这是使用该命令删除Finalizers的演示。如果我们想删除一个对象,我们可以简单地在命令行上修补它以删除Finalizers。这样,在后台运行的删除将完成,对象将被删除。当我们尝试get该 configmap 时,它将消失。

代码语言:javascript复制
# kubectl patch configmap/mymap 
     --type json 
     --patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'
configmap/mymap patched

# kubectl get configmap/mymap
Error from server (NotFound): configmaps "mymap" not found

因此,如果我们尝试删除一个带有Finalizers的对象,它将一直处于终结状态,直到Controller删除了Finalizers或使用 Kubectl 删除了Finalizers。一旦Finalizers列表为空,该对象实际上可以由 Kubernetes 回收并放入队列中以从注册表中删除。

级联删除我们分别创建一个父对象和一个子对象。因为添加Owner Reference需要绑定集群的uid,所以我们创建完父级对象之后,需要获得父级对象的uid。

代码语言:javascript复制
# cat <<EOF | kubectl create -f -
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: mymap-parent
 EOF
 
configmap/mymap-parent created

# CM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")

#cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: mymap-child
  ownerReferences:
  - apiVersion: v1
    kind: ConfigMap
    name: mymap-parent
    uid: $CM_UID
EOF

configmap/mymap-child created

当添加了Owner Reference之后,删除子级对象并不会把父级对象也删除掉,但删除掉父级对象会把子级对象一并删除掉。

我们可以通过--cascade参数来指定级联删除策略。当我们指定为--cascade=orphan时,就会忽略Owner Reference,此时删除掉父级对象,子级对象仍然会存在。

代码语言:javascript复制
curl -X DELETE 
   127.0.0.1:80/api/v1/namespaces/default/configmaps/mymap-parent 
   -d '{ "kind":"DeleteOptions", "apiVersion":"v1", "propagationPolicy":"Ophan" }' 
   -H "Content-Type: application/json"

来源(版权归原作者所有):https://juejin.cn/post/7153970808480595999

0 人点赞