本文发布时间已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
使用 Finalizers 控制删除
在 Kubernetes 中删除对象可能具有挑战性。您可能认为您已删除某些内容,但发现它仍然存在。虽然发出 kubectl delete
命令并希望获得最佳结果可能适用于日常操作,但了解 Kubernetes delete
命令如何操作将帮助您了解为什么某些对象在删除后仍然存在。
在本文中,我将介绍
- 资源的哪些属性控制删除
- Finalizers 和所有者引用如何影响对象删除
- 如何使用传播策略更改删除顺序
- 删除如何工作,并提供示例
为了简单起见,所有示例都将使用 ConfigMap 和基本 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
命令的状态图非常简单

删除的状态图
尽管此操作很简单,但其他因素可能会干扰删除,包括 finalizers 和所有者引用。
了解 Finalizers
在理解 Kubernetes 中的资源删除时,了解 finalizers 的工作方式非常有帮助,可以帮助您理解为什么某些对象不会被删除。
Finalizers 是资源上的键,用于指示删除前操作。它们控制资源的垃圾回收,旨在提醒控制器在删除资源之前执行哪些清理操作。但是,它们不一定命名应该执行的代码;资源上的 finalizers 基本上只是一系列键,很像注释。与注释一样,它们也可以被操作。
您可能遇到的一些常见 finalizers 是
kubernetes.io/pv-protection
kubernetes.io/pvc-protection
上面的 finalizers 用于卷以防止意外删除。类似地,某些 finalizers 可以用于防止删除任何资源,但不受任何控制器管理。
下面是一个自定义 configmap,它没有属性,但包含 finalizer
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap
finalizers:
- kubernetes
EOF
configmap 资源控制器不理解如何处理 kubernetes
finalizer 键。我将这些 configmap 的 finalizers 称为“死”finalizers,因为它通常在命名空间上使用。以下是尝试删除 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 看到该对象包含 finalizers,并阻止了从 etcd 中删除该对象。删除时间戳表示已请求删除,但在我们编辑该对象并删除 finalizer 之前,删除将不会完成。
以下是使用 patch
命令删除 finalizers 的演示。如果我们想删除一个对象,我们可以简单地在命令行上修补它以删除 finalizers。这样,在后台运行的删除将完成,并且该对象将被删除。当我们尝试 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 的状态图

finalization 的状态图
因此,如果您尝试删除一个具有 finalizer 的对象,它将保持 finalization 状态,直到控制器删除 finalizer 键或使用 Kubectl 删除 finalizers。一旦 finalizer 列表为空,该对象实际上可以被 Kubernetes 回收,并放入队列中以便从注册表中删除。
所有者引用
所有者引用描述了对象组之间的关系。它们是资源上的属性,用于指定彼此之间的关系,因此可以删除整个资源树。
当存在所有者引用时,将处理 Finalizer 规则。所有者引用包含一个名称和一个 UID。所有者引用将同一命名空间中的资源链接在一起,并且它还需要该引用的 UID 才能工作。Pod 通常具有指向拥有副本集的所有者引用。因此,当部署或有状态集被删除时,子副本集和 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 时,命名空间中没有任何 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
。级联的默认值为 true
,但是,您可以对 kubectl delete
使用 --cascade=orphan 选项来删除对象并使其子对象成为孤儿。更新:从 kubectl v1.20 开始,级联的默认值为 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 中的传播策略,它允许您更改树中对象被删除的顺序。在以下示例中,使用 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 服务器,然后执行一个 curl
命令,其中仅包含一个 URL 来执行该 delete
命令。
传播策略有三种不同的选项
Foreground
:在父级之前删除子级(后序)Background
:在子级之前删除父级(先序)Orphan
:忽略所有者引用
请记住,当您删除对象并且指定了所有者引用时,在此过程中将遵守 finalizers。这可能导致对象树持久存在,并且您最终得到一个部分删除。此时,您必须查看对象上任何现有的所有者引用以及任何 finalizers,以了解正在发生的事情。
强制删除命名空间
有一种情况可能需要强制命名空间 finalization。如果您已删除命名空间并清除了它下面的所有对象,但该命名空间仍然存在,可以通过更新命名空间子资源 finalize
来强制删除。这将通知命名空间控制器它需要从命名空间中删除 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
应该谨慎执行此操作,因为它可能仅删除命名空间,并在现已不存在的命名空间中留下孤立的对象 - 这对 Kubernetes 来说是一种令人困惑的状态。如果发生这种情况,可以手动重新创建命名空间,有时孤立的对象会重新出现在刚刚创建的命名空间下,这将允许手动清理和恢复。
要点
正如这些示例所展示的,终结器可能会妨碍 Kubernetes 中资源的删除,尤其是在对象之间存在父子关系时。通常,在代码中添加终结器是有原因的,因此在手动删除之前应始终进行调查。所有者引用允许您指定和删除资源树,尽管在此过程中会遵守终结器。最后,传播策略可用于通过自定义 API 调用指定删除顺序,从而让您控制对象的删除方式。现在您已经对 Kubernetes 中的删除工作原理有了更多了解,我们建议您使用测试集群自行尝试一下。