1 K8s 调度要回答什么
用户点击“提交订单”时,希望接口及时返回订单编号。团队把订单 API 放进 Kubernetes,简称 K8s,并声明需要 3 个副本。这里的副本通常由 Deployment 管理,每个副本运行在 1 个 Pod 中。Pod 是调度的基本单位,可以包含需要共同运行的容器。集群里有多个节点,调度要回答的直接问题是:这个尚未分配节点的 Pod,现在可以放在哪里,放在哪里更合适?
“有服务器”并不能直接推出“有地方放”。订单 API 可能要求节点具有指定标签,不能进入维护中的节点,还需要足够的 CPU 和内存请求额度。若使用带有区域限制的数据卷,节点位置也会受影响。调度器需要把这些条件放在同一个决策里,找出满足要求的节点。即使集群总资源看上去很多,符合全部条件的节点集合仍可能为空。
部署、身份、网络、数据和可观测性在这里交汇。Deployment 声明应用数量;身份与权限决定谁能修改工作负载和节点配置;网络让请求抵达就绪的副本;存储决定数据能否在目标节点使用;事件和指标帮助解释为什么没有得到期望结果。调度只负责其中的节点选择阶段,不能单独保证整条服务链成功。

选定节点后,还需要该节点上的 kubelet 准备容器运行环境、拉取镜像、挂载卷并启动容器。应用通过就绪检查后,才能成为 Service 可用端点的候选。因此“Pod 已绑定节点”“容器正在运行”“订单接口能够正常服务”是不同证据。看见调度成功就结束排查,可能遗漏镜像、网络或数据依赖的问题。
本课把注意力放在可解释的决策上:先找硬条件,再看偏好;先确认是否已经绑定,再读对应事件;最后把约束折算成真正可用的容量。暂时不展开调度插件的内部实现,也不把调度器理解成持续搬动所有 Pod 的自动均衡器。通常,已经运行的 Pod 不会仅仅因为别处出现了更优节点就自动迁移。
2 过滤、打分与绑定期望
理解调度可以从“过滤、打分、绑定”入手。过滤检查不能违背的条件:节点资源是否能容纳请求,标签是否符合要求,是否存在无法容忍的污点,存储和分布约束是否满足。任何必要条件失败,该节点就不能成为本次落点。打分发生在可行节点之间,用来比较资源分布、位置偏好等因素;高分不能让已经被硬条件排除的节点重新合格。
假设订单 API 的每个 Pod 请求 CPU 500m、内存 512Mi。CPU 1000m 表示 1 个 CPU 的请求量,500m 表示其一半。节点 A 只有 300m 可供新的请求使用,节点 B、C 各有 1000m,内存也充足。在不考虑其他限制的简化例子中,A 被过滤,B、C 继续竞争。如果 Pod 偏好 B 所在区域,这项偏好可以影响分数,但真实选择还受其他评分因素影响。

这里尤其要分清 requests 和实际使用率。requests 是应用声明的资源需求,也是调度进行资源容纳判断的重要依据。节点监控显示 CPU 当前只有 20% 使用率,不代表还能接受任意数量的请求;已有 Pod 声明的请求可能已占满可分配额度。反过来,实际负载突然上升,也不意味着调度器会立即把已经绑定的 Pod 搬走。资源限制 limits 对容器运行期同样重要,但不能用它替代对 requests 的理解。
节点的可分配资源也小于或等于物理容量,因为系统和节点组件需要保留资源。排查时应看 allocatable,并结合已有 Pod 的请求评估剩余额度。还要注意,CPU 和内存必须在同一个节点上同时满足。节点 A 剩 CPU、节点 B 剩内存,不能把两者拼成 1 个 Pod 的落点。这就是总量充足而局部无法容纳的常见来源。
绑定把选出的节点归属记录到 Pod 上。它表示调度决策完成到相应阶段,不是应用可用承诺。集群状态不断变化,选择过程中资源或条件发生变化时,绑定相关操作也可能失败并重新评估。若没有可行节点,Pod 会继续等待,调度器记录原因并在相关变化后重试。理解这个循环后,就能把“为什么还没运行”拆成“没有可行落点”和“已有落点但启动未完成”来调查。
3 污点、容忍与亲和边界
污点写在节点上,用来表达“哪些 Pod 不应轻易进入这里”。容忍写在 Pod 上,表示它能够接受某类污点。对于 NoSchedule 效果的污点,没有匹配容忍的新 Pod 不能调度到该节点;已有 Pod 通常不会仅因新增这种污点被驱逐。NoExecute 则还涉及已有 Pod 的驱逐,匹配容忍及其容忍时间会影响结果。维护时不能把这 2 种效果混用。
容忍的含义是通过相应检查,不是指定目的地。给订单 API 加上专用节点污点的容忍,只能说明这些节点不再因该污点排除它;它仍可能落在其他合格节点。如果要求必须进入专用节点池,还要配合可信标签和节点选择条件。反方向也成立:节点标签符合要求,却存在 Pod 无法容忍的污点,仍然无法调度。各项硬条件需要同时满足。
亲和描述希望靠近什么。节点亲和根据节点标签选择位置,Pod 亲和或反亲和根据其他 Pod 的位置表达靠近或分离。入门时先抓住必需和偏好:必需规则决定是否合格,偏好规则帮助比较。规则名中的 requiredDuringSchedulingIgnoredDuringExecution,表示调度时必须满足,运行后标签变化本身通常不会触发迁移。不要由“忽略运行期变化”推断其他驱逐机制也失效。

例如给节点加上 workload=orders 标签,再要求订单 API 必须匹配这个标签,可以缩小候选范围。若只是希望优先靠近某区域,应考虑偏好是否足够。把每个喜好都写成必需条件,会把维护或故障时的退路逐步删掉。标签名也需要治理:若应用使用 team=orders,节点实际使用 workload=orders,业务含义相近并不能让调度器视为匹配。
希望副本分散时,可以使用 Pod 反亲和或拓扑分布约束;后者能按主机、可用区等拓扑域表达分布要求。本课不展开所有字段,只强调必须说明分布单位和不能满足时的行为。按主机分散可降低单节点故障影响,却不一定能抵御整个可用区故障。约束涉及的 Pod 选择器、命名空间和节点拓扑标签也要正确,否则你以为在分散的副本,可能根本没被同一条规则识别。
4 动手:解释一次 Pending 的原因链
现在观察订单 API 的发布。旧版本有 2 个副本就绪,新版本 Pod 长时间显示 Pending。第一步不是修改调度配置,而是确认对象和时间范围。在练习命名空间中执行下列只读命令,先找到实际 Pod 名,再替换后续示例中的占位符。没有集群时,也可以沿交互中的简化输出判断每一步需要什么证据。
kubectl get pods -n retail -l app=orders -o wide
kubectl describe pod <pod-name> -n retail
kubectl get events -n retail --sort-by=.metadata.creationTimestamp
先看 NODE 是否为 <none>,再在 describe 输出中找 PodScheduled 条件和 Events。若尚未绑定,并出现 FailedScheduling,继续调查调度条件;若已有节点归属,Pending 也可能来自镜像拉取、卷挂载或容器创建等阶段。phase 是概括状态,不能单凭 Pending 断定 CPU 不够。事件还可能聚合多次尝试,需要结合最近发生时间和当前配置阅读。
假设事件摘要表示:3 个节点中,2 个因内存请求不足被排除,1 个存在未容忍污点。这段话说明当前没有可行节点,但不能解释为扩容任意节点都能解决。新增节点若复制了同样的污点,也可能无法接收 Pod。事件原因是诊断入口,同一个节点也可能不满足多项条件,不要机械地把所有原因计数相加当作节点总数。
kubectl get nodes --show-labels
kubectl describe node <node-name>
kubectl get pod <pod-name> -n retail -o yaml
节点输出用于核对实际标签、污点、可分配资源和已分配请求;Pod 输出用于核对实际生效的 requests、容忍与亲和。重点是线上对象,因为模板经过默认值填充或准入处理后可能与手头文件不同。若有多个普通容器,资源评估需要考虑它们的组合;初始化容器等情况还有专门规则,不要只读某个业务容器的请求就下结论。

把结果写成原因链:“新 Pod 未绑定;最近调度事件指出资源与污点限制;候选节点核对结果与事件一致;当前符合全部条件的节点数为 0。”接着选择最小变更:增加满足条件的容量,修正误写的标签,或在确认维护结束后清理遗留污点。降低 requests 应以真实负载数据为依据,否则只是把等待问题转化为运行期争用。
变更后重新观察节点归属、调度事件、容器状态、就绪副本和接口错误率。成功标准需要覆盖用户体验:Pod 绑定了节点却始终未就绪,不能算服务恢复。若事件仍存在,先确认它是历史记录还是新的失败,再判断是否需要继续处理。保留变更前后证据,后续才能解释这次修复为什么有效。
5 抢占、优先级与公平取舍
资源紧张时,业务之间可能需要有明确先后。例如订单 API 直接承接用户请求,离线报表可以延后。PriorityClass 用来给 Pod 声明优先级,调度器会考虑优先级安排待调度工作。在允许抢占的情况下,较高优先级 Pod 无法调度时,调度器还可能评估移除部分较低优先级 Pod,是否能为它创造落点。抢占是有代价的恢复手段。
抢占能改变某些资源占用,却不能凭空创造节点标签、可用区或合适的卷。如果订单 API 必须进入 zone-a,而该区域没有合格节点,移除 zone-b 上的低优先级 Pod 并无帮助。若瓶颈是未容忍污点,腾出 CPU 也不能自动让条件成立。因此看到“抢占无帮助”的信息,应回到过滤条件,而不是继续调高优先级。

即使可能抢占,恢复也不是瞬间完成。被选中的 Pod 需要退出,节点资源需要释放,新 Pod 还要重新获得可行落点并启动。候选节点提示也不等于最终绑定承诺。PodDisruptionBudget 用于表达副本可用性期望,但调度抢占对它的考虑属于尽力而为,不能把它当作绝对不会被突破的保证。保护关键业务仍需要适当容量和明确操作边界。
优先级也不等于公平。若所有团队都把 Pod 标为最高优先级,排序就失去业务含义;若高优先级流量长期占满资源,低优先级工作可能持续等待。可以使用 ResourceQuota 控制命名空间资源预算,用 LimitRange 辅助约束默认请求与范围,并限制谁能使用关键优先级。但这些机制本身不构成完整的公平调度系统,需要配合容量安排和业务准入规则。
对于可等待但希望排队靠前的任务,可以评估非抢占优先级,即保留队列优先顺序但不主动抢占较低优先级 Pod。无论选择哪种方式,方案都要写明谁会受影响、允许等待多久、何时补充容量。判断标准不能只有“关键 Pod 最终跑起来”,还应包括报表积压、重试负载和其他服务的可用副本是否处于可接受范围。
6 常见坑:过度亲和制造单点
订单 API 曾经访问同区域数据库更快,团队于是把“偏好 zone-a”改成“必须 zone-a”,又要求节点具有某个机型标签,最后加上“必须与缓存 Pod 同节点”。每项规则单看都有理由,组合后却可能只剩 1 个节点。此时部署 3 个副本也未必获得有效冗余;若还要求副本不能同节点,甚至只能运行其中 1 个。副本数量和可用故障域数量必须一起看。
这种问题常出现在把性能优化直接升级为可用性硬条件时。靠近缓存可能节省网络往返,但缓存本来支持跨节点访问,就不一定需要强制共址。指定机型也许只是早期测试习惯,而不是应用功能要求。逐条询问“违反这条规则会导致功能错误,还是仅影响成本或延迟”,有助于判断它应该是必需还是偏好。
另一种陷阱是硬性要求每节点最多 1 个副本,却按资源总量估计扩容能力。假设有 3 个合格节点,每个可按资源容纳 4 个订单 Pod。若硬性反亲和要求它们不能同节点,最多仍只能放置 3 个。把副本数从 3 调到 6 不会自动获得 6 个落点。此时可以增加合格节点,或在接受同节点故障影响的前提下调整分布要求。
放宽成偏好后,允许共址并不代表调度器保证平均分配;实际结果还受资源和其他评分影响。若需要明确的分布目标,应评估合适的拓扑分布约束,并测试节点短缺时的行为。也不要把“每节点最多 1 个”说成“每区域最多 1 个”:主机分散与区域分散抵御的故障不同,后者还会牵涉跨区网络和数据访问条件。
避免单点的方法是定期列出约束的交集,并模拟失去 1 个节点或 1 个区域后还剩哪些可行落点。维护、发布和自动扩容也要纳入:新节点的标签与污点是否正确,旧节点排空时替代副本能否调度,发布额外副本是否被分布规则挡住。正常状态能运行只是起点,故障和变更状态仍能满足最低服务能力,才说明约束与容量相容。
7 案例:污点遗留导致大面积 Pending
某次节点池维护前,运维给 6 个节点添加 maintenance=planned:NoSchedule,阻止新的普通业务 Pod 进入。维护完成后,节点恢复健康,但结束步骤遗漏了移除污点。已有订单 API Pod 继续运行,因此首页监控暂时正常。随后滚动发布创建替代副本,多个业务的新 Pod 陆续 Pending,团队最初把它当成突发资源不足。
这个时间差是关键。NoSchedule 主要阻挡新调度,不会仅因添加该污点就把已有普通 Pod 全部赶走,因此故障会在发布、扩容或节点替换时暴露。监控里节点 CPU 利用率不高,也无法证明能够接纳新副本。节点健康、资源有余量、允许目标 Pod 进入,是需要分别检查的条件。
值班人员先确认订单 API 的新 Pod 没有节点归属,再读取 FailedScheduling 事件。事件指向未容忍污点;节点列表显示目标池的 6 个节点都带有维护标记,而应用模板没有对应容忍。维护记录显示工作已结束,节点健康检查与应用基础连通性检查通过,这才支持“隔离条件已经过期”的判断。只看到污点名字像维护用途,还不足以直接清除。

团队没有给所有业务加上匹配任意污点的宽泛容忍,而是在确认范围后移除失效的维护污点。若维护尚未完成,正确方向应是保留隔离并提供其他合格容量,或恢复维护中的节点能力。为了赶进度让普通业务容忍所有污点,可能会把它们送到专用、故障或其他不适合运行的位置,掩盖真正的配置问题。
恢复验证同时看待调度 Pod 数量、绑定速度、就绪副本、订单成功率和副本分布。积压的 Pod 获得节点后,镜像拉取与应用启动还需要时间,不应只用 Pending 数下降宣布结束。复盘则把维护污点加入变更清单:记录添加原因、负责人、预期撤销条件;维护关闭前检查实际节点状态;对长时间遗留的维护标记和未绑定 Pod 设置告警。这样修复的是重复发生的条件。
8 把调度约束写进容量方案
容量方案需要从业务承诺走向可行落点。先估计订单 API 在目标请求量下需要多少副本,再根据实际负载与测量结果确定 requests。随后列出硬条件:可用区、节点标签、污点容忍、卷位置和副本分布规则。集群总 CPU 是背景信息,满足这些条件的节点才是这个服务的有效容量。不同工作负载即使共用集群,其有效容量集合也可能不同。
例如正常运行需要 3 个副本,每个请求 CPU 500m、内存 512Mi,并要求硬性按主机分散。方案不能只写“还有 CPU 2000m”。它应说明至少有 3 个合格主机可承载正常副本;若要求失去 1 个主机后仍恢复到 3 个副本,就需要额外可行主机及其资源。还要检查滚动发布允许的额外副本,新旧版本选择器与分布规则是否会让发布等待。

有效容量也有时间维度。自动伸缩节点需要等待创建、启动和加入集群;新节点还要携带正确标签、污点和存储条件。即便采用基于待调度需求的节点伸缩,也必须核对目标节点池模板能否满足 Pod。某些无法满足的硬约束不会因为增加普通节点而消失。业务允许的恢复时间若短于扩容时间,就需要提前保留合格余量。
数据与网络条件要进入同一张容量表。有些持久卷只能在特定区域使用;采用等待首次消费者的卷绑定模式时,卷选择与 Pod 调度还会协调。无需在本课深入存储插件实现,但应明确:给 Pod 增加跨区落点,不等于原有数据自动可以跨区挂载。网络访问策略、外部依赖延迟和应用就绪条件则决定放置成功后能否真正服务。
最后为每条约束安排负责人、证据和调整条件。硬性区域约束要对应数据或合规依据,偏好规则要对应可测量的性能收益,专用节点污点要对应明确工作负载范围。容量评审时用故障、维护、发布 3 类场景检验,并约定 Pending 持续时间、可用副本和扩容延迟的观察方式。调度配置由此成为可讨论的业务取舍,而不是只能照抄的 YAML。
9 从K8s 调度走向K8s 安全(C33)
调度约束依赖可信的配置。若订单 API 必须进入专用节点,节点标签由谁修改、Pod 容忍由谁填写、关键 PriorityClass 由谁使用,都会影响隔离效果。拥有创建工作负载的权限并不等于应该能够使用所有节点池或抢占等级。下一课讨论的身份、授权与安全策略,需要把这些控制入口保护起来,让规则对应真实的权限边界。
污点与容忍尤其容易被误读成安全屏障。它们能表达调度意图,但如果调用者可以自由给 Pod 添加容忍,就可能绕过这层放置限制。节点亲和也不会自动限制容器权限或网络访问。保护订单数据还需要恰当的服务身份、最小权限、网络控制和数据访问授权;“运行在专用节点”只是部署条件中的一部分。
反过来,安全规则也会改变有效容量。如果只有满足某项受控标签的节点可以运行支付相关组件,那么这些节点的维护和扩容就必须与安全要求一起安排。随意放宽标签条件可能恢复了调度,却违背原本隔离承诺。更稳妥的处理是先确认约束的依据,再补充符合要求的容量,或按明确的风险接受流程调整规则。
例如支付组件要求节点带有受控的隔离标签。如果节点自身也能任意声明这个标签,节点失陷后就可能伪装成合格落点。启用 Node 授权与 NodeRestriction 准入插件,并使用其保护的标签键范围,可以限制 kubelet 修改这些标签;普通的 workload=payments 标签并不会自动获得这种保护。平台还需要限制其他身份修改节点的权限,并由可信管理流程核验节点后授予标签。调度器只读取标签作判断,不会替平台验证标签背后的隔离事实。
Pod 提交入口同样需要检查。假设共享命名空间的用户只能部署普通订单组件,却能在模板里任意填写 tolerations 和 nodeAffinity,他们就可能同时容忍支付池污点并匹配支付池标签。命名空间本身不会阻止这种放置。可以通过准入策略校验允许的容忍键、节点选择范围和 PriorityClass,并覆盖 Deployment 等控制器最终创建的 Pod。验收时分别提交普通模板和越权模板,要求前者能进入批准的节点池,后者在准入阶段被拒绝,并保留拒绝原因与审计记录。
还应覆盖直接指定 spec.nodeName 的路径:这个字段会绕过常规调度器选择节点,单靠 NoSchedule 污点不能阻止这种指定。若方案依赖统一调度策略,应限制未经授权的 nodeName 与自定义 schedulerName,并检查相关绑定权限。否则只测试正常 Deployment 发布,可能遗漏绕过路径。这里的失败模式不是调度器算错,而是工作负载没有经过方案假定的决策入口。
进入 C33 时,可以带着这组问题继续:谁能提交 Pod,谁能修改节点标签,谁能授予宽泛容忍,谁能读取业务凭据,哪些动作会留下审计证据?本课建立的是“Pod 为什么能去某个节点”的解释框架;安全课将继续追问“谁被允许作出这些声明,以及工作负载落地后能做什么”。这使部署位置、访问能力和责任归属能够连起来讨论。
放进方案里
在订单服务方案中,先写服务目标与副本基线:正常运行需要 3 个就绪副本,每个请求 CPU 500m、内存 512Mi;允许单节点故障后短暂降低副本数,但应在约定恢复时间内补齐。请求值应附压测和运行数据来源,不能仅凭调度方便决定。再写可行范围:允许的区域、节点标签、存储位置、污点容忍及其业务依据,区分必须满足的条件与可以让步的偏好。
接着写分布和余量。若硬性要求按主机分散,就按合格主机数量核算正常运行、单节点故障和滚动发布期间的落点;明确发布额外副本需要的资源。若采用偏好分散,应说明同节点共存时可接受的故障影响。使用节点自动伸缩时,记录节点池模板、预计加入时间和扩容期间保留的余量,并确认新增节点能满足同样的标签、污点和数据条件。
再写竞争规则:订单 API 与可延后的报表分别使用什么优先级,是否允许抢占,谁能授予关键级别,被影响工作如何恢复。用资源配额限制无边界申请,同时承认配额不能代替可行节点容量。维护污点要记录负责人、用途与撤销条件,禁止为了消除 Pending 给所有业务添加宽泛容忍。
最后写观察与验收。发现 Pending 后,先核对节点归属和最近调度事件,再对照资源请求、标签、污点及卷约束;已有节点的 Pod 沿启动链调查。变更需保存原因链与回退方式,验收同时覆盖绑定结果、就绪副本、订单错误率和分布情况。把安全责任也落到人:谁能改标签、容忍和优先级,哪些修改需要审计。评审由此能够判断容量是否可用、规则为何存在以及故障时如何恢复。
10 小结:下一步K8s 安全(C33)
K8s 调度先确定可行节点,再比较偏好并完成绑定。资源请求、污点、亲和、存储和分布条件共同决定落点;绑定之后,应用仍需要完成启动和就绪过程。排查 Pending 的起点是节点归属,容量规划的起点是约束交集,优先级与抢占则需要明确受益者和代价承担者。
用 3 个问题检查理解:节点 CPU 使用率很低,为什么 Pod 仍可能无法调度?Pod 容忍了专用节点污点,为什么不保证进入专用池?每节点只能放 1 个副本时,把副本数调高为什么可能无效?回答时分别指出资源请求与硬条件、容忍的许可边界、合格节点数量,便抓住了本课主线。
下一步进入 K8s 安全(C33),继续检查这些声明由谁控制,以及容器运行后拥有什么身份、权限和访问能力。