题干与适用场景
集群节点启动后,kubelet 可能已经报告 Ready,但 GPU 驱动、CNI、存储插件或本地代理尚未可用。若调度器过早放置 Pod,工作负载会进入反复失败或隐性降级。请设计一个 Node Readiness Controller:根据节点条件声明额外前置要求,在满足要求前阻止调度,并支持节点生命周期中的持续失效。
这道题适合平台工程、SRE 和 Kubernetes 控制器岗位。公开的 Kubernetes 面试资料把节点 NotReady、污点/容忍和 Pending Pod 排查列为场景题;官方 Node Readiness Controller 项目则将 NodeReadinessRule、自动污点管理、bootstrap-only/continuous 模式和 dry run 定义为当前实现方向。本文讨论的是一套可审计的设计推导,不声称它是公司真题。
面试官考察点
面试官关注你能否把“节点 Ready”与“节点适合某类工作负载”分开。强回答会定义条件来源、规则作用域、污点幂等、控制器重启恢复、状态可观测性和误封锁保护。
普通回答只会写一个 DaemonSet 检查脚本。强回答会解释为什么条件报告与决策控制解耦,如何区分 bootstrap-only 和 continuous,如何在 dry run 中估算影响,并处理条件过期、控制器分区和多个规则互相冲突的情况。
回答前需要澄清的问题
- 目标是只保护新节点启动,还是节点运行中驱动失效也要停止新调度?这决定 enforcement mode 和恢复动作。
- 条件由谁产生:Node Problem Detector、设备插件、CNI Agent 还是自定义 DaemonSet?来源不可信时需要身份与时间戳校验。
- 规则按全部条件 AND,还是允许任一条件通过?GPU、网络和存储通常需要不同的节点池与规则。
- 误判时是阻止新 Pod,还是驱逐已有 Pod?前者可回滚,后者需要更高证据和独立的驱逐策略。
30 秒回答框架
“我会把节点就绪拆成条件报告、规则评估和污点执行三层。NodeReadinessRule 选择节点并声明必须为 True 的条件,控制器根据 bootstrap-only 或 continuous 模式添加或移除 NoSchedule 污点。所有写入都要幂等、可观测并支持 dry run;规则和条件状态持久化在 API Server,重启后可重建。发布时先只记录影响,再小范围启用;故障时停止写入或回滚规则,避免把一个控制器问题扩散成全局不可调度。”
分步骤深入解答
先定义边界。控制器不主动探测 GPU 或网络,它消费 Node Condition;现有 Node Problem Detector、设备插件或自定义 Agent 负责报告事实。这样可以复用已有探针,也能把“检查失败”和“是否允许调度”分开。条件至少包含类型、状态、更新时间、来源和观测代次,过期条件不能继续授权。
规则模型包含节点选择器、条件集合、目标污点和 enforcement mode。官方项目要求所有指定条件满足时才解除污点,并支持按节点标签选择异构节点。bootstrap-only 在初始化条件满足后停止针对该规则的再评估;continuous 则在运行期间任一关键条件转为 False 时重新加上污点。前者适合预拉镜像或硬件初始化,后者适合持续依赖的设备或网络健康。
控制循环读取规则、节点和条件,计算期望污点,再用资源版本执行条件更新。写入必须使用幂等键和冲突重试:只管理自己声明的污点,不删除其他控制器或管理员添加的污点。多个规则若写入相同污点键但语义不同,应由准入校验拒绝;否则一个规则恢复会错误解除另一个规则的保护。
故障路径需要明确。条件报告者停止工作时,默认拒绝还是默认允许是产品选择:关键安全依赖可采用 fail-closed,低风险初始化可采用带过期时间的 fail-open。控制器失联时不应自动清空既有污点;恢复后重新对账。节点被删除或标签变化时,规则状态要标记为不适用,避免旧状态阻塞新节点。
可观测性至少包含每条规则的匹配节点数、条件缺失/过期数、期望与实际污点差异、从条件变为 True 到可调度的延迟,以及被 gate 的 Pod 数。状态要给出失败条件和最后一次评估时间,事件中避免打印凭证或设备敏感信息。控制面负载可按节点和规则建立队列,合并短时间内的条件变化,避免每个心跳都写 API Server。
发布策略从 dry run 开始。官方项目的 dry run 只记录计划动作并更新规则状态,不实际添加污点;通过观察受影响节点与 Pending Pod 数量后,再对一个节点池启用。回滚顺序是暂停新规则评估、保留现有保护污点、修复条件来源,最后按规则重新对账;不能直接批量删除所有 NoSchedule 污点。
替代方案是给每个工作负载加 nodeSelector、使用 Pod schedulingGates,或由启动脚本手工加污点。工作负载选择器适合少量明确消费者,但难以覆盖所有未来 Pod;Pod gate 保护的是 Pod,而本题要保护节点;手工脚本缺乏统一状态与恢复。控制器适合异构节点和多团队共用集群,代价是引入 CRD、控制循环和新的故障域。
高质量示范回答
我会把它设计成一个只负责“是否允许新调度”的声明式控制器。条件由 Node Problem Detector、设备插件或 CNI Agent 写入节点状态,控制器不重复探测。规则选择节点,列出必须为 True 的条件、目标 NoSchedule 污点和模式。启动阶段用 bootstrap-only,例如 GPU 驱动和网络代理都准备好后解除污点;持续依赖用 continuous,条件变坏就重新加污点,但不自动驱逐已有 Pod。
控制器只管理自己带 owner 标识的污点,使用资源版本和冲突重试保证幂等;同一污点键的冲突规则由校验拒绝。规则状态显示匹配节点、缺失或过期条件、实际污点差异和最后评估时间。部署先 dry run,比较预计受影响节点与 Pending Pod,再对一个节点池灰度。控制器失联时保留已有保护,恢复后重新对账;回滚暂停评估并修复条件来源,避免用一次命令清空全 cluster 的污点。
常见错误
- 错误表现 → 让控制器自己 SSH 节点检测所有组件;失败原因 → 失去 Kubernetes 条件生态与统一权限边界;修正方法 → 让专用 Agent 报告条件,控制器只负责规则评估和污点。
- 错误表现 → 用
continuous处理一次性初始化;失败原因 → 初始化完成后仍持续抖动,造成不必要的不可调度;修正方法 → 明确选择bootstrap-only,记录完成状态。 - 错误表现 → 看到规则就删除同键污点;失败原因 → 可能删除管理员或其他控制器的安全保护;修正方法 → 使用 owner 标记、冲突校验和只更新自有字段。
- 错误表现 → 控制器重启后清空全部污点;失败原因 → 故障窗口会把工作负载放到未就绪节点;修正方法 → 保留现状,恢复后执行有版本控制的对账。
追问及应对
如果条件报告者停止更新,你选择 fail-open 还是 fail-closed?
按依赖风险分级。GPU 驱动、加密模块或跨区域网络等关键依赖采用带过期时间的 fail-closed,宁可暂时少调度;低风险的一次性初始化可在已有成功记录和短 TTL 下 fail-open。无论选择哪种,都要把“条件未知”与“条件为 False”分开计量,避免把监控缺失伪装成健康。
如果一个节点同时匹配两个规则,而两个规则使用相同污点键怎么办?
在准入或规则编译阶段拒绝语义冲突,要求不同规则使用不同键,或明确一个聚合规则拥有该键。控制器保存每个规则的期望状态,只有所有拥有者都满足解除条件时才允许移除聚合污点;不能由最后一次成功评估的规则单独删除它。
如何证明 dry run 没有把集群容量打穿?
把规则匹配节点、现有可调度容量、Pod 资源请求和拓扑分布放到同一份影响报告。先在一个节点池模拟,观察被 gate 的 Pod、自动扩容队列和调度延迟,再逐步扩大。若报告显示关键工作负载没有可用余量,先调整节点池或规则,不进入强制模式。
规则要持续监控节点时,为什么不直接驱逐已有 Pod?
节点不可接收新 Pod 与已有 Pod 是否安全继续运行是两个判断。NoSchedule 只阻止新增放置,保留业务连续性;驱逐需要独立的 PDB、优雅终止和数据安全策略。只有条件明确表示现有工作负载也不安全时,才由专门的驱逐控制器在另一套门槛下处理。