您的位置:首页 >聚焦 >

Kubernetes Pod 删除操作源码解析

2022-04-13 07:48:57    来源:程序员客栈

比如现在我有一个更新策略为 Recreate的应用,然后执行删除命令,如下所示:

☸➜kubectlgetpodsNAMEREADYSTATUSRESTARTSAGEminio-875749785-sv5ns1/1Running1(2m52sago)42h☸➜kubectldeletepodminio-875749785-sv5nspod"minio-875749785-sv5ns"deleted

在删除之前在另外一个终端观察应用状态:

☸➜kubectlgetpods-wNAMEREADYSTATUSRESTARTSAGEminio-875749785-sv5ns1/1Running1(2m46sago)42hminio-875749785-sv5ns1/1Terminating1(2m57sago)42hminio-875749785-h2j2b0/1Pending00sminio-875749785-h2j2b0/1Pending00sminio-875749785-h2j2b0/1ContainerCreating00sminio-875749785-sv5ns0/1Terminating1(2m59sago)42hminio-875749785-sv5ns0/1Terminating1(2m59sago)42hminio-875749785-sv5ns0/1Terminating1(2m59sago)42hminio-875749785-h2j2b0/1Running017sminio-875749785-h2j2b1/1Running030s

从上面的过程可以看到当我们执行 kubectl delete命令后 Pod 变成了 Terminating状态,然后才消失。接下来我们会从代码角度来介绍下删除 Pod 的整体流程。

这里我们以 v1.22.8版本的 Kubernetes 为例进行说明,其他版本不保证代码完全一致,但是整体思路是一致的。

删除状态

我们可以根据 kubectl 操作后看到的状态来进行跟踪,上面的格式化结果是通过代码 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/printers/internalversion/printers.go#L88-L102 实现的,如下所示:

对于 Pod 的输出结果是通过 printPod函数获取的,代码位于:https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/printers/internalversion/printers.go#L756-L840,其中有一段代码提到了 Terminating值,是在 pod.DeletionTimestamp != nil的情况下变成该状态的,如下所示:

也就是说当执行删除操作的时候,会设置 Pod 的 DeletionTimestamp属性,这个时候就会显示成 Terminating状态。

当执行删除操作的时候,会向 apiserver 发送一次 DELETE 请求:

I040811:25:33.00215542938round_trippers.go:435]curl-v-XDELETE-H"Content-Type:application/json"-H"User-Agent:kubectl/v1.22.7(darwin/amd64)kubernetes/b56e432"-H"Accept:application/json""https://192.168.0.111:6443/api/v1/namespaces/default/pods/minio-875749785-sv5ns"I040811:25:33.03724542938round_trippers.go:454]DELETEhttps://192.168.0.111:6443/api/v1/namespaces/default/pods/minio-875749785-sv5ns200OKin35milliseconds

接收到删除请求的处理器位于代码 https://github.com/kubernetes/kubernetes/blob/v1.22.8/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L986,如下所示:

在 BeforeDelete函数中判断是否需要优雅删除,判断的标准是 DeletionGracePeriodSeconds值是否为 0,不为零则认为是优雅删除,apiserver 不会立即将这个对象从 etcd 中删除,否则直接删除。对于 Pod 而言,默认 DeletionGracePeriodSeconds为 30 秒,因此这里不会被立刻删除掉,而是将 DeletionTimestamp设置为当前时间,DeletionGracePeriodSeconds设置为默认值 30 秒。代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/staging/src/k8s.io/apiserver/pkg/registry/rest/delete.go#L93-L159,在该函数中会设置 DeletionTimestamp的值,如下所示:

上面的代码验证了当执行删除操作的时候,apiserver 会先设置 Pod 的 DeletionTimestamp属性为当前时间加上优雅删除宽限时长的时间点,设置了该属性后,我们客户端格式化过后看到的就是 Terminating状态了。

优雅删除

由于 Pod 中涉及到其他很多资源,比如 sandbox 容器、volume 卷等等,在删除后都需要进行回收,而删除 Pod 最终也是去删除对应的容器,这个就需要 Pod 所在节点的 kubelet 来完成清理了。kubelet 首先同样会一直 watch 我们的 Pod,当 Pod 的删除时间更新后,自然就会接收到事件,然后进行相应的清理工作。

kubelet 对 Pod 的处理主要在 syncLoop函数中,会去调用和事件相关的处理函数 syncLoopIteration,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kubelet.go#L2040-L2079 中,如下所示:

当执行删除操作的时候,apiserver 首先会更新 Pod 中的 DeletionTimestamp属性,这个改变对于 kubelet 来说属于更新操作,所以会对应 kubetypes.UPDATE操作,会调用 HandlePodUpdates函数进行更新。

在 HandlePodUpdates中会调用 dispatchWork将 Pod 删除分配给具体的 worker 处理,podWorker 是具体的执行者,也就是每次 Pod 需要更新都会发送给 podWorker。

dispatchWork方法会调用 UpdatePod函数对 Pod 进行删除,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/pod_workers.go#L540-L765,在该函数中会通过一个 channel 传递 Pod 信息,在一个 goroutine 中调用 managePodLoop函数进行处理,该函数中会调用 syncTerminatingPod/syncPod方法来进行删除操作。

最终都会调用 killPod函数去执行删除 Pod:

killPod函数中会调用容器运行时去停止该 Pod 中的容器,代码位于https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kubelet_pods.go#L856-L868:

容器运行时的 KillPod 方法位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L969-L998,如下所示:

killPodWithSyncResult方法中首先调用函数 killContainersWithSyncResult杀掉所有运行的容器,然后删除 Pod 的 sandbox。

在该函数中,利用多个 goroutine 来对 Pod 中的每一个容器进行删除,删除容器的方法是 killContainer,在该函数中首先会执行 pre-stop 这个 hooks(如果存在的话),然后才停止容器,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kuberuntime/kuberuntime_container.go#L660-L736。

首先获取优雅删除的宽限时间:

其中 TerminationGracePeriodSeconds可以在资源清单文件中进行设置,默认为 30 秒,这个时间是,给 Pod 发出关闭指令后会给应用发送 SIGTERM 信号,程序只需要捕获 SIGTERM 信号并做相应处理即可。也就是 Pod 接收到 SIGTERM 信号后,应用能够优雅关闭的时间。该时间是由 apiserver 设置的,前面已经分析过。

如果配置了 pre-stop hook 并且还有足够的时间,则会执行该 hook,pre-stop 主要是为了业务在容器删除前前,能够优雅的停止,比如资源回收等操作:

最后才会真正去调用底层容器运行时来停止容器:

容器删掉后回到前面的 killPodWithSyncResult函数中,接下来就会去调用运行时服务的 StopPodSandbox函数停止 sandbox 容器,也就是 pause 容器。

//Stopallsandboxesbelongstosamepodfor_,podSandbox:=rangerunningPod.Sandboxes{iferr:=m.runtimeService.StopPodSandbox(podSandbox.ID.ID);err!=nil&&!crierror.IsNotFound(err){killSandboxResult.Fail(kubecontainer.ErrKillPodSandbox,err.Error())klog.ErrorS(nil,"Failedtostopsandbox","podSandboxID",podSandbox.ID)}}

到这里 kubelet 就完成了对 Pod 的优雅删除,但是这并没有结束。

同步状态

对于优雅删除一开始在 apiserver 只是给 Pod 设置了 DeletionTimestamp属性,然后 kubelet watch 来更新后去完成了 Pod 的优雅删除,但是现在服务端中还有 Pod 的记录,并没有真正去删除。

在 kubelet 启动的时候同时还去启动了一个 statusManager 的同步循环,该 Manager 是 kubelet pod 状态的真实来源,应该与最新的 v1.PodStatus保持同步,它还将更新同步回 apiserver,也就是当优雅删除完成后我们还将通过该管理器将状态同步回 apiserver。

状态管理器在与 apiserver 进行状态同步的时候会去调用该管理器下面的 syncPod方法进行处理,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/status/status_manager.go#L149-L181,如下所示:

在该方法中会判断 Pod 是否已经优雅停止了,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/status/status_manager.go#L583-L652,如下所示:

比如会判断是否还有容器在运行、volumes 是否还没有清理、pod cgroup 还没清空等等,如果 canBeDeleted返回 true,则表示 pod 已经优雅的停止了,那么这个时候就可以向 apiserver 发送 Delete 请求,再次删除 Pod 了。

不过这一次的设置的 GracePeriodSeconds为 0,表示要强制删除 Pod 了,到这里 apiserver 会再次收到 DELETE 请求,与第一次不同的是,这次是强制删除 Pod,会去 etcd 中删除 Pod 对象了。

这个时候 kubelet 会接受到 REMOVE 的事件,调用 HandlePodRemoves函数去进行处理:

首先会去调用 deletePod函数去停掉关联的 pod worker,然后还会调用 probeManager去移除 Pod 相关的探针 prober worker,到这里就表示 Pod 彻底从节点上删除了。

关键词: 进行处理 这个时候 当前时间

相关阅读