Kubernetes 中的对象删除并不像表面上看起来那么简单,删除对象涉及一系列过程,例如对象的级联和非级联删除,在删除之前检查以确定是否可以安全删除对象等等。这些都是通过称为 Finalizers
(终结器)的 API 对象实现的。
Finalizers
是由字符串组成的数组,当 Finalizers
字段中存在元素时,相关资源不允许被删除,Finalizers
是 Kubernetes 资源删除流程中的一种拦截机制,能够让控制器实现异步的删除前(Pre-delete)回调,在对象删除之前执行相应的逻辑。
Finalizers
可以防止意外删除集群所依赖的、用于正常运作的资源。Kubernetes 中有些原生的资源对象会被自动加上 Finalizers
标签,例如 PVC 和 PV 分别原生自带 kubernetes.io/pvc-protection
和 kubernetes.io/pv-protection
的 Finalizers
标签,以保证持久化存储不被误删,避免挂载了存储的的工作负载产生问题。假如你试图删除一个仍被 Pod 使用的 PVC,该资源不会被立即删除, 它将进入 Terminating
状态,直到 PVC 不再挂载到 Pod 上时, Kubernetes 才清除这个对象。
当删除一个对象时,其对应的控制器并不会真正执行删除对象的操作,在 Kubernetes 中对象的回收操作是由 GarbageCollectorController (垃圾收集器)负责的,其作用就是当删除一个对象时,会根据指定的删除策略回收该对象及其依赖对象。删除的具体过程如下:
metadata.deletionTimestamp
字段设置为当前时间戳,这使得对象处于只读状态(除了修改 finalizers
字段)。metadata.deletionTimestamp
字段非空时,负责监视该对象的各个控制器会执行对应的 Finalizer
动作,每个 Finalizer
动作完成后,就会从 Finalizers
列表中删除对应的 Finalizer
。Finalizers
列表为空时,就意味着所有 Finalizer
都被执行过了,垃圾收集器会最终删除该对象。在 Kubernetes 中,一些对象是其他对象的属主(Owner)。例如,ReplicaSet 是一组 Pod 的属主,具有属主的对象是属主的附属(Dependent)。附属对象有一个 metadata.ownerReferences
字段,用于引用其属主对象。在 Kubernetes 中不允许跨 namespace 指定属主,namespace 空间范围的附属可以指定集群范围或者相同 namespace 的属主。
Kubernetes 会自动为一些对象的附属资源设置属主引用的值, 这些对象包含 ReplicaSet、DaemonSet、Deployment、Job、CronJob、ReplicationController 等等。你也可以通过改变这个字段的值,来手动配置这些关系。
接下来我们通过手动设置 metadata.ownerReferences
字段来设置从属关系。如下所示,我们首先创建了一个属主对象,然后创建了一个附属对象,根据 ownerReferences
字段中的 name 和 uid 关联属主对象。
# 创建属主对象
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-parent
EOF# 获取属主对象 UID
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 # 父对象的 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
现在我们重新创建属主和附属对象,这次我们删除属主对象,发现附属对象也一并被删除了。
$ 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.
继续重新创建属主和附属对象,Kubernetes 默认删除时使用级联删除,这次我们在删除属主对象的时候加上参数 --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
在默认情况下,删除一个对象同时会删除它的附属对象,如果我们在一些特定情况下只是想删除当前对象本身并不想造成复杂的级联删除,可以指定具体的删除策略。在 Kubernetes 中有三种删除策略:
deletionTimestamp
字段设置为对象被标记为要删除的时间点。metadata.finalizers
字段值设置为 foregroundDeletion
。对象一旦被设置为 deletion in progress
状态时,垃圾收集器会删除对象的所有依赖, 垃圾收集器在删除了所有有阻塞能力的附属对象之后( ownerReference.blockOwnerDeletion=true
),再删除属主对象。Foreground
策略:先删除附属对象,再删除属主对象。在 Foreground
模式下,待删除对象首先进入 deletion in progress
状态。在此状态下存在如下的场景:Background
策略(默认):先删除属主对象,再删除附属对象。 在 Background
模式下,Kubernetes 会立即删除属主对象,之后垃圾收集器会在后台删除其附属对象。Orphan
策略:不会自动删除它的附属对象,这些残留的依赖被称作是原对象的孤儿对象。在 kubernetes v1.9 版本之前,大部分控制器的默认删除策略为 Orphan
,从 v1.9 开始,对 apps/v1 下的资源默认使用 Background
模式。
下面的例子中,在删除 Deployment 时指定删除策略为 Orphan
,这样删除 Deployment 后不会删除 Deployment 的附属对象 ReplicaSet,同样地, ReplicaSet 的附属对象 Pod 也不会被删除。
方式一:使用 kubectl,在 -cascade
参数中指定删除策略。
kubectl delete deployment nginx-deployment --cascade=orphan
方式二:使用 Kubernetes API,在 propagationPolicy
参数中指定删除策略。
# 启动一个本地代理会话
kubectl proxy --port=8080 # 使用 curl 来触发删除操作
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"
你可以检查 Deployment 所管理的 ReplicaSet 和 Pod 仍然处于运行状态:
# deployment 已经删除
$ kubectl get deployments
No resources found in default namespace.
# replicaset 和 pod 依然在运行
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-deployment-66b6c48dd5 3 3 3 23h
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-66b6c48dd5-4tnxf 1/1 Running 0 23h
nginx-deployment-66b6c48dd5-l48cp 1/1 Running 0 23h
nginx-deployment-66b6c48dd5-ss6nx 1/1 Running 0 23h
存储的管理是一个与计算实例的管理完全不同的问题,Kubernetes 引入 PersistentVolume 和 PersistentVolumeClaim 两个 API,将存储的细节和使用抽象出来。
下面的 yaml 资源文件中,分别声明的 PV, PVC 和 Pod 三个资源。PV 使用节点本地的 /tmp/mydata 目录作为存储,磁盘容量为 1Gi,在 PVC 中申领容量至少为 1Gi 的卷,Pod 使用 PVC 作为存储卷。
# 创建 PV,使用节点本地 /tmp/mydata 目录作为存储
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/mydata"---
# 创建 PVC,请求至少 1Gi 容量的卷
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
# 创建 Pod,使用 PVC 作为存储卷
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: task-pv-claim
containers:
- name: task-pv-container
image: busybox:1.34
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello >> /var/log/hello.log; sleep 5;done"]
volumeMounts:
- mountPath: "/var/log"
name: task-pv-storage
查看创建的 PV, PVC, Pod。
如下图所示,从左到右依次是 PV, PVC, Pod 的资源详情:
Finalizers
列表中包含 kubernetes.io/pv-protection
,说明 PV 对象是处于被保护状态的,当 PV 没有绑定的 PVC 对象时,该 PV 才允许被删除。PVC 申领与 PV 卷之间的绑定是一种一对一的映射,实现上使用 ClaimRef
来记录 PV 卷与 PVC 申领间的双向绑定关系。Finalizers
列表中包含 kubernetes.io/pvc-protection
,说明 PVC 对象是处于被保护状态的。Pod 中的 volumes.persistentVolumeClaim
字段记录了使用的 PVC。如果用户删除被某 Pod 使用的 PVC 对象,该 PVC 申领不会被立即移除,PVC 对象的移除会被推迟,直至其不再被任何 Pod 使用。此外,如果删除已绑定到某 PVC 申领的 PV 卷,该 PV 卷也不会被立即移除,PV 对象的移除也要推迟到该 PV 不再绑定到 PVC。
接下来演示 Kubernetes 是如何延迟删除 PV 和 PVC 对象的。首先删除 PV。
$ kubectl delete pv task-pv-volume
persistentvolume "task-pv-volume" deleted
^C # 删除后控制台会卡住,ctrl + c 退出
查看该 PV,你可以看到 PV 的状态为 Terminating
,这是因为和该 PV 绑定的 PVC 还未删除,因此 PV 对象此时处于被保护状态的。然后删除 PVC。
$ kubectl delete pvc task-pv-claim
persistentvolumeclaim "task-pv-claim" deleted
^C # 删除后控制台会卡住,ctrl + c 退出
查看该 PVC,发现 PVC 同样处于 Terminating 状态,这是因为使用 PVC 的 Pod 还未删除,因此 PVC 对象此时还处于被保护状态。接着删除 Pod,当 Pod 被删除后,由于没有 Pod 使用 PVC 了,此时 PVC 会被安全地删除;同样地,和 PV 绑定的 PVC 被删除后,PV 也可以被安全地删除了。
$ kubectl delete pod task-pv-pod
再次查看,可以看到此时 Pod, PVC, PV 都被删除了。
Deployment 是最常用的用于部署无状态服务的方式,通过 Deployment 控制器能够以声明的方式更新 Pod(容器组)和 ReplicaSet(副本集)。Deployment 会自动创建并管理 ReplicaSet,可以维护多个版本的 ReplicaSet,方便我们升级和回滚应用;ReplicaSet 的职责是确保任何时间都有指定数量的 Pod 副本在运行。
下面是一个 Deployment 示例,其中创建了一个 ReplicaSet,负责启动三个 nginx
Pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
查看创建的 Deployment, ReplicaSet, Pod。
如下图所示,从左到右依次是 Pod, ReplicaSet, Deployment 的资源详情:
ownerReferences.name
参数表示该 Pod 是名为 nginx-deployment-66b6c48dd5 的 ReplicaSet 的附属对象,并且 Pod 的 ownerReferences.uid
和 ReplicaSet 对象的 uid
相同。ownerReferences.name
参数表示该 ReplicaSet 是名为 nginx-deployment 的 Deployment 的附属对象,并且 ReplicaSet 的 ownerReferences.uid
和 Deployment 对象的 uid
相同。虽然在上面的资源详情中,我们并没有看到 Finalizers
字段,但是当你使用前台或孤立级联删除时,Kubernetes 也会向属主资源添加 Finalizer
。在前台删除中,会添加 Foreground Finalizer
,这样控制器必须在删除了拥有 ownerReferences.blockOwnerDeletion=true
的附属资源后,才能删除属主对象。如果你指定了孤立删除策略,Kubernetes 会添加 Orphan Finalizer
, 这样控制器在删除属主对象后,会忽略附属资源。
在使用 Kubernetes 的过程中,我们有时候会遇到删除 Namespace 或者 Pod 等资源后一直处于 Terminating 状态,等待很长时间都无法删除,甚至有时增加 --force
参数之后还是无法正常删除。这时就需要 edit
该资源,将 finalizers
字段设置为 [],之后 Kubernetes 资源就正常删除了。
Finalizers
可以防止意外删除集群所依赖的、用于正常运作的资源。Finalizers
是 Kubernetes 资源删除流程中的一种拦截机制,能够让控制器实现异步的删除前(Pre-delete)回调,在对象删除之前执行相应的逻辑。Finalizers
列表为空时,就意味着所有 Finalizer
都被执行过了,垃圾回收器会最终删除该对象。metadata.ownerReferences
字段,用于引用其属主对象。Foreground
和 Background
是级联删除,Orphan
是非级联删除。Foreground
先删除附属对象,再删除属主对象;Background
先删除属主对象,再删除附属对象。推荐阅读 点击标题可跳转
《Docker中Image、Container与Volume的迁移》
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!