前言¶
简单记录一下 Pod 从创建到最终 Running 背后发生的事情, 以便对 k8s 的一些工作机制有一个更深入一点的了解。
本文内容所针对的 Kubernetes 版本为 v1.21.3
从发送创建 Pod 的请求到 Pod 信息存入 etcd¶
先讲一下从客户端发送创建 Pod 的请求到 apiserver 然后 apiserver 把数据存入 etcd 过程中发生的事情:
- 客户端向 apiserver 发送创建 Pod 的请求: POST /api/v1/namespaces/{namespace}/pods
- apiserver 收到请求后
- 首先会对请求做 认证(authentication) ,解析请求所携带的认证信息得到 User 信息,然后将 User 信息写入请求的 Context 中。 支持的认证方法详见 官方文档
- 认证通过后,再对 User 做 鉴权(Authorization) ,检查当前 User 对这个请求所操作的资源是否有相应的操作权限。 支持的鉴权方法详见 官方文档
- 认证和鉴权都通过后,请求的 body 将会被反序列化为 runtime.Object 对象。
- 在 存入 etcd 之前 , 反序列化后的对象会 先被 填充默认值 和进行 字段校验
- 然后这个请求和对象还会被 Admission Controllers 处理一遍。
Admission Controllers 即包括 kube-apiserver 内置的 admission controllers 也包括用户自行实现的 admission webhooks 。
- Admission Controllers 既可以实现对请求做进一步的校验(比如按一定策略对请求校验,拦截未使用指定 docker registory 的 Pod)( validating admission )) 也可以实现修改请求创建/修改的对象的属性的需求(比如给 Pod 注入 sidecar 容器)( mutating admission ))。
- 先处理 mutating admission 然后 再处理 validating admission )
- 只要有一个 Admission Controller 返回失败,请求就会失败。
- 多个 Admission Controller 串行执行 , 每个 Admission Controller 内部都有自己的逻辑,比如,
- Admission controllers 处理完以后, 对象被存入到 etcd 中
- 最后根据执行结果方法相应的 Response。
- 此时 Pod 就创建成功了,但是还没有被调度到某个节点并且状态是 Pending。
Pod 调度¶
kube-scheduler 组件负责 Pod 的调度工作,具体过程如下:
- kube-scheduler 通过 Informer 机制 监控 Pod 等资源的变更事件并注册相应的回调函数
- 当上面的 Pod 创建成功后, 触发了 Pod 的变更事件 ,因为此时这个 Pod 满足 nodeName 的值为空并且 schedulerName 中指定的是已知的 Scheduler Framework Name,所以这个 Pod 对象会被放入到 SchedulingQueue 队列中等待处理。
- kube-scheduler 中 SchedulingQueue 中的待调度 Pod 会由 scheduleOne 函数进行处理,Pod 调度逻辑就在这个函数里:
- 根据 Pod 的 schedulerName 字段的值找到 Pod 指定要使用的 Scheduler Framework (fwk)
- 根据调度算法(内置的策略加 fwk 实现的策略)得出适合这个 Pod 的最佳节点(调度算法的详细说明以后再单独细说)
- 如果调度算法失败了:
- 执行 fwk.RunPostFilterPlugins 函数,获取可能的 nominatedNode
- 产生一个 FailedScheduling Event、 更新 Pod 的 status.conditions 字段增加一个 type 为 PodScheduled status 为 False 的 PodCondition 以及更新 status.nominatedNodeName 字段的值为前面获取的 nominatedNode
- 如果调度算法成功返回了节点信息,首先执行 fwk.RunReservePluginsReserve 如果失败了执行 fwk.RunReservePluginsUnreserve 然后按上面 3.2 的操作记录调度失败
- 然后再执行 fwk.RunPermitPlugins `` 如果失败了执行 ``fwk.RunReservePluginsUnreserve 然后按上面 3.2 的操作记录调度失败
- 最后执行 binding 操作
- 执行 fwk.WaitOnPermit 如果失败了执行 fwk.RunReservePluginsUnreserve 然后按上面 3.2 的操作记录调度失败
- 执行 fwk.RunPreBindPlugins 如果失败了执行 fwk.RunReservePluginsUnreserve 然后按上面 3.2 的操作记录调度失败
- 执行真正的 binding 操作 sched.bind , 默认的 Bind 实现 会去 post 当前 Pod 的 binding 子资源 记录 Pod 被调度到哪个节点上了, 如果失败了执行 fwk.RunReservePluginsUnreserve 然后按上面 3.2 的操作记录调度失败
- 执行 fwk.RunPostBindPlugins
当 apiserver 收到对 Pod binding 子资源的 post 请求的时候,会触发 binding 的 create 逻辑, 更新 Pod 的 nodeName 字段为请求中包含的 NodeName 以及 更新 Pod 的 status.conditions 字段增加一个 type 为 PodScheduled status 为 True 的 pod condition 。
此时 Pod 就被调度到一个节点上了,但是 Pod 的还是 Pending 因为 Pod 内的容器还没有在被调度的节点上运行。
节点上运行 Pod 中的容器¶
kubelet 组件负责在节点上运行 Pod 中定义的容器,具体的过程如下:
- kubelet 组件启动后会 watch 所有 nodeName 字段的值是当前节点名称的 Pod 的 变更事件
- 当 Pod 经过调度后,它的 nodeName 字段会被设置为被选中的节点的名称,此时会触发 kubelet 中 pod ADD 事件(因为之前没在这个节点上处理过):
- 触发 Pod 更新 的 处理 逻 辑 :
- 首先执行 canRunPod 检查( 检查 AppArmor 、 NoNewPrivs 以及 ProcMount 这三个特性),如果检查不通过的话,不会进行后续的操作
- 如果网络插件未就绪并且当前 Pod 未使用 Host 网络的话,返回 network is not ready 的错误以及产生一个 NetworkNotReady 的 Event
- 如果启用了 cgroups-per-qos 功能,将为 Pod 创建 Cgroups
- 创建存放 Pod 容器数据的目录:
- Pod 目录,比如 /var/run/kubelet/pods/{PodUID}
- PodVolumes 目录,比如 {PodDir}/volumes
- PodPlugins 目录,比如 {PodDir}/plugins
- 通过 volumeManager.WaitForAttachAndMount 等待 Pod 中所有容器的 volumeMounts 和 volumeDevices 中使用的 volume 被成功 attatch 和 mount (关于 volumeManager 相关内容以后再单独细说)。 如果失败的话,返回 mount 失败的 event 和错误
- 获取 Pod 中指定的 imagePullSecrets 所使用的那些 secret 数据的内容。
- 容器运行时创建容器 :
- 执行 createPodSandbox 方法创建一个 pod sandbox
- 内部 会通过 gRPC 调用不同 CRI(Container Runtime Interface) 所实现的 RunPodSandbox 接口
- 不同 CRI 实现 RunPodSandbox 接口的方法可能会不尽相同。以 Docker 为例,dockershim 中实现的 RunPodSandbox 接口的 内部操作 如下:
- pull sandbox 容器(pause 容器)所用的镜像(默认是 k8s.gcr.io/pause:3.4.1 ,)
- 调用 docker client api 创建 sandbox 容器
- 创建 sandbox checkpoint
- 启动 sandbox 容器
- 更新容器内的 resolv.conf 文件的内容
- 如果 Pod 使用的是 Host 网络,直接返回, 如果不是用的 Host 网络的话,继续
- 通过 CNI 插件 配置容器网络 :
- 实际上是调用 CNI 插件的二进制可执行文件, 执行 一个 ADD 指令
- 如果网络配置失败
- 清理网络资源:执行 CNI 插件的 DEL 指令
- 停止前面启动的容器
- 然后再通过调用 CRI 的 PodSandboxStatus 接口查询一下创建的 pod sandbox 的状态,确保创建的 pod sandbox 无异常,同时获取 status 中包含的 pod IP 信息。
- 启动 ephemeral 容器, 启动容器 的步骤如下:
- 使用前面 6 获取的 secret 数据 pull image
- 调用 CRI 的 CreateContainer 接口创建容器
- 调用 CRI 的 StartContainer 接口启动容器
- 执行 container 中定义的 lifecycle.postStart hook
- 启动 init 容器
- 启动剩下的容器
- 执行 createPodSandbox 方法创建一个 pod sandbox
- 容器启动完成后,将当前 Pod 注册 到 probeManager 中。 probeManager 负责异步执行容器中定义的 startupProbe 、 readinessProbe 以及 livenessProbe 操作。
- 这些 probe 操作的结果会发送到 startupManager 、 readinessManager 以及 livenessManager 中,从而触发响应的 事件响应逻辑
- 比如 readinessProbe 执行成功了会触发更新 statusManager 中记录的 Pod 的 status 信息,更新 ContainersReady 和 Ready 信息,以及 触发 Pod 信息同步操作(这里会有更新 statusManager 把 statusManager 中的Pod 状态 更新 为 Running 的 逻辑 )。
- statusManager 里有个 协程 会定期把待更新的 pod 状态通过 apiserver 进行 更新 。
- 触发 Pod 更新 的 处理 逻 辑 :
经过 kubelet 中一些列的处理后,此时 Pod 的状态就变成 Running 了。
总结¶
简单记录了一下 Pod 从创建到最终 Running 背后发生的事情,其中有些细节没有展开, 后面再补充或者另写一些文章说一下那些没展开的内容。
Comments