1. 调度系统深度拆解:Kubernetes 资源分配的高阶玩法
在 Kubernetes 面试里,最容易被问懵的一类问题就是调度。因为调度不只是“把 Pod 放到某个节点上”这么简单,它涉及决策链路、扩展机制、真实环境里的资源碎片问题。我自己在实际排查中就遇到过节点明明有空余资源,Pod 却迟迟调度不上去的情况,后来一步步追下去,才把整套调度逻辑吃透。这篇博文就把这些高频话题串起来,从实现原理到答题角度都走一遍。
1.1 从 kube-scheduler 到 Scheduling Framework:调度流程到底长什么样
先说结论:调度器本身是一个独立组件,它不负责真正把 Pod 跑到节点上,只负责“做决定”。底层机制是拿 Pod 的 spec 去匹配节点,匹配完通过 API Server 写一个 binding 对象,剩下的事交给 kubelet 去执行。
在 v1.26 时代,调度流程已经高度插件化。整个决策过程可以拆成三阶段:调度周期、绑定周期,加上扩展点(Extension Points)提供的拦截机会。
- Filter 阶段:先筛掉不满足硬性条件的节点,比如端口冲突、资源不足、污点不容忍;
- Score 阶段:对剩下来的节点评分,从 0 到 100,靠插件维度累计(比如节点资源均衡、亲和性匹配);
- Bind 阶段:选择分数最高的节点,生成绑定记录。
真正要理解的不只是这三个词,而是扩展点允许你插一脚的地方。比如很多公司会做自定义调度器,就是在 Filter 前面插一个 PreFilter,或者在 Score 之后插一个 PostFilter。还有一个很实用的点,调度器的打分不是“全局最优”,而是“够用就好”——只要某个节点得分最高,Pod 就会被绑过去,不会做二次抢占。
这里插一个我自己的体会:调试调度问题时,kube-scheduler 的日志会给出“0/5 nodes are available”这类信息,但千万别只看字面意思。它后面通常会跟着一串 reason,比如 Insufficient cpu、NodeUnschedulable、MatchNodeSelector,这些才是根因。你真正要修的,往往不是调度器本身,而是节点状态、亲和性规则或资源请求值。
1.2 高级调度策略实战:亲和性、污点、容忍与拓扑分布
面试里最常见的延伸题,就是“你如何保证 Pod 不被调度到某个节点?”很多人第一反应是 nodeSelector,但那是第一代玩法,现在真正的答案是污点和容忍(Taint / Toleration)。
污点的意思是给节点打个标记,默认拒绝不带容忍的 Pod 调度上来。比如标注 node-role.kubernetes.io/control-plane:NoSchedule 就会把控制面节点隔离出来。要指定某个 Pod 依然可以上去,就在 Pod spec 里添加对应的 tolerations,key、effect 完全匹配才算生效。
而亲和性解决的是另一类问题:主动把 Pod 调度到某个节点组。nodeAffinity 分为两种模式,requiredDuringSchedulingIgnoredDuringExecution(硬需求)和 preferredDuringSchedulingIgnoredDuringExecution(软偏好)。硬需求不满足就调度不了,软偏好只影响打分,绑不上也无所谓。
有一个高频面试题就是“如何保证一个工作负载的两个副本分布在两个可用区”。答案是用拓扑分布约束(Pod Topology Spread Constraints)。它的控制思路是:以某个维度(比如 zone)为单位,计算每个维度上的 Pod 数,调度时优先让新 Pod 落到副本数少的维度。注意这个约束要求你提前给节点打好 region/zone 标签,否则约束不会生效。
1.3 调度器源码观察与调优思路
真正高级的玩法不是只会用这些声明式字段,而是能读懂调度器的行为。比如你可以自己起一个 kube-scheduler 实例,利用 --config 指定 profile,把默认插件开关调整掉,甚至注册一个自己的插件。这套机制在 Scheduling Framework 设计里是官方支持的自定义入口。
此外,调度还有一个被忽略的性能隐患:默认调度器是串行做过滤和打分的,Pod 数量上来以后延迟会明显。解决办法一般有两种思路,一是开 --kube-api-qps 缓解 API 请求压力,二是用 PendingPod 监控找出真正的瓶颈调度器指标。生产中如果出现“节点资源充足但调度很慢”,优先怀疑对象是 scheduler 在抢占队列里有大量积压的 unschedulable pods。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 控制器模式与声明式 API:Kubernetes 的"大脑"是怎么工作的
很多人学 Kubernetes 卡在“控制器”这个概念上,因为它在传统运维里没有对应物。用它来类比:控制器就像一个自动巡航系统,你设定好目标状态,系统持续探测当前状态,一发现偏差就拨正方向。Kubernetes 里几乎所有核心组件——Deployment、StatefulSet、DaemonSet、Node 控制器——都是这个思路。
2.1 Reconcile Loop(调谐循环)原理与并发模型
面试必问的一个题就是“Reconcile Loop 是什么”。标准答法很简单:控制器通过一种持续循环的方式,读取期望状态和实际状态,然后执行一系列操作让实际状态尽量逼近期望状态,循环往复。
但深度理解需要落到实现层面。控制器通过 List-Watch API 监听资源变化,比如 Deployment 控制器发现 ReplicaSet 数量不对,就会调谐去创建或删除。调谐的核心设计要点有三个:
- 它是异步的,不是每来一个事件就同步处理一次;
- 它是不保证顺序的,事件丢失也没关系,因为下一轮全量对比会纠正回来;
- 它是以最终一致性为目标的,短暂的不一致是可容忍的。
实际调谐循环里还有一个重要机制:控制器会维护一个本地缓存(informer cache),避免每次调谐都查 API Server。这个缓存与 etcd 之间的同步就靠 watch 事件驱动。如果你自己写 Operator,一定要同样用 client-go 的 informer 机制,不要直接裸 LIST API。
2.2 List-Watch 与 Informer 机制:为什么不用轮询
如果直接面对这个问题:“为什么 event 系统用的是长连接 watch 而不是轮询?”最佳切入点有三个:
第一,轮询对 API Server 的压力大。假如有 1000 个控制器,每个都每 10 秒拉一次全量数据,瞬间的 QPS 会把 etcd 打爆。
第二,watch 是增量推送,事件一到就发给 client,延迟低了很多。
第三,watch 天然具备语义:你可以知道一个对象是 Added 还是 Updated 还是 Deleted,而轮询只能猜测。
但 watch 的重连也是成本,所以 informer 内部会做 relist(重新列取全部对象)来兜底。这个时机如果设计和处理不好,会出现 watch 断开后数据陈旧的坑。我自己写自定义控制器时遇到过一次,现象是资源删除后控制器没反应,最后发现是 watch 事件断了,但 informer 还一直用缓存数据,controller 没有做重新同步(resync)的兜底处理。
2.3 Operator / CRD:从扩展视角理解 Kubernetes
面试里追问“如何给 Kubernetes 添加一个自定义资源”时,就是在考 CRD + Operator 的组合拳。CRD 只定义数据模型,它不产生行为。要让一个新资源真正被“协调”,还要写一个 controller 去 watch 它。
常见误区是把 CRD 直接当成 API 来用。实际上,CRD 只是完成了 API 对象的存储和结构校验部分。要做像样点的能力,必须把下面几件事想清楚:
- 自定义资源的 status 条件该怎么设计;
- controller 怎么处理资源删除时的清理逻辑;
- 要不要引入 finalizer(终结器)来保护正在被删除的资源。
推荐的做法是参考社区成熟的 Operator 框架(比如 kubebuilder / operator-sdk),它们把生成脚手架、webhook 注册、RBAC 规则都帮你铺好了,剩下的事专注于业务调谐逻辑。即使你不打算写 Operator,理解这套扩展路径也能让你在面试里谈 K8s 的“平台化能力”时有层次感。
2.4 持久化存储背后的控制器链条
StorageClass、PVC、PV 之间的关系也属于控制器模式的高频延伸考点。PV 是独立于 Node 的资源,PVC 是用户声明需求,StorageClass 是动态供应模板。核心链路是:用户创建 PVC,PV Controller 看到 PVC 未绑定且有 StorageClass,就会去调用相应 provisioner 插件创建 PV,创建完再绑定。删除 PVC 时还会进入回收或者删除策略的判定。
面试里最容易卡壳的是回收策略。不少人分不清 Retain、Delete、Recycle 三者的语义。Retain 就是 PV 删了,但底层存储数据保留,管理员手动清理;Delete 是 PV 删除时关联存储资源一起删;Recycle 是基础版清空卷数据再复用,目前已不推荐。现在是 CSI 的时代,回收策略也慢慢被 CSI 插件自己接管。答题时能说出“回收语义逐渐下沉到 CSI 层”这个趋势,基本就能加分。
3. 弹性伸缩与稳定性设计:让工作负载既能扛压也能容错
我见过很多生产事故,不是流量大把集群打爆,而是流量稍微一波动,Pod 频繁重建,后端数据库连接池被打满,最后服务雪崩。Kubernetes 就像是给你提供了自动扩缩的引擎,但不会替你定义好油门和刹车。要真正用稳它,HPA、探针、配额、预算这些机制都得按业务场景调好。
3.1 HPA 自动扩缩容的原理与指标计算
HPA(Horizontal Pod Autoscaler)是“根据指标增减 Pod 副本数”的标准方案。它的核心算法其实不复杂:期望副本数 = 当前副本数 ×(当前指标值 ÷ 期望指标值)。
举个具体例子:一个 Deployment 当前有 2 个副本,HPA 目标是 CPU 使用率 50%,实际当前 CPU 平均使用率是 75%。代入公式,期望副本 = 2 × (75% ÷ 50%) = 3,算出 3 个副本。注意,HPA 默认取所有 Pod 指标的平均值来与目标值比较,这个“指标聚合口径”很多人会忽略。
必须留意的两件事:
- metrics-server 只提供基础的内核指标,如果你的业务依赖自定义指标(比如队列长度、每秒请求数),需要部署 Prometheus Adapter 之类的组件。
- 扩容是有延迟的,本质上受制于指标采集频率、HPA sync 周期、Pod Ready 时间三个环节,链路越长,扩容越滞后。
3.2 探针三兄弟:Liveness、Readiness、Startup 的实际应用
探针可以说是面试里性价比最高的一类题,因为它直接关联到生产故障。
- Liveness 探针:检查容器是否活着,失败就杀掉重启。
- Readiness 探针:检查容器是否具备接收流量能力,失败则从 Service 端点列表摘除。
- Startup 探针:给慢启动应用用的,它会先于另外两个探针生效期间接管启动检测。
有个典型场景是 Java 应用启动要 5 分钟,如果用默认的 Liveness initialDelaySeconds=10,第一次探测失败就会把容器杀掉,反复重启永远起不来。正确方案是设置 startupProbe 去容忍较长的启动期,等它成功后,再启用 liveness 来兜底运行期问题。
这里还有一个我实际踩过的坑:Liveness 探针请求路径用了和业务同一条数据库连接链路,一旦数据库抖动,所有 Pod 都报失败,然后全被重启,问题被无限放大。探针路径在设计上一定要越轻量越好,能返回 200 的静态接口就够了。
3.3 PodDisruptionBudget:驱逐保护的正确姿势
面试问“如何避免节点维护把服务全拆了”,考的就是 PDB。PDB 定义的是一个最大不可用数量或最小可用比例,比如 minAvailable: 2 表示至少允许 2 个 Pod 保持可用。但要注意,PDB 只约束“自愿驱逐”,比如执行 kubectl drain 时的驱逐行为,非自愿故障(节点宕掉)它是管不了的。
很多人记不住 PDB 不约束 Deployment 缩容的行为。手动 kubectl scale 到 0 的时候,不会检查 PDB,这在生产环境特别容易让人误判。所以平稳发布与维护场景里的标准动作是:先把 PDB 配好,然后做 drain 前再确认一下节点上的 Pod 是不是都有 PDB 覆盖。
3.4 资源配额与驱逐:LimitRange、ResourceQuota 和 eviction
这个话题虽然偏运维,但也是面试官验证“你处理过真实集群”的很直接切入点。ResourceQuota 是限定 namespace 级别的资源总量(比如 CPU、内存、Pod 数),LimitRange 是限定单个 Pod 的默认 request/limit 边界。
节点资源耗尽时的驱逐行为也容易被误解。kubelet 会根据 hard eviction threshold(比如可用内存低于 100Mi)标记 node pressure,然后按 QoS 等级优先驱逐 BestEffort 的 Pod,再看 Burstable,最后才碰 Guaranteed。所以生产环境里尽量给核心服务设置完整且相等的 request 与 limit,让它们处于 Guaranteed QoS,避免一抖动就被最先牺牲掉。
4. 实战面试高频题型:答题框架与避坑示范
前面偏原理,这块偏“临场发挥”。我把面试中真正高频的题目整理出来,每个题型的答题思路都给你梳理清楚。面试官真正想听到的,不只是“知道这是什么”,而是“你在生产里怎么选的,以及为什么”。
4.1 Deployment、StatefulSet、DaemonSet 三件套差异化答题
这个题出现率极高,但大多数人的答案只停留在概念层面。一个有区分度的答法会从四个维度切入:
- 身份:Deployment 的 Pod 是无状态且随机名称;StatefulSet 的 Pod 是稳定标识,且配套稳定的网络标识与持久化卷;
- 顺序:StatefulSet 创建和销毁有严格的顺序语义,滚动更新时也是倒序逐个推进;
- 范围:DaemonSet 默认保证每个节点上一个 Pod,常用于日志收集(比如 fluentd)、节点监控(node-exporter);
- 调度异常:DaemonSet 在节点不可调度时通常会等节点恢复,而 Deploymment 可以把 Pod 调度到其他节点。
想要加分,一定要补充你在真实场景里的取舍。比如我遇到过一个场景:日志收集器改成 DaemonSet 后,节点数量非常大,DaemonSet 更新时容易把整集群的网络瞬间打满,所以改成分批滚动更新或者只更新一个节点组。这种实战细节远比背书有说服力。
4.2 Service 网络链路:iptables vs IPVS 的原理问答
Service 是 K8s 网络里的“固定入口”,它的实现原理通常靠 kube-proxy,目前主流模式是 iptables 和 IPVS。
iptables 模式:每个 Service 会生成一组规则,请求过来做 DNAT,把 service VIP 替换成后端 Pod IP,再做负载均衡。iptables 规则是按链式匹配的,规则越多匹配越慢。当集群规模到几千个 Service 时,规则变更期间整个节点网络会出现短暂的性能抖动。
IPVS 模式:IPVS 本身就是内核态的四层负载均衡器,它靠哈希表查后端,性能比 iptables 规则链高出一截。它还能提供更多调度算法,比如 rr、wrr、lc 等。在 CNI 环境中,如果要开 IPVS,需要在 kube-proxy 配置里指定 mode: ipvs。
面试中如果再深问一层:“Service 的 ClusterIP 怎么做到负载均衡的”,可以用一句话解释思路:所有节点上的 kube-proxy 都维护同样一套转发表,无论请求打到哪个节点,都能按本机规则转发到目标后端。所以 Service 只在集群内部有效,外部访问必须经过 NodePort 或负载均衡器再导进来。
4.3 Pod 调度典型题:无法调度、Pending 状态排查
这类题几乎必考。建议直接用“现象 → 怀疑列表 → 定位命令 → 根因”的框架去答。
- 现象是 Pod 一直 Pending;
- 先
kubectl describe pod看 Events,通常会出现 FailureScheduling; - 核心排查点依次是:资源不足(看 requests vs allocatable)、亲和性不满足、污点不容忍、PVC 没绑定、节点 Selector 不匹配。
有一个脱离概念的高级回答点:如果 Pending 是因为“节点亲和只匹配到 0 个节点”,你可以去看节点标签是否真的打了。生产里常有标签丢失或拼写不一致的情况,比如 node-role.kubernetes.io/control-plane 和控制面版本不同,导致你的选择器匹配不上。这种时候不要改代码,先把节点标签打对。
4.4 其他高频追踪题:Pause 容器、Etcd 选举、节点 NotReady
把这几个零散考点也列一版速记:
- Pause 容器:它不是业务容器,而是每个 Pod 最先启动的“基础容器”,作用是持有 Pod 的网络命名空间和 PID 命名空间,业务容器之后就共享它创建好的网络栈。
- etcd 选主:Raft 算法,只有 leader 才能接受写请求,follower 会把写转发给 leader。遇到 etcd 频繁切主,先检查网络延迟和磁盘 fsync 性能,这两项是最常见的触发源。
- 节点 NotReady:优先用
kubectl get nodes -o wide查看节点 LastHeartbeat,如果遥远说明 kubelet 失联;再看 kubelet 状态与容器运行时是否正常。很多 NotReady 的根本原因其实是节点上的容器运行时挂掉而不是节点本身网络断了。
这套题型的作答攻略是一个原则:概念清楚的前提下,尽量把你真实遇到的事件拉出来,讲清楚你是如何顺着现象一层层定位的。面试官普遍反感“背文档型”的候选者,喜欢能通过现象反推机制的人。
5. 面试之外的延伸:集群交付与平台化能力进阶
聊完面试题,还想写点生产落地的杂谈。Kubernetes 本身只是工具,真正拉开工程师差距的是“你怎么把集群用好、管稳、把能力产品化”。
5.1 kubeadm 初始化集群时的关键细节
如果你的实践是从 kubeadm 起步的,那就绕不开初始化后的第一道关卡:kubeadm init 时 kubelet 会做 preflight 检查,比如端口是否占用、swap 是否关闭、容器运行时是否连通。最典型的翻车点是宿主机的 swap 没关干净,会卡在 running pre-flight checks 报 swap 告警。
有一个经验技巧:不用一遇到 preflight 报错就直接加 --ignore-preflight-errors 跳过。该参数是兜底手段,直接跳过会导致 kubelet 起不来,排查反而更被动。正确的处理方式是把报错逐行看明白,比如端口冲突就去杀掉占用进程,cgroup 驱动不一致就把 kubelet 和容器运行时统一成 systemd cgroup 驱动。
5.2 多集群管理与混合负载:从面试题延伸出来的平台规划
如果介绍信里有“多集群治理”相关经历,一定要能讲清楚你的决策逻辑。核心问题无非是三个:为什么要拆集群、拆了之后怎么打通应用访问、如何统一做策略下发。
- 拆集群的动机通常是隔离故障边界、环境差异(开发/预发/生产)、安全合规;
- 应用访问打通套路是:对外统一入口 + 东西向流量走服务网格或 API 网关;
- 策略下发一般借助 GitOps 把多集群部署清单统一管理,再通过 ArgoCD 这类工具同步到各集群。
这些点完全可以只停留在方案选型层面,但一旦进入追问,你需要把 Pod 拓扑约束、节点亲和、PDB、资源配额这些基础工具串起来,证明你有完整的设计能力。
5.3 平台工程视角下的 K8s
面试题之外,K8s 的高级用法其实都指向一个方向:让业务团队只关心业务代码,不关心集群和资源细节。具体体现为内部开发者平台(IDP)的建设:模板化应用部署、自动化环境创建、统一的可观测性、租户与权限体系。
如果你面试的是 SRE 或平台组,一定要准备好这个问题:“如何为研发团队提供一个安全、自服务的 K8s 环境?”那你的答案里至少应该覆盖:Namespace 隔离、ResourceQuota 限定、RBAC 授权、默认 NetworkPolicy 拦截、镜像来源管控、流水线自动发布。这些话题都是前面所有原理的交叉融合。
我的个人体会是,Kubernetes 的知识体系不是线性的,你永远不可能靠背接口文档成为专家。真正的成长路径就是踩坑、看源码、复盘案例。希望这篇整理能帮你把散落的知识点串成体系,面试的时候不仅答得出“是什么”,更讲得清“为什么”和“怎么办”。
