你有没有在使用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 | $ cat <<EOF | kubectl create -f - |
终结器通常用于名称空间(namespace),而管理configmap资源的控制器不知道该如何处理finalizers字段。 下面我们尝试删除这个configmap对象:
1 | $ kubectl delete configmap/mymap & |
Kubernetes返回该对象已被删除,然而它并没有真正意义上被删除,而是在删除的过程中。 当我们试图再次获取该对象时,我们发现该对象多了个deletionTimestamp(删除时间戳)字段。
1 | $ kubectl get cm mymap -o yaml |
简而言之,当我们删除带有finalizers字段的对象时,该对象仅仅是被更新了,而不是被删除了。 这是因为Kubernetes获取到该对象包含终结器,通过添加deletionTimestamp(删除时间戳)字段将其置于只读状态(删除终结器键更新除外)。 换句话说,在删除该对象终结器之前,删除都不会完成。
接下来我们尝试通过patch命令删除终结器,并观察configmap/mymap是否会被’真正’删除。
1 | $ kubectl patch configmap/mymap \ |
再次检索该对象
1 | $ kubectl get cm mymap |
发现该对象已被真正删除,下图描述了带有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 | $ cat <<EOF | kubectl create -f - |
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 | $ cat <<EOF | kubectl create -f - |
即cm/mymap-parent为cm/mymap-child的父对象,此时我们删除cm/mymap-parent对象并观察cm/mymap-child对象状态
1 | $ kubectl get cm |
即我们通过删除父对象,间接删除了父对象下的所有子对象。 这种删除k8s中被称为级联删除。我们可不可以只删除父对象,而不删除子对象呢?
答案是: 可以的,删除时通过添加–cascade=false参数实现,我们通过下面的例子来验证:
1 | $ cat <<EOF | kubectl create -f - |
–cascade=false参数实际改变了父-子资源的删除顺序,k8s中关于父-子资源删除策略有以下三种:
- Foreground: 子资源在父资源之前被删除(post-order)
- Background: 父资源在子资源之前被删除(pre-order)
- Orphan: 忽略所有者引用进行删除
6. 强制删除命名空间
有一种情况可能需要强制删除命名空间:
如果您已经删除了一个命名空间,并删除了它下面的所有对象,但名称空间仍然存在,一般为Terminating状态。 则可以通过更新名称空间的finalize属性来强制删除该名称空间。
会话1
1
$ kubectl proxy
会话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便成了孤儿对象。
当出现孤儿对象时,可以手动重新创建名称空间,随后可以手动清理和恢复该对象。