查看原文
其他

Kubernetes Pod 删除操作源码解析

The following article is from k8s技术圈 Author 阳明

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

☸ ➜ kubectl get pods
NAME                    READY   STATUS    RESTARTS        AGE
minio-875749785-sv5ns   1/1     Running   1 (2m52s ago)   42h
☸ ➜ kubectl delete pod minio-875749785-sv5ns
pod "minio-875749785-sv5ns" deleted

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

☸ ➜ kubectl get pods -w
NAME                    READY   STATUS              RESTARTS         AGE
minio-875749785-sv5ns   1/1     Running             1 (2m46s ago)   42h
minio-875749785-sv5ns   1/1     Terminating         1 (2m57s ago)   42h
minio-875749785-h2j2b   0/1     Pending             0               0s
minio-875749785-h2j2b   0/1     Pending             0               0s
minio-875749785-h2j2b   0/1     ContainerCreating   0               0s
minio-875749785-sv5ns   0/1     Terminating         1 (2m59s ago)   42h
minio-875749785-sv5ns   0/1     Terminating         1 (2m59s ago)   42h
minio-875749785-sv5ns   0/1     Terminating         1 (2m59s ago)   42h
minio-875749785-h2j2b   0/1     Running             0               17s
minio-875749785-h2j2b   1/1     Running             0               30s

从上面的过程可以看到当我们执行 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 请求:

I0408 11:25:33.002155   42938 round_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'
I0408 11:25:33.037245   42938 round_trippers.go:454] DELETE https://192.168.0.111:6443/api/v1/namespaces/default/pods/minio-875749785-sv5ns 200 OK in 35 milliseconds

接收到删除请求的处理器位于代码 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 容器。

// Stop all sandboxes belongs to same pod
for _, podSandbox := range runningPod.Sandboxes {
    if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil && !crierror.IsNotFound(err) {
        killSandboxResult.Fail(kubecontainer.ErrKillPodSandbox, err.Error())
        klog.ErrorS(nil"Failed to stop sandbox""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 彻底从节点上删除了。

- END -



后台回复“加群”,带你进入高手交流群


推荐阅读
还怕记不住 Kubectl 命令?K9s 太强大了Kubernetes 微服务最佳实践
使用 Lux 下载B站视频,真强大
38 万K8s API 服务暴露在公网上可能被攻击?
大规模 K8s 集群性能瓶颈和调优实践
优雅的跨 Namespace 同步 Secret 和 ConfigMap?
Kruise 轻松让 K8S 应用实现渐进式交付图解 Kubernetes Pod 如何获取 IP 地址使用 Telepresence 轻松在本地调试 k8s 应用程序GitOps 工具选型,33 款工具任你挑!就在刚刚 k8s 1.24 正式发布,来看功能总览Nomad 会替代 Kubernetes 吗?两者如何选择?Docker 入门终极指南,详细版!漫画轻松看懂如何用 Kubernetes 实现 CI/CD阿里开源的低代码引擎,已收获 4.5K 星星新手必须知道的 Kubernetes 架构Kubernetes 架构核心点详细总结!


全网粉丝20W的头部大号,专注云原生、Golang、Linux实用脚本,效率工具,免费CSDN下载,回复【go】获取近 6 万 Star 的资源回复【1024】获取全种类IT资料,回复【红包封面】获取超好看封面,回复【加群】进入高手如云技术交流群

分享、点赞和在看
支持我们分享更多好文章,谢谢!
                
 点个在看集群永保稳定👇

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存