谁在造 Pod,谁在挑节点
一句话总结
Deployment 不创建 Pod。它创建 ReplicaSet,ReplicaSet 创建 Pod,scheduler 选择节点,kubelet 执行。**每一层只看自己下面的一层。**理解这条链,就能按层拆分诊断 rollout 与 scheduling 问题。
为什么需要它
为什么 Deployment 不直接创建 Pod?因为要进行 rolling update。
修改镜像后,Deployment 会再创建一个新 ReplicaSet,一边增加新实例,一边减少旧实例。maxSurge 与 maxUnavailable 决定增减速度。
| 参数 | 含义 | replicas=4 时 |
|---|---|---|
maxSurge: 2 |
最多能比目标数量多启动多少 | rolling 期间最多 6 个 |
maxUnavailable: 0 |
最多允许比目标数量少多少 | 始终至少 4 个 Ready |
两者都设为 0,更新将无法移动。maxUnavailable: 0 表示无中断,但需要富余资源;maxSurge: 0 节省资源,却会短暂降低容量。
能够回滚也是因为这种结构。旧 ReplicaSet 不会被删除,而会以副本数 0 的状态保留,数量由 revisionHistoryLimit 决定。rollout undo 只是重新扩大那个旧 ReplicaSet。把该值设为 0 就无法回滚。
它如何运作
指挥 scheduler 放置工作负载主要有七种手柄。
- nodeSelector——标签完全匹配,最简单也最常用。
- nodeAffinity required——用表达式(In, NotIn, Exists, Gt, Lt)选择节点。无法匹配就 Pending。
- nodeAffinity preferred——设置 weight 后,只在 score 阶段加分。无法匹配也能放置。
- podAntiAffinity——避免在同一 topologyKey 中出现具有相同标签的 Pod,是高可用放置的基础。
- taint / toleration——节点发出的拒绝与 Pod 出示的通行证。
NoSchedule只阻止新 Pod,NoExecute还会驱逐已运行 Pod。 - PriorityClass——资源不足时决定谁优先。高优先级 Pod 可以抢占低优先级 Pod。
- topologySpreadConstraints——让各 zone/node 的 Pod 数量偏差保持在
maxSkew以下。
经常出错的一点是,topologySpread 的候选 domain 集合由通过 Pod nodeAffinity/nodeSelector 的节点构成,taint 默认却会被忽略。因此 cordon 或被 taint 的节点仍可能作为 Pod 数为 0 的 domain,扩大偏差;若设为 DoNotSchedule,其他 Pod 就会 Pending。
在实际工作中会遇到的情况
**案例 1——Pending 未必是错误。**家庭实验室刚用 kubeadm 1.34 + Cilium 重建后,在仅有一个节点时,hubble-relay 与 hubble-ui 保持 Pending。
Warning FailedScheduling 0/1 nodes are available: 1 node(s) had untolerated taint(s).
控制平面节点带有 node-role.kubernetes.io/control-plane:NoSchedule taint,hubble 组件不是 DaemonSet,而是 Deployment,因此没有容忍它。CoreDNS 则有默认 toleration,能够正常启动。同一集群、同一节点、不同结果,差别只有一个 toleration。worker 加入后问题立即消失。这不是 bug,而是正常行为。
**案例 2——一个 GPU 不等于另一个 GPU。**同一家庭实验室有 4 块 GPU:RTX 3090 24GB、RTX 5090 32GB、两块 RTX 4070 Laptop 8GB。Pod 如果只请求 nvidia.com/gpu: 1,**需要 32GB 的训练任务可能被放到 8GB 笔记本 GPU 上。**因为对 scheduler 来说,扩展资源只是数量,两者同为一个 GPU。
GPU Feature Discovery 添加的 gpu.memory 标签是字符串,无法使用“24GB 以上”这样的比较 selector。因此需要自行添加语义标签。
gpu.homelab/tier=xlarge gpu.homelab/vram=32g # 5090
gpu.homelab/tier=large gpu.homelab/vram=24g # 3090
gpu.homelab/tier=small gpu.homelab/vram=8g # 4070 Laptop x2
现在 workload 可以用 nodeSelector: {gpu.homelab/tier: xlarge} 选择自身等级。scheduling 归根结底是资源名称定义得有多准确的问题。
再举一个目标选择示例:同一集群安装 GPU Operator 后,NFD worker 出现在全部 5 个节点,其余 GPU 组件只出现在 4 个 GPU 节点,因为其他 DaemonSet 会通过 nodeSelector 查看 NFD 添加的标签。
后续实验要做什么
第一个实验将创建并扩缩 Deployment,调整 rolling 参数后实际 rollout 和回滚,还会创建 DaemonSet 与 StatefulSet。第二个实验会依次使用从 nodeSelector 到 topologySpreadConstraints 的七种手柄。最后一题只有记住前面被 cordon 和 taint 的节点状态才能解决。