Kubernetes Pod 删除操作源码解析

发布于 2022-5-1 14:11
浏览
0收藏

作者 阳明
来源 | k8s技术圈(ID:kube100)

比如现在我有一个更新策略为 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 实现的,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

也就是说当执行删除操作的时候,会设置 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,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

在 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 的值,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

上面的代码验证了当执行删除操作的时候,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 中,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

当执行删除操作的时候,apiserver 首先会更新 Pod 中的 DeletionTimestamp 属性,这个改变对于 kubelet 来说属于更新操作,所以会对应 kubetypes.UPDATE 操作,会调用 HandlePodUpdates 函数进行更新。Kubernetes Pod 删除操作源码解析-开源基础软件社区

在 HandlePodUpdates 中会调用 dispatchWork 将 Pod 删除分配给具体的 worker 处理,podWorker 是具体的执行者,也就是每次 Pod 需要更新都会发送给 podWorker。Kubernetes Pod 删除操作源码解析-开源基础软件社区

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:Kubernetes Pod 删除操作源码解析-开源基础软件社区

killPod 函数中会调用容器运行时去停止该 Pod 中的容器,代码位于https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kubelet_pods.go#L856-L868:Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

Kubernetes Pod 删除操作源码解析-开源基础软件社区

 

killPodWithSyncResult 方法中首先调用函数 killContainersWithSyncResult 杀掉所有运行的容器,然后删除 Pod 的 sandbox。Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

首先获取优雅删除的宽限时间:Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

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

最后才会真正去调用底层容器运行时来停止容器:Kubernetes Pod 删除操作源码解析-开源基础软件社区

容器删掉后回到前面的 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,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

在该方法中会判断 Pod 是否已经优雅停止了,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/status/status_manager.go#L583-L652,如下所示:Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

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

这个时候 kubelet 会接受到 REMOVE 的事件,调用 HandlePodRemoves 函数去进行处理:Kubernetes Pod 删除操作源码解析-开源基础软件社区

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

收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐