1. Request与Limit:资源数字背后的调度潜规则
开头
做K8s运维这几年,我最大的感受是:大部分Pod调度问题,根源不在调度器本身,而在编写Deployment时随手填的那两个数字——resources.requests和resources.limits。很多人对K8s资源调度有误解,以为Pod能不能跑起来取决于节点剩余内存够不够大,其实不对。调度器做决策时根本不看节点当前真实使用量,只看节点上已分配资源的累计值。这意味着,哪怕一台节点实际空闲得发慌,只要它上面所有Pod的request总和已经超过节点容量,新Pod照样调度不上去。
这篇文章我从调度机制、节点选择策略、QoS驱逐规则、真实排障、HPA联动五个层面,把Pod资源调度的核心链路完整梳理一遍。适合刚接触K8s没多久、正在被Pod Pending折磨的初学者,也适合那些想搞明白“为什么明明有空闲资源、Pod却卡在调度队列里”的进阶选手。不说空话,全部是实际操作中验证过的东西。
1.1 这几个数字到底决定什么
资源配置通常长这样:
yaml复制resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
两个数字作用完全不同。
requests是给调度器看的。调度器为Pod选节点时,会把Pod的request累加到节点“已分配资源”上,保证所有Pod的request总和不超过节点容量。它本质上是“预留额度”,是一种逻辑上的资源承诺,不是实时用量。kubelet每10秒会上报一次节点状态,其中allocatable就是节点总容量减去系统预留和kubelet自身保留后的可分配值,调度时用的就是它。
limits是给运行时看的。容器实际使用超过limit时,CPU会被限流(throttling),内存超过limit会触发OOM Killer,直接杀掉容器进程。也就是说,limit更多是保护邻居、保护节点,不是用来保障自己。
有两个细节经常被忽略。
第一,CPU的request单位是核,500m就是0.5核,可以写成500m,也可以写成0.5。CPU是可压缩资源,超过limit只是被限流,容器不会死。内存是不可压缩资源,无路可退时内核只能杀进程。
第二,如果只写limit不写request,K8s有一个隐含行为:它会自动把request值设为limit值。这个设计本意是帮用户兜底,因为它会把Pod自动归为最高QoS等级,避免用户因为忘记写request导致Pod变成BestEffort。很多人在测试环境图省事只写limit,结果数量一多,每个Pod实际预留的都等于limit,节点资源瞬间被“凭空”占满。我见过不止一次测试环境节点莫名其妙地调度不上新Pod,查到最后全是这个原因。
1.2 从监控反推合理的Request值
Request值设多大,不能拍脑袋。我的习惯是:先压测,再看监控,然后留buffer。
具体操作流程是这样。第一步,把业务容器没有配置资源的Pod(或不设limits)部署到一台独立测试节点上,确认监控系统在采集容器CPU和内存的working_set。第二步,用压测工具(Jmeter、wrk、k6都行)把流量打到预期的日常峰值的1.5到2倍。第三步,观察这段压测时间内的P99内存使用和CPU使用曲线。
拿到数据后,我的建议是请求值取“P99观测值 × 1.3”,limit取“请求值 × 2到3倍”。比如某个服务P99内存是400Mi,request设512Mi,limit设2Gi,CPU同理。这个写法既让调度器为Pod预留了足够“过冬”的粮食,又允许它在突发流量下临时借助节点空余资源,而不是一超限就死。
有人会问:为什么不直接request等于limit,这样QoS等级最高,最稳?这需要看你集群的资源利用率和业务重要程度。如果节点资源充足、成本不敏感、集群规模小,那我支持这种做法。但如果你在一个业务线很多、节点资源长期处于70%以上水位的大集群里,所有Pod都Guaranteed,资源浪费会很严重,之后就必然面对加节点、提成本的压力。至于是不是Guaranteed一定最好,下面QoS章节我会展开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 调度器的工作闭环:Pod从提交到Running经历了什么
2.1 调度、预选、优选:kube-scheduler的完整决策链路
提交一个Deployment后,资源调度的主流程是这样的。
Pod先进入API Server,被写入etcd。这时它的状态是Pending,spec.nodeName还是空的。kube-scheduler监听到这个没有归属节点的Pod,把它放进调度队列,开始处理。
调度过程分两阶段:Filter(预选)和Score(优选)。
Filter阶段是硬性的,淘汰制。调度器会用一系列predicate插件检查节点。比如NodeResourcesFit检查节点剩余可分配资源是否满足Pod的request,NodeAffinity检查节点标签是否匹配Pod声明的亲和性规则,TaintToleration检查节点上的污点Pod是否容忍,PodTopologySpread检查拓扑分布约束是否被违反。任何一个插件返回false,节点直接出局。如果最后没有节点通过,Pod就卡在Pending,事件里会写明失败原因。
Score阶段是软性的,排名制。所有通过筛选的节点会参与打分,调度器按一
