Kubernetes Pod 删除操作源码解析

k8s技术圈

共 6110字,需浏览 13分钟

 ·

2022-04-13 07:31

比如现在我有一个更新策略为 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 彻底从节点上删除了。

浏览 47
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报