问题描述
最近遇到了好几个Kubernetes集群出现了删除Statefulset时,Pod未被删除的问题,经过定位是开发同事,基于farbric 的k8s api进行删除statefulset的操作
,调用了删除statefulset的接口后,又调用了删除pod的接口,但是都是使用的默认删除方式,非级联删除(Orphan策略),这在某些情况下,可能只是调用了删除statefulset的接口,但是未调用删除Pod的接口,就会
出现Pod未被删除的,此时Pod的metadat内可能仍然存在,但是kube-controller-manager中的garbargecollector会将该Pode的ownerreference移除,但是label中仍然带有controller-revision-hash和statefulset的信息,如下图所示
对于正常的属于Statefulset的Pod标识如下所示:
借此机会对Statefulset删除的源码进行了分析,原以为是由Statefulset-controller 进行控制,但是看了一下代码,发现Statefulset-controller只是用来控制副本数的变化,但是对于Statefulset的删除,并不做任何处理,statefulset-controller的如下代码:
1 | // If the StatefulSet is being deleted, don't do anything other than updating |
##Kubernetes资源删除的方式
Kubernetes 在删除资源时,存在级联删除和非级联删除
###控制垃圾收集器删除 Dependent
####级联删除
当删除对象时,可以指定是否该对象的 Dependent 也自动删除掉。自动删除 Dependent 也称为级联删除。Kubernetes 中有两种级联删除的模式:background 模式和 foreground 模式。
1 | kubectl delete statefulset -n de2ca8d1-94b4-4faa-8077-e9374ca9db4e 5bagk2rivkjno --cascade=true |
Background 级联删除,在 background 级联删除 模式下,Kubernetes 会立即删除 Owner 对象,然后垃圾收集器会在后台删除这些 Dependent。
1
2
3 curl -X DELETE 127.0.0.1:8080/apis/extensions/v1beta1/namespaces/default/replicasets/my-repset \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
-H "Content-Type: application/json"
Foreground 级联删除m在 foreground 级联删除 模式下,根对象首先进入 “删除中” 状态。该对象会设置deletionTimestamp 字段对象的 metadata.finalizers 字段包含了值 “foregroundDeletion”,对象仍然可以通过 REST API 可见,一旦被设置为 “删除中” 状态,垃圾收集器会删除对象的所有 Dependent。垃圾收集器删除了所有 “Blocking” 的 Dependent(对象的 ownerReference.blockOwnerDeletion=true)之后,它会删除 Owner 对象。
如果一个对象的ownerReferences 字段被一个 Controller(例如 Deployment 或 ReplicaSet)设置,blockOwnerDeletion 会被自动设置,没必要手动修改这个字段。
1 | curl -X DELETE localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/my-repset \ |
非级联删除
如果删除对象时,不自动删除它的 Dependent,这些 Dependent 被称作是原对象的 孤儿(Orphan),可以使用以下命令实现
1 | kubectl delete statefulset -n de2ca8d1-94b4-4faa-8077-e9374ca9db4e 5bagk2rivkjno --cascade=false |
或者
1 | curl -X DELETE 127.0.0.1:8080/apis/extensions/v1beta1/namespaces/default/replicasets/my-repset \ |
Kubernetes 删除apps流程分析
删除流程中几个重要的过程包括 kube-apiserver 提供的rest服务
通过调用rest api实现etcd数据库中,app对象的状态更新,包括增加deletetimestamp和finalizer自带,触发更新事件
kube-controller-manager收到了apps状态的更新事件,通过更新内置的graph(集群内资源依赖附属关系的图)和garbagecollector进行资源极其附属的删除,这里是只在etcd数据库删除
kubelet收到第1步中的资源删除事件,进行底层资源的删除和回收
###kube-apiserver
kube-apiserver在启动时会基于go-restful将rest服务的handler 进行加载,主要如下所示:
pkg/master/master.go
1 | // InstallAPIs will install the APIs for the restStorageProviders if they are enabled. |
主要的调用链:
1 | k8s.io/apiserver/pkg/server/genericapiserver.go:InstallAPIGroups() |
rest注册的核心函数:将rest请求直接映射为etcd存储的操作
1 | staging/src/k8s.io/apiserver/pkg/endpoints/installer.go:183 registerResourceHandlers() |
删除的rest请求对应的处理请求在这里
1 | ..... |
该接口的定义如下所示,即Delete函数可能直接删除数据,也可能异步删除资源
1 | // GracefulDeleter knows how to pass deletion options to allow delayed deletion of a |
这里有个比较重要的Delete函数,用于在Delete之前做一些业务处理,就包括了我们前面重点提到的设置deletetimestamp 和finalizers字段,对于Statefulset特有的增删改查的预处理,代码都归档在了k8s.io\kubernetes\pkg\registry\apps\statefulset目录,但是对于通用的增删改查预处理操作
,代码被归档在了这里 k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/registry/rest/,其中通用的BeforeDelete归档在了staging/src/k8s.io/apiserver/pkg/registry/rest/delete.go:BeforeDelete()
其中核心的Delete函数 在staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go:Delete(),重点关注下面的代码段,设置删除策略:
1 | .... |
###kube-controller-manager
kube-controller-manager内有一个GarbageCollector用于完成资源的清理删除工作,启动的时候首先会运行一个dependencyGraphBuilder 用于构建集群资源的依赖关系图谱,这个graphbuild 会获取集群的全部资源,并根根据资源的metadata信息构建关系图谱
,并基于事件监听更新 关系图谱
1 | pkg/controller/garbagecollector/graph_builder.go:startMonitors() |
graph_build 中会处理收到的资源状态更新事件,将产生事件的对象放到缓存队列内,主要pkg/controller/garbagecollector/graph_builder.go:processGraphChanges()
1 | // Dequeueing an event from graphChanges, updating graph, populating dirty_queue. |
pkg/controller/garbagecollector/garbagecollector.go:attemptToDeleteWorker() 处理每个删除对象的事件
1 | func (gc *GarbageCollector) attemptToDeleteItem(item *node) error { |
##kubelet
kubelet主要完成资源的释放,主要的删除Pod的处理逻辑,在kubelet的主函数syncpod中,当发现是删除pod的事件时,立即处理
1 | func (kl *Kubelet) syncPod(o syncPodOptions) error { |