Pod 启动过程:从 kubectl apply 到容器就绪
大多数 Kubernetes 入门文章会把 Pod 启动过程讲成一个线性的编号列表:API Server 接收请求 → Scheduler 选节点 → kubelet 拉镜像 → 容器启动。
这个描述没有错,但它掩盖了真正重要的东西:这个过程根本不是线性的,它是多个独立控制循环并发协作的结果,而且每个环节都有自己独特的失败模式。
控制循环,不是调用链
理解 Pod 启动的关键,是先放弃”请求-响应”的思维模型。Kubernetes 里没有一个”主程序”在协调整个流程——有的是多个组件各自维护自己的协调循环(reconcile loop),通过 etcd 这个共享状态存储异步通信。
当你执行 kubectl apply 时,你的 YAML 被写入 etcd,仅此而已。后面发生的一切,都是各个控制器通过 Watch API 监听到状态变化、然后各自行动的结果。
1 | |
Scheduler 和 kubelet 并不互相调用,它们只是各自监听 etcd 里的状态变化,然后做自己该做的事。
Scheduler:决定落在哪台机器
Scheduler 的工作比”找一个资源够用的节点”复杂得多。它要通过两轮过滤:
Filtering(过滤):排除不可用的节点。节点资源不足、有不兼容的 Taint、PodAffinity 约束不满足——任何一条满足就直接排除。
Scoring(打分):在候选节点中排名。默认的打分策略包括 LeastRequestedPriority(选资源利用率低的)、InterPodAffinityPriority(考虑同类 Pod 的分布)等多个维度。
Scheduler 选好节点后,会向 API Server 写一个 Binding 对象,把 Pod 的 spec.nodeName 填上目标节点名。这个写操作完成之后,Scheduler 的工作就结束了——它不管后面发生什么。
Pending 卡住的常见原因:节点资源(CPU/内存)不足、没有节点满足 nodeSelector 或 nodeAffinity、PVC 无法绑定。用 kubectl describe pod <pod-name> 看 Events 字段,Scheduler 的失败原因通常写得很清楚。
kubelet:reconcile 循环的执行者
kubelet 不是被 Scheduler 触发的,它是持续运行一个 reconcile 循环,定期对比”API Server 期望这台机器上运行哪些 Pod”和”这台机器上实际运行了哪些 Pod”,有差异就修正。
Pod 绑定到节点后,kubelet 的下一次 reconcile 扫描会发现这个 Pod,然后启动一系列操作:
- 准入检查:ConfigMap、Secret 是否存在,PVC 是否已挂载
- 镜像拉取:调用容器运行时(containerd)拉取镜像
- 网络设置:调用 CNI 插件为 Pod 分配 IP、配置网络命名空间
- 容器启动:按 init containers → 业务容器的顺序启动
- 健康检查:启动 liveness 和 readiness probe
这些步骤里,镜像拉取几乎永远是最大的延迟来源。
镜像拉取:生产里真正的瓶颈
一个 1GB 的镜像,在冷节点上拉取可能需要 30 秒到几分钟,取决于节点到镜像仓库的带宽。在大规模集群里,这个问题被放大:一次滚动更新触发几十个节点同时拉取同一个新镜像,镜像仓库的出口带宽会成为瓶颈。
几个实践原则:
imagePullPolicy: IfNotPresent比Always在生产环境里更合理。Always意味着每次 Pod 重建都要查询镜像仓库,即使镜像没变,也多了一次网络往返。- 合理分层。把变化频繁的应用代码放在 Dockerfile 靠后的层,把不常变的基础依赖放在前面,让节点缓存发挥作用。
- 大集群里可以考虑在每个节点或每个可用区放镜像缓存代理(如 dragonfly、harbor proxy cache),减少跨地域拉取。
CNI 和 IP 分配
容器启动后,Pod 还没有可用的网络——这是 CNI(Container Network Interface)插件的工作。kubelet 在创建容器的网络命名空间之后,会调用配置好的 CNI 插件:
- 为 Pod 分配一个集群内 IP
- 在节点上配置路由规则
- (可选)配置网络策略
CNI 插件的速度差异很大。Flannel 很快但功能简单;Calico 支持 NetworkPolicy 但每次 IP 分配需要和数据存储同步,在大集群里 IP 分配延迟可能明显。
一个容易踩的坑:Pod IP 分配完成,不代表 Pod 之间可以立即通信。在 Calico 这类通过 BGP 扩散路由信息的网络插件里,路由传播需要一点时间。新 Pod 刚起来时,其他节点上的 Pod 访问它可能会有几百毫秒到几秒的短暂失败期。
Running ≠ Ready
这是生产环境里最常见的认知盲区。Pod 状态变成 Running 意味着容器进程已经启动,不意味着业务已经准备好接流量。
readinessProbe 决定了 Pod 什么时候被加入 Service 的 Endpoints 列表。在 readinessProbe 第一次成功之前,即使 Pod 是 Running 状态,流量也不会打过来。
1 | |
initialDelaySeconds 的设置很讲究:太短,probe 在应用启动完成之前就开始检查,一直失败,Pod 迟迟不 Ready;太长,滚动更新的总耗时被不必要地拉长。正确做法是测量应用的实际启动时间,把 initialDelaySeconds 设成 P90 左右的启动耗时。
startupProbe 是 Kubernetes 1.18 引入的改进——专门给启动慢的应用用,在 startupProbe 成功之前,liveness 和 readiness probe 不会运行。这样可以给启动阶段更大的容忍度,同时不影响运行中的检测频率。
排查启动慢的实用流程
1 | |
Pod 启动慢通常不是 Kubernetes 本身的问题,而是镜像太大、应用启动逻辑有阻塞、外部依赖(数据库、配置中心)未就绪这三类中的一个。理解控制循环的运作方式,才能准确定位是哪个环节拖慢了整个过程。