这篇文章发表已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不再准确。
使用 Finalizer 控制删除
在 Kubernetes 中删除对象可能具有挑战性。您可能认为已删除了某个对象,却发现它仍然存在。虽然执行 kubectl delete
命令并寄希望于其能正常工作适用于日常操作,但了解 Kubernetes delete
命令的工作原理将有助于您理解为何某些对象在删除后仍然残留。
在本文中,我将探讨
- 资源的哪些属性控制删除
- Finalizer 和所有者引用如何影响对象删除
- 如何使用传播策略更改删除顺序
- 删除的工作原理及示例
为简单起见,所有示例都将使用 ConfigMaps 和基本 Shell 命令来演示此过程。我们将探讨这些命令的工作原理,并讨论在实际使用中可能产生的后果和结果。
基本的 delete
Kubernetes 有几个不同的命令可用于创建、读取、更新和删除对象。为了本文的目的,我们将重点关注四个 kubectl
命令:create
、get
、patch
和 delete
。
以下是基本 kubectl delete
命令的示例
kubectl create configmap mymap
configmap/mymap created
kubectl get configmap/mymap
NAME DATA AGE
mymap 0 12s
kubectl delete configmap/mymap
configmap "mymap" deleted
kubectl get configmap/mymap
Error from server (NotFound): configmaps "mymap" not found
以 $
开头的 Shell 命令后面是它们的输出。可以看到,我们首先执行 kubectl create configmap mymap
,这会创建一个空的 ConfigMap mymap
。接下来,我们需要 get
该 ConfigMap 以证明它存在。然后我们可以删除该 ConfigMap。再次尝试 get
它会产生一个 HTTP 404 错误,这意味着找不到该 ConfigMap。
基本 delete
命令的状态图非常简单

Delete 状态图
尽管此操作很简单,但其他因素可能会干扰删除过程,包括 finalizer 和所有者引用。
理解 Finalizer
在理解 Kubernetes 中的资源删除时,了解 finalizer 的工作原理很有帮助,并且可以帮助您理解为什么有些对象无法删除。
Finalizer 是资源上的键,用于指示预删除操作。它们控制资源的垃圾回收,旨在提醒控制器在移除资源之前需要执行哪些清理操作。但是,它们不一定指定要执行的代码;资源上的 finalizer 本质上只是一系列键,很像注解。像注解一样,它们可以被操作。
一些您可能遇到过的常见 finalizer 有
kubernetes.io/pv-protection
kubernetes.io/pvc-protection
上方的 finalizer 用于卷上,以防止意外删除。类似地,一些 finalizer 可以用于防止任何资源被删除,但它们不受任何控制器管理。
下方是一个自定义的 ConfigMap,它没有其他属性,但包含一个 finalizer
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap
finalizers:
- kubernetes
EOF
ConfigMap 资源控制器不理解如何处理 kubernetes
finalizer 键。我将这些用于 ConfigMap 的 finalizer 称为“死” finalizer,因为它通常用于 Namespace 上。尝试删除此 ConfigMap 时会发生以下情况
kubectl delete configmap/mymap &
configmap "mymap" deleted
jobs
[1]+ Running kubectl delete configmap/mymap
Kubernetes 会报告对象已删除,但是,它并没有以传统意义上的方式被删除。相反,它正在删除过程中。当我们再次尝试 get
该对象时,发现对象已被修改以包含删除时间戳。
kubectl get configmap/mymap -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: "2020-10-22T21:30:18Z"
deletionGracePeriodSeconds: 0
deletionTimestamp: "2020-10-22T21:30:34Z"
finalizers:
- kubernetes
name: mymap
namespace: default
resourceVersion: "311456"
selfLink: /api/v1/namespaces/default/configmaps/mymap
uid: 93a37fed-23e3-45e8-b6ee-b2521db81638
简而言之,发生的情况是对象被更新了,而不是被删除了。这是因为 Kubernetes 发现对象包含 finalizer,并阻止从 etcd 中移除对象。删除时间戳表示已请求删除,但删除操作直到我们编辑对象并移除 finalizer 后才能完成。
以下是如何使用 patch
命令移除 finalizer 的演示。如果要删除对象,只需在命令行上对其进行 patch 操作以移除 finalizer。这样,在后台运行的删除操作将完成,对象将被删除。当我们尝试 get
该 ConfigMap 时,它将消失。
kubectl patch configmap/mymap \
--type json \
--patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'
configmap/mymap patched
[1]+ Done kubectl delete configmap/mymap
kubectl get configmap/mymap -o yaml
Error from server (NotFound): configmaps "mymap" not found
这是 finalization 的状态图

Finalize 状态图
因此,如果尝试删除带有 finalizer 的对象,它将一直处于 finalization 状态,直到控制器移除 finalizer 键或使用 Kubectl 移除 finalizer。一旦 finalizer 列表为空,对象才能被 Kubernetes 真正回收并放入队列中,从注册表中删除。
所有者引用
所有者引用描述了一组对象如何相互关联。它们是资源上的属性,用于指定彼此之间的关系,从而可以删除整个资源树。
存在所有者引用时,会处理 finalizer 规则。所有者引用由名称和 UID 组成。所有者引用链接同一 Namespace 内的资源,并且该引用也需要 UID 才能工作。Pod 通常包含指向所属 ReplicaSet 的所有者引用。因此,当 Deployment 或 StatefulSet 被删除时,其子 ReplicaSet 和 Pod 也会在此过程中被删除。
以下是一些所有者引用的示例及其工作原理。在第一个示例中,我们首先创建父对象,然后创建子对象。结果是一个非常简单的 ConfigMap,它包含对其父对象的所有者引用
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-parent
EOF
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
当存在所有者引用时,删除子对象不会删除父对象
kubectl get configmap
NAME DATA AGE
mymap-child 0 12m4s
mymap-parent 0 12m4s
kubectl delete configmap/mymap-child
configmap "mymap-child" deleted
kubectl get configmap
NAME DATA AGE
mymap-parent 0 12m10s
在此示例中,我们重新创建了上述的父子 ConfigMap。现在,当我们从父对象(而不是子对象)删除时,子对象指向父对象的所有者引用存在,当我们 get
这些 ConfigMap 时,Namespace 中没有任何 ConfigMap。
kubectl get configmap
NAME DATA AGE
mymap-child 0 10m2s
mymap-parent 0 10m2s
kubectl delete configmap/mymap-parent
configmap "mymap-parent" deleted
kubectl get configmap
No resources found in default namespace.
总之,当存在子对象指向父对象的覆盖所有者引用时,删除父对象会自动删除子对象。这称为 cascade
。cascade 的默认值为 true
,但是,您可以使用 kubectl delete
的 --cascade=orphan 选项来删除对象并使其子对象成为孤儿。更新:从 kubectl v1.20 开始,cascade 的默认值为 background
。
在以下示例中,有一个父对象和一个子对象。请注意,所有者引用仍然包含在内。如果我使用 --cascade=orphan 删除父对象,父对象被删除,但子对象仍然存在
kubectl get configmap
NAME DATA AGE
mymap-child 0 13m8s
mymap-parent 0 13m8s
kubectl delete --cascade=orphan configmap/mymap-parent
configmap "mymap-parent" deleted
kubectl get configmap
NAME DATA AGE
mymap-child 0 13m21s
--cascade 选项链接到 API 中的传播策略 (propagation policy),该策略允许您更改树中对象的删除顺序。以下示例使用 API 访问来构建一个带有背景传播策略的自定义删除 API 调用
kubectl proxy --port=8080 &
Starting to serve on 127.0.0.1:8080
curl -X DELETE \
localhost:8080/api/v1/namespaces/default/configmaps/mymap-parent \
-d '{ "kind":"DeleteOptions", "apiVersion":"v1", "propagationPolicy":"Background" }' \
-H "Content-Type: application/json"
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Success",
"details": { ... }
}
请注意,传播策略不能通过 kubectl 命令行指定。您必须使用自定义 API 调用来指定它。只需创建一个代理,以便您从客户端访问 API 服务器,然后执行一个包含 URL 的 curl
命令来执行该 delete
命令。
传播策略有三种不同的选项
Foreground
:先删除子对象,再删除父对象(后序)Background
:先删除父对象,再删除子对象(前序)Orphan
:忽略所有者引用(对象成为孤儿)
请记住,删除对象并指定所有者引用后,在此过程中将遵循 finalizer。这可能导致对象树仍然存在,最终出现部分删除的情况。此时,您必须查看对象上存在的任何所有者引用以及任何 finalizer,以了解发生了什么。
强制删除 Namespace
有一种情况可能需要强制对 Namespace 进行 finalization。如果您已删除 Namespace 并清空了其下的所有对象,但 Namespace 仍然存在,可以通过更新 Namespace 子资源 finalize
来强制删除。这将通知 Namespace 控制器需要从 Namespace 中移除 finalizer 并执行任何清理操作。
cat <<EOF | curl -X PUT \
localhost:8080/api/v1/namespaces/test/finalize \
-H "Content-Type: application/json" \
--data-binary @-
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "test"
},
"spec": {
"finalizers": null
}
}
EOF
此操作应谨慎进行,因为它可能只删除 Namespace 而留下孤儿对象在该(现已不存在的)Namespace 中——这对于 Kubernetes 来说是一个令人困惑的状态。如果发生这种情况,可以手动重新创建 Namespace,有时孤儿对象会重新出现在刚创建的 Namespace 下,从而允许手动清理和恢复。
主要收获
正如这些示例所示,finalizer 可能会妨碍在 Kubernetes 中删除资源,尤其是在对象之间存在父子关系时。通常,在代码中添加 finalizer 是有原因的,因此在手动删除之前应始终进行调查。所有者引用允许您指定和移除资源树,尽管在此过程中会遵循 finalizer。最后,传播策略可以通过自定义 API 调用用于指定删除顺序,让您能够控制如何删除对象。现在您对 Kubernetes 中的删除操作有了更多了解,我们建议您使用测试集群自己尝试一下。