关注小众语言、AI技术,记录、分享技术点滴!

0%

K8s无法删除状态为terminating的pod解决方法

你有没有在使用k8s过程中遇到过这种情况: 通过kubectl delete指令删除一些资源时,一直处于Terminating状态。 这是为什么呢?

本文将介绍当你执行kubectl delete语句时,K8s内部都执行了哪些操作。 以及为何有些资源’删除不掉’(具体表现为一直Terminating,删除namespace时很容易遇到这种情况)

接下来,我们聚焦讨论以下四个方面:

1)资源的哪些属性会对删除操作产生影响?
2)finalizers与owner references属性是如何影响删除操作的?
3)如何利用Propagation Policy(分发策略)更改删除顺序?
4)删除操作的工作原理?

1. 词汇表

1)资源: k8s的资源对象(如configmap, secret, pod…)
2)finalizers: 终结器,存放键的列表。列表内的键为空时资源才可被删除
3)owner references: 所有者引用(归谁管理/父资源对象是谁)
4)kubectl: K8s客户端工具

2. 强制删除pod

每当删除namespace或pod等一些Kubernetes资源时,有时资源状态会卡在terminating,很长时间无法删除,甚至有时增加–force flag(强制删除)之后还是无法正常删除。这时就需要edit该资源,将字段finalizers设置为null,之后Kubernetes资源就正常删除了。

当删除pod时有时会卡住,pod状态变为terminating,无法删除pod

1)强制删除pod

1
$ kubectl delete pod xxx -n xxx --force --grace-period=0

2)如果强制删除还不行,设置finalizers为空
如果一个容器已经在运行,这时需要对一些容器属性进行修改,又不想删除容器,或不方便通过replace的方式进行更新。kubernetes还提供了一种在容器运行时,直接对容器进行修改的方式,就是patch命令。

1
$ kubectl patch pod xxx -n xxx -p '{"metadata":{"finalizers":null}}'

这样pod就可以删除了。

3. k8s删除流程

删除操作看似简单,但是有很多因素可能会干扰删除,包括finalizers与owner references属性

4. Finalizers是什么?
上面我们提到了两个属性:finalizers与owner references可能会干扰删除操作,导致删除阻塞或失败。 那Finalizers是什么?会对删除有何影响呢?

当要理解Kubernetes中的资源删除原理时,了解finalizers(以下我们称finalizers为终结器)的工作原理是很有帮助的, 可以帮助您理解为什么有些对象无法被删除。

终结器是资源发出预删除操作信号的属性, 控制着资源的垃圾收集,并用于提示控制器在删除资源之前执行哪些清理操作。

finalizers本质是包含键的列表,不具有实际意义。与annotations(注释)类似,finalizers是可以被操作的(增删改)。

以下终结器您可能遇到过:

  • kubernetes.io/pv-protection
  • kubernetes.io/pvc-protection

这两个终结器作用于卷,以防止卷被意外删除。

类似地,一些终结器可用于防止资源被删除,但不由任何控制器管理。 下面是一个自定义的configmap,它没有具体值,但包含一个终结器:

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

终结器通常用于名称空间(namespace),而管理configmap资源的控制器不知道该如何处理finalizers字段。 下面我们尝试删除这个configmap对象:

1
2
3
4
$ kubectl delete configmap/mymap &
configmap "mymap" deleted
$ jobs
[1]+ Running kubectl delete configmap/mymap

Kubernetes返回该对象已被删除,然而它并没有真正意义上被删除,而是在删除的过程中。 当我们试图再次获取该对象时,我们发现该对象多了个deletionTimestamp(删除时间戳)字段。

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
$ kubectl get cm mymap -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: "2021-09-29T11:04:40Z"
deletionGracePeriodSeconds: 0
deletionTimestamp: "2021-09-29T11:04:55Z"
finalizers:
- kubernetes
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:finalizers:
.: {}
v:"kubernetes": {}
manager: kubectl
operation: Update
time: "2021-09-29T11:04:40Z"
name: mymap
namespace: default
resourceVersion: "1378430"
selfLink: /api/v1/namespaces/default/configmaps/mymap
uid: 8d6ca0b1-4840-4597-8164-a63b526dbf5f

简而言之,当我们删除带有finalizers字段的对象时,该对象仅仅是被更新了,而不是被删除了。 这是因为Kubernetes获取到该对象包含终结器,通过添加deletionTimestamp(删除时间戳)字段将其置于只读状态(删除终结器键更新除外)。 换句话说,在删除该对象终结器之前,删除都不会完成。

接下来我们尝试通过patch命令删除终结器,并观察configmap/mymap是否会被’真正’删除。

1
2
3
$ kubectl patch configmap/mymap \
--type json \
--patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'

再次检索该对象

1
2
$ kubectl get cm mymap
Error from server (NotFound): configmaps "mymap" not found

发现该对象已被真正删除,下图描述了带有finalizers字段的对象删除流程:

总结:当您试图删除一个带有终结器的对象,它将一直处于预删除只读状态, 直到控制器删除了终结器键或使用Kubectl删除了终结器。一旦终结器列表为空,Kubernetes就可以回收该对象,并将其放入要从注册表中删除的队列中

带有finalizers字段的对象无法删除的原因大致如下:

  • 对象存在finalizers,关联的控制器故障未能执行或执行finalizer函数hang住: 比如namespace控制器无法删除完空间内所有的对象, 特别是在使用aggregated apiserver时,第三方apiserver服务故障导致无法删除其对象。 此时,需要会恢复第三方apiserver服务或移除该apiserver的聚合,具体选择哪种方案需根据实际情况而定。
  • 集群内安装的控制器给一些对象增加了自定义finalizers,未删除完fianlizers就下线了该控制器,导致这些fianlizers没有控制器来移除他们。 此时,需要恢复该控制器会手动移除finalizers(多出现于自定义operator),具体选择哪种方案根据实际情况而定。

5. Owner References又是什么?
上面我们提到了两个属性:finalizers与owner references可能会干扰删除操作,导致删除阻塞或失败。 并介绍了Finalizers,接下来我们聊聊Owner References.

Owner References(所有者引用或所有者归属)描述了对象组之间的关系。 指定了资源彼此关联的属性,因此可以级联删除整个资源树。

当存在所有者引用时,将处理终结器规则。所有者引用由名称和UID组成

所有者引用相同名称空间内的链接资源,它还需要UID以使该引用生效(确保唯一)。 Pods通常具有对所属副本集的所有者引用。 因此,当Deloyment或有StatefulSet被删除时,子ReplicaSet和Pod将在流程中被删除。

我们通过下面的例子,来理解Owner References(所有者引用)的工作原理:

1.创建cm/mymap-parent对象

1
2
3
4
5
6
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-parent
EOF

2.获取cm/mymap-parent的UID

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

3.创建cm/mymap-child对象,并设置ownerReferences字段声明所有者引用(通过kind、name、uid字段确保选择器可以匹配到)

1
2
3
4
5
6
7
8
9
10
11
$ 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

即cm/mymap-parent为cm/mymap-child的父对象,此时我们删除cm/mymap-parent对象并观察cm/mymap-child对象状态

1
2
3
4
5
6
7
8
9
10
$ kubectl get cm
NAME DATA AGE
mymap-child 0 2m44s
mymap-parent 0 3m

$ kubectl delete cm mymap-parent
configmap "mymap-parent" deleted

$ kubectl get cm
No resources found in default namespace.

即我们通过删除父对象,间接删除了父对象下的所有子对象。 这种删除k8s中被称为级联删除。我们可不可以只删除父对象,而不删除子对象呢?

答案是: 可以的,删除时通过添加–cascade=false参数实现,我们通过下面的例子来验证:

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
$ 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 delete --cascade=false configmap/mymap-parent
configmap "mymap-parent" deleted

$ kubectl get cm
NAME DATA AGE
mymap-child 0 107s

–cascade=false参数实际改变了父-子资源的删除顺序,k8s中关于父-子资源删除策略有以下三种:

  • Foreground: 子资源在父资源之前被删除(post-order)
  • Background: 父资源在子资源之前被删除 (pre-order)
  • Orphan: 忽略所有者引用进行删除

6. 强制删除命名空间
有一种情况可能需要强制删除命名空间:

如果您已经删除了一个命名空间,并删除了它下面的所有对象,但名称空间仍然存在,一般为Terminating状态。 则可以通过更新名称空间的finalize属性来强制删除该名称空间。

  1. 会话1

    1
    $ kubectl proxy
  2. 会话2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $ NAMESPACE_NAME=test
    cat <<EOF | curl -X PUT \
    127.0.0.1:8001/api/v1/namespaces/$NAMESPACE_NAME/finalize \
    -H "Content-Type: application/json" \
    --data-binary @-
    {
    "kind": "Namespace",
    "apiVersion": "v1",
    "metadata": {
    "name": "$NAMESPACE_NAME"
    },
    "spec": {
    "finalizers": null
    }
    }
    EOF

我们应该谨慎思考是否强制删除命名空间,因为这样做可能只删除名称空间,命名空间下的其他资源删不完全,最终导致留下孤儿对象。 比如资源对象A存在于ddd命名空间,此时若强制删除ddd命名空间, 且对象A又未被删除,那么对象A便成了孤儿对象。

当出现孤儿对象时,可以手动重新创建名称空间,随后可以手动清理和恢复该对象。