ᕕ( ᐛ )ᕗ Jimyag's Blog

Kubernetes Pod 创建流程

· 398 字 · 约 2 分钟

本文基于 Kubernetes 集群架构Pod 生命周期调度 等官方文档,梳理 Pod 从被提交到在节点上运行的整体流程及涉及组件。

流程概览

Pod 创建不是单点完成,而是由控制面(control plane,即 API Server、etcd、Scheduler、Controller Manager 等)与节点上的 kubelet 协同完成:用户或控制器提交 Pod 后,请求先到 API Server(kube-apiserver),经认证、鉴权、准入后写入 etcd(集群元数据后端存储);随后 Scheduler(kube-scheduler)为未绑定的 Pod 选择节点并写入绑定信息;目标节点上的 kubelet 监听到该 Pod 后,通过容器运行时(CRI)、网络插件(CNI)、存储接口(CSI)等拉取镜像、创建容器、配置网络与卷,并持续把 Pod 状态回写到 API Server。下文按阶段说明。

阶段一:请求进入与 API Server 处理

用户通过 kubectl apply / kubectl create 或其它客户端向 Kubernetes API 提交 Pod 规约(PodSpec),请求发往 API Server(kube-apiserver,即控制面暴露 Kubernetes API 的组件)。

  1. 认证(Authentication):API Server 验证请求者身份(证书、Token、ServiceAccount 等)。
  2. 鉴权(Authorization):确认该身份是否有权创建对应命名空间下的 Pod(如 RBAC,Role-Based Access Control,基于角色的访问控制)。
  3. 准入控制(Admission Control):可对创建请求做修改或校验,例如 MutatingAdmissionWebhook、ValidatingAdmissionWebhook、ResourceQuota 等;通过后,API Server 将 Pod 对象写入集群的后端存储。
  4. 持久化:若集群使用 etcd 作为后端存储,Pod 的元数据与规约会写入 etcd。此时 Pod 已被集群“接受”,但尚未被调度到任何节点,其阶段(phase)为 Pending。

官方 Pod 生命周期 中说明:Pod 从 Pending 开始,表示“已被集群接受,但一个或多个容器尚未就绪”,其中包含“等待被调度”的时间。

阶段二:调度(Scheduling)

kube-scheduler(Scheduler)是控制面组件,负责为“未指定运行节点”的 Pod 选择节点。据 Cluster ArchitectureScheduling 文档,Scheduler 监听(watch)未绑定节点的 Pod,并执行:

  1. 预选(Filter / Predicate):根据 Pod 的资源需求、节点亲和/反亲和、污点与容忍、节点状态等,过滤掉不满足条件的节点。
  2. 打分(Score / Priority):在剩余节点上按资源均衡、亲和性、拓扑分布等策略打分,选出最优节点。
  3. 绑定(Bind):将选中的节点名写入 Pod 的 spec.nodeName(或通过 Binding 子资源),完成“调度”;此后该 Pod 由该节点上的 kubelet 负责运行。

同一 Pod(同一 UID)在其生命周期内只会被调度一次;若该 Pod 之后被替换(例如被控制器重建),新 Pod 拥有新 UID,会重新走一遍调度流程,且不保证落在同一节点。

阶段三:节点上创建与运行(kubelet)

目标节点上的 kubelet 是运行在每个节点上的代理,负责“根据 PodSpec 保证该节点上的 Pod 中容器处于运行且健康”。据 Cluster Architecture,kubelet 接收一组 PodSpec(通过 API Server 等机制获取),并确保其中描述的容器在运行。kubelet 并不管理非由 Kubernetes 创建的容器。

当 Pod 已被调度到本节点(spec.nodeName 为本节点)后,kubelet 大致会:

  1. 从 API Server 同步该 Pod 的规约与状态(通常通过 watch 或轮询)。
  2. 若 Pod 需要卷:通过 CSI(Container Storage Interface,容器存储接口)或内置(in-tree)卷插件挂载 Volume,供容器使用。
  3. 通过 CRI(Container Runtime Interface,容器运行时接口)调用本节点的容器运行时(如 containerd、CRI-O):先创建 Pod 沙箱(例如 pause 容器或等效沙箱),再按 Pod 规约创建并启动各个业务容器;若镜像不存在,先拉取镜像。
  4. 网络:通过 CNI(Container Network Interface,容器网络接口)插件为 Pod 配置网络(分配 IP、设置路由等),使 Pod 可与集群内其它 Pod 或 Service(集群内逻辑服务与负载均衡)通信。
  5. 执行 init 容器(若有):按序执行完成后再启动主容器。
  6. 启动主容器后,根据配置执行探针(Liveness、Readiness、Startup)并持续把容器与 Pod 状态写回 API Server(如 Running、Ready 等)。

Pod 生命周期文档指出:一旦 Pod 被调度并绑定到节点,kubelet 会通过容器运行时创建容器;容器有三种状态:Waiting、Running、Terminated。Pod 的 phase 会从 Pending 转为 Running(当至少一个主容器已启动),或在后续根据容器退出情况变为 Succeeded / Failed。

kubelet 日志示例

以下为某节点上 kubelet 处理一个 Pod 创建时的典型日志(敏感信息已替换为占位:<node-name> 节点名、<namespace> 命名空间、<pod-name> Pod 名、<pod-uid> Pod UID、<container-id> 容器 ID)。可用 journalctl -u kubelet.service --since="1 hour ago" | grep <pod-name> 在节点上查看同类输出。

  1. SyncLoop ADD:kubelet 从 API 收到该 Pod,加入同步循环。
1
<node-name> kubelet[...]: "SyncLoop ADD" source="api" pods=["<namespace>/<pod-name>"]
  1. 拓扑与 sandbox:拓扑偏好(如 NUMA)决策后,发现尚无 sandbox,需新建。
1
2
<node-name> kubelet[...]: "Best TopologyHint" bestHint={"NUMANodeAffinity":3,"Preferred":true} pod="<namespace>/<pod-name>" containerName="nginx"
<node-name> kubelet[...]: "No sandbox for pod can be found. Need to start a new one" pod="<namespace>/<pod-name>"
  1. 卷挂载:对 ServiceAccount 的 projected 卷执行挂载。
1
2
3
<node-name> kubelet[...]: "operationExecutor.VerifyControllerAttachedVolume started for volume \"kube-api-access-xxxx\" (UniqueName: \"kubernetes.io/projected/<pod-uid>-kube-api-access-xxxx\") pod \"<pod-name>\" (UID: \"<pod-uid>\") " pod="<namespace>/<pod-name>"
<node-name> kubelet[...]: "operationExecutor.MountVolume started for volume \"kube-api-access-xxxx\" ..." pod="<namespace>/<pod-name>"
<node-name> kubelet[...]: "MountVolume.SetUp succeeded for volume \"kube-api-access-xxxx\" ..." pod="<namespace>/<pod-name>"
  1. SyncLoop UPDATE:Pod 规约或状态有更新,再次调和。
1
<node-name> kubelet[...]: "SyncLoop UPDATE" source="api" pods=["<namespace>/<pod-name>"]
  1. PLEG ContainerStarted:PLEG(Pod Lifecycle Event Generator,Pod 生命周期事件生成器)从容器运行时拿到事件;先上报 sandbox 容器,再上报业务容器启动。
1
2
<node-name> kubelet[...]: "SyncLoop (PLEG): event for pod" pod="<namespace>/<pod-name>" event={"ID":"<pod-uid>","Type":"ContainerStarted","Data":"<sandbox-container-id>"}
<node-name> kubelet[...]: "SyncLoop (PLEG): event for pod" pod="<namespace>/<pod-name>" event={"ID":"<pod-uid>","Type":"ContainerStarted","Data":"<app-container-id>"}
  1. 启动耗时统计:从 Pod 进入节点到 Running 的端到端时间及拉镜像区间。
1
<node-name> kubelet[...]: "Observed pod startup duration" pod="<namespace>/<pod-name>" podStartE2EDuration="18.1s" ... firstStartedPulling="..." lastFinishedPulling="..." observedRunningTime="..."

上述顺序与阶段三一致:收到 Pod → 卷挂载 → 创建 sandbox → 创建业务容器 → PLEG 上报 ContainerStarted → 更新启动耗时。

参考链接:

#Kubernetes #Pod #Scheduler #Kubelet