Kubernetes资源控制实战:从requests/limits到QoS与HPA配置

发布时间:2026/10/11 18:06:11
Kubernetes资源控制实战:从requests/limits到QoS与HPA配置 1. 资源控制不搞明白集群早晚要出事在我接触过的所有 Kubernetes 集群问题里由于资源控制没做好而引发的故障占比相当高。不是应用代码写的多烂也不是集群网络又有什么古怪升级而是最朴素的CPU和内存不够用了、被误配置了、分配不合理了导致Pod被杀、节点被压垮、整个业务雪崩。先看一个很典型的场景某天线上有个服务突然大量报错查看Pod状态发现全是OOMKilled再往下一查发现这个服务的内存limit设得很随意有人把它调成了512Mi但实际业务在高峰要跑到1Gi以上。另一个情况更常见——某个没有设置任何requests和limits的Pod在流量高峰把节点内存打满了kubelet被迫触发节点级驱逐把同一节点上的其他服务全部拖下水。这就是资源控制的意义所在它决定了一个Pod能拿到多少资源、能用到多少资源、以及在资源紧张时谁先被牺牲。说白了Kubernetes的调度、伸缩、驱逐、QoS这些核心机制通通建立在资源请求requests和资源限制limits的基础上。如果你没有把这套东西管明白集群就像一栋没有楼道消防门的大楼一家着火整层遭殃。这篇内容不是讲Kubernetes入门而是针对已经上生产、想真正把资源控制这件事做扎实的团队。适合运维、平台工程师、负责Kubernetes集群稳定性的同学参考。我会从资源模型本身讲起再给出一套可以直接抄的配置方案最后把常见的坑和排查思路理一遍。看完之后你应该能回答这几个问题requests和limits到底怎么设LimitRange和ResourceQuota怎么配合用HPA怎么配置才靠谱Pod被杀、节点告警时第一反应应该查什么2. 先把资源模型的地基打牢Requests、Limits和QoS2.1 参数拆解requests是下限limits是上限很多刚接触Kubernetes的人最容易犯的错是混淆requests和limits的含义。简单记一句话requests是“我保证至少要给这么多”limits是“我最多只能用到这么多”。CPU和内存是两套完全不同的机制。CPU是可压缩资源单位是核core或毫核millicore1核等于1000m。Pod里的进程在CPU上是可以被挤占的所以当一个Pod超过了它申请的CPU requests时它并不会被杀掉而是会被限流也就是慢下来。CPU的limits则是一个硬性上限超过之后内核会通过CPU调度机制强制限制。但这里有个容易被忽视的细节cpu: 1这样的写法等于1000m如果你的节点是2核Pod申请了requests.cpu: 1那么调度器会认为这个Pod需要占满50%的节点CPU。实际跑起来后如果它只用了200m那剩下的800m调度器也无法再塞进其他Pod因为调度依据是requests不是实时用量。内存是不可压缩资源单位是字节Mi/Gi或者M/G。内存一旦超出limitkubelet会直接杀掉容器触发OOMKilled。内存的requests同样影响调度但它还有一个关键作用当节点内存压力比较大时kubelet会参考Pod的requests来决定驱逐顺序。假如一个Pod的requests很小但实际占用很大node压力一到它就会是被优先清理的对象。所以我们在设计YAML的时候必须想清楚一件事**requests是给调度器和驱逐机制看的limits是给运行时资源上限看的。**真正常见的行为是people把一堆服务全部塞到一个节点里以为只要CPU/内存的sum没超过节点总量就安全但没考虑到节点本身还有系统进程、kubelet、容器运行时开销。结果节点Allocatable资源被占满新的Pod一直Pending老的Pod时不时被驱逐。2.2 QoS等级被牺牲的顺序一开始就要定好每个Pod根据requests和limits的配置方式会被归入三类QoSQuality of Service等级之一Guaranteed、Burstable、BestEffort。如果Pod里所有容器都同时设置了requests和limits并且两者相等这个Pod就是Guaranteed。它会得到最高的保障优先级在节点资源紧张时最晚被驱逐。如果至少有一个容器设置了requests但limits和requests不相等或者有的容器没设置limits那这个Pod就是Burstable。这是生产环境里最常见的一类。如果容器完全没有设置requests和limits那就是BestEffort。这类Pod的优先级最低节点一紧张最先被清理的就是它们。我见过太多团队上线服务时不加任何资源配置导致默认生成一堆BestEffort Pod。平时没事流量一冲上来节点压力升高最先挂掉的恰恰是那些没设资源的服务。更麻烦的是因为它们是BestEffort驱逐时不会给你任何预警直接就被杀掉了业务方根本来不及反应。所以在生产环境里最低标准应该是所有Pod都至少设置requests。如果想让核心服务更稳最好把limits和requests设成相等让它进Guaranteed档位。不要觉得这样浪费——Guaranteed Pod更稳定调试成本更低后面我会详细聊这里面的取舍。3. 从零配置一套可落地的资源管控3.1 给工作负载设置资源请求与限制第一步是所有Deployment、StatefulSet、Job、DaemonSet里的Pod模板都写成带resources的版本。下面是一个比较保守的模板apiVersion: apps/v1 kind: Deployment metadata: name: web-demo namespace: dev spec: replicas: 3 selector: matchLabels: app: web-demo template: metadata: labels: app: web-demo spec: containers: - name: app image: registry.example.com/web-demo:1.2.3 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi ports: - containerPort: 8080怎么看这几个数值通常做法是先用没有requests/limits的方式在测试环境跑一段压力测试观察实际使用情况再根据P99或者平均峰值来设定。比如通过kubectl top pod看到这个服务平时CPU在150m左右高峰能冲到400m内存稳定在300Mi左右那么requests可以设为cpu 200m、memory 256Milimits设置为cpu 500m、memory 512Mi。这样既给足缓冲又不至于让limits高到失控。有人会问既然limits设成512Mi内存会不会早早被杀掉这里要提醒一句内存limit的设定切忌拍脑袋。设小了服务容易OOM设大了又失去限制意义。我的经验是初始可以按照实际峰值的1.5~2倍给limit等线上监控数据跑上两周之后再收敛。3.2 用LimitRange约束单个Pod的资源配置有了基础模板下一步要解决的是“有人忘了配置”的问题。团队越大YAML越难统一总有人写了个复杂Job或者临时调试Pod漏掉resources字段。这时候需要引入LimitRange。LimitRange做的事情是在Namespace级别约束单个Pod或容器的资源使用范围。它能在创建时强制填上默认值也可以规定一个Pod的最小/最大资源范围。比如下面这个配置apiVersion: v1 kind: LimitRange metadata: name: dev-limitrange namespace: dev spec: limits: - max: cpu: 4 memory: 8Gi min: cpu: 100m memory: 64Mi default: cpu: 500m memory: 512Mi defaultRequest: cpu: 200m memory: 256Mi maxLimitRequestRatio: cpu: 3 - type: PersistentVolumeClaim max: storage: 100Gi这段配置的实际含义是在dev命名空间里单个Pod最大能申请4核CPU和8Gi内存最小不能低于100m CPU和64Mi内存。如果Pod没写资源配置LimitRange会自动给它的容器填上defaultRequest200m/256Mi和default500m/512Mi。maxLimitRequestRatio: cpu: 3用来控制limit和request的比例防止有人把requests写得很低、limits写得极高导致Pod分数上看起来很小实际却可能把节点冲爆。需要注意的是LimitRange是立即生效的但它不会修改已经存在的Pod。如果你想通过它给所有老Pod补上配置需要滚动重建工作负载。3.3 用ResourceQuota做命名空间总盘子控制LimitRange管单个PodResourceQuota则是管整个命名空间的总量。有了它你可以限制一个团队或一个项目在一个命名空间里最多能消耗多少资源。典型配置apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 8 requests.memory: 16Gi limits.cpu: 16 limits.memory: 32Gi pods: 50 services: 20 persistentvolumeclaims: 30这个Quota限定了dev环境最多能创建50个PodCPU requests总和不超过8核内存requests总和不超过16GiCPU limits总和不超过16核内存limits总和不超过32Gi。一旦达到上限新Pod创建会报exceeded quota错误Event里能明确看到是哪项资源和数量的配额被耗尽。把ResourceQuota和LimitRange组合使用是生产环境中非常有用的组合拳。ResourceQuota负责总量控制LimitRange负责个体约束。举个实际例子团队A在dev命名空间申请了8核16Gi额度他们内部有人配置内存requests为4Gi的Pod那最多只能同时跑4个再多就创建不了。这种“硬约束”会逼着团队去优化自己的资源配置而不是无限制地堆资源。另外ResourceQuota对存储也有效比如限定requests.storage总容量或PVC数量上限。如果要在多团队共用集群时避免某团队把存储空间撑爆这个配置必须加上。3.4 配套实践给默认命名空间设置标准在多个团队共用集群的场景下光有开发团队自己写YAML显然不够平台团队应该在生产Namespace级别建立统一规范。我的建议是搭建一个简单的准入控制机制例如使用ValidatingAdmissionPolicy或者更传统的PodSecurity类工具。不过这些配置比较多如果不想引入额外组件建议至少统一以下三件事所有Pod模板必须包含resources字段平台通过扫描脚本检查发现缺失就拦截或告警。核心生产服务的requests和limits必须相等也就是Guaranteed QoS。每个Namespace必须存在对应的ResourceQuota和LimitRange否则不允许创建部署。这三条看着简单但对稳定性的提升是立竿见影的。因为一旦每个Pod的资源配置和总量控制都被强制约束“某个Node被某一个无限制Pod打爆”这种事故的概率会大幅下降。4. 动态伸缩与资源治理HPA和VPA的正确打开方式4.1 HPA怎么配才不容易一直抖动资源控制的另一个重要方向是自动伸缩。HorizontalPodAutoscaler简称HPA通过监控指标动态调整Pod副本数。上了Kubernetes自动伸缩之后资源控制就不只是“写死一个值”了而是让副本数和资源请求形成闭环。先看一个基于CPU指标的HPA配置apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-demo-hpa namespace: dev spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-demo minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 behavior: scaleDown: stabilizationWindowSeconds: 300这个HPA的指标是“所有Pod的CPU平均使用率相对于requests的比例”。如果requests是200m实际使用达到平均70%利用率即140m时HPA会尝试扩容。实际的控制算法是期望副本数 当前副本数 * (当前指标值 / 期望指标值)。比如当前3个副本CPU使用率平均是140%相对requests目标是70%那么期望副本数就是3 * (140/70) 6。这里面最容易踩的坑是HPA的扩容指标只认requests不认limits。如果你把requests设得非常低而应用实际占用很高那么HPA可能在你意想不到的时机疯狂扩容直到把maxReplicas打到顶。反过来requests设得太高那么CPU利用率再怎么压都超不过目标值HPA迟迟不扩容导致服务在高峰期被压垮。我的建议是HPA所依赖的CPU requests必须刻意设成和应用常态工作的基线一致别为了“调低一点更好扩容”而故意把requests设得很小。HPA调优的关键是让扩容速度和业务增长曲线匹配而不是让它频繁跳变。上面配置里的stabilizationWindowSeconds就是在缩容时加上一个稳定窗口默认300秒避免流量一波动副本数就忽上忽下。4.2 VPA的使用时机和那些隐蔽的坑相比HPA的副本扩缩VerticalPodAutoscaler简称VPA解决的是“单个Pod的requests和limits该设多大”的问题。它会根据历史使用数据给出recommendation然后可以自动更新Pod的资源配置。听起来很美好但实际落地时要非常谨慎。VPA最关键的坑是它调整资源时需要重启Pod。因为Kubernetes的requests和limits在容器创建之后是不能动态修改的。也就是说VPA每次调整配置都意味着你的Pod会被重建一次连接会被断开。对于无状态服务还好对于有状态的数据库、消息队列这类服务VPA的自动更新模式基本不可用。所以在生产环境里我见过比较稳的做法是把VPA设成mode: Off只用它的Recommendation功能定期查看它建议的requests/limits值然后由人来决定要不要改。相当于给它加了一个“参谋”角色而不是让它直接操控掌舵。apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-demo-vpa namespace: dev spec: targetRef: apiVersion: apps/v1 kind: Deployment name: web-demo updatePolicy: updateMode: Off resourcePolicy: containerPolicies: - containerName: app minAllowed: cpu: 100m memory: 128Mi maxAllowed: cpu: 2 memory: 4Gi这里通过minAllowed和maxAllowed限定了recommendation的边界不让VPA建议出离谱的值。每天让VPA在测试环境运行收集一周数据后把它的建议值人工应用到生产。这样既利用了VPA的预测能力又避开了它自动重启Pod的风险。5. 线上问题排查与避坑实录5.1 Pod一直Evicted为什么节点上明明有空间Evicted是最常见的资源控制问题之一。它的核心原因是kubelet检测到节点层面存在资源压力然后主动清理了一些Pod被清理的Pod状态就会变成Evicted。很多人不理解我明明看节点还有空闲CPU和内存怎么Pod就被Evicted了原因往往是kubelet判断的不是节点总资源而是节点Allocatable资源减去系统保留资源后的可用量。还有一点被驱逐的未必是资源占用最高的那个Pod而是QoS优先级最低的那个。换句话说如果你的Pod是BestEffort只要节点上任何其他工作负载造成压力你的Pod都有可能被殃及。排查Evicted Pod的标准流程是先看节点状态kubectl describe node node-name关注Conditions里的MemoryPressure、DiskPressure、PIDPressure。查看被驱逐的Pod的事件kubectl describe pod pod-nameEvent里通常会写清楚是哪类压力导致的驱逐。检查节点上是否有非Kubernetes管理的进程在吃资源比如被侵入的挖矿进程或者写死循环的脚本。检查这节点的Pod密度和requests总量。如果requests总量超过节点Allocatable那么新Pod根本调度不上来如果节点本身超卖但没有足够的Pids或者磁盘inode同样会触发Pressure。我给团队的实战建议是给每个节点预留一定的“驱逐头寸”。比如一个16核32Gi的节点Allocatable大概会扣掉系统组件和kubelet保留的一部分实际上才能调度约15核28Gi。在这个基础上不要把requests总量压到95%以上留出至少5%~10%的缓冲能极大降低节点进入MemoryPressure的概率。5.2 容器OOMKilled可能是limit设小了OOMKilled和Evicted不一样它是容器本身超过了memory limit被内核OOM Killer干掉了。排查时通常用这两条命令kubectl get pod pod-name -o json | jq .status.containerStatuses kubectl logs pod-name --previousEXIT CODE: 137代表容器被SIGKILL杀掉绝大多数是OOM如果要更精确看内核日志journalctl -k -f | grep -i oomDmesg里会显示Out of memory: Killed process X (java)之类的记录。知道了是OOM之后不要急着把limit调大先看监控里的内存使用曲线。很多Java应用默认的Heap大小和容器内存limit没有做联动比如容器limit是1Gi但JVM觉得自己有4Gi可用于是把Heap扩到2.7Gi直接触发OOM。这种时候解决问题的关键不是加limit而是设置JVM的-XX:MaxRAMPercentage让它按容器limit比例分配堆内存。还有一类隐蔽问题limit设置了但requests没设置或者requests和limits差距过大。这种情况会让Pod变成Burstable。在内存压力下超过requests的部分会被优先回收。所以如果你的服务对延迟敏感最好把requests和limits设成一致直接进Guaranteed。5.3 调度失败不是资源不够是碎片问题Pod一直Pendingkubectl describe里出现Insufficient cpu或Insufficient memory但不代表整个集群没资源。这很可能是资源碎片化。什么意思举个例子集群里有三台节点每台都有2Gi空闲内存但你的Pod需要3Gi调度器找不到一台能满足requests的节点Pod就一直Pending。这个问题没有银弹几个现实的做法第一调整Deployment的副本数和requests尺度。如果Pod声明内存requests是2Gi而Pod本身常态只用几百Mi可以考虑把requests降下来让调度器更容易找到位置。第二如果业务确实需要大内存Pod可以给这个Deployment单独打上节点亲和把它调度到大内存节点上。但这一步需要区分“大内存节点”和“通用节点”否则会造成资源倾斜。第三开启集群的调度器配置优化比如使用NodeResourcesFit评分策略或者在集群规模足够大时引入descheduler这样的组件定期整理碎片。后者比较复杂非必要不建议一上来就折腾。最后提醒一个非常常见的坑不要为了让Pod能调度进去就把requests降到特别低、limits维持高位。这会让调度器认为节点很空然后把几十个这种Pod全部调度到一台节点上。结果就是节点上requests看起来只用了70%实际内存峰值却快顶到Allocatable上限最后触发节点驱逐。这种配置比不配还危险属于典型的“用调度层的宽松换运行层的爆雷”。6. 资源控制的一些实操习惯和心得管理Kubernetes资源这件事说到底不是一次性配置完就结束的它更像一个持续运营的过程。我在实际项目里积累了几个习惯不一定适合所有团队但希望能给后来人一点参考。第一个习惯是每周固定看一次资源水线。用kubectl top nodes和kubectl top pods结合观察节点的资源分配率requests/Allocatable和实际使用率。通常只看两个指标节点请求占用率超过85%要留意超过90%必须处理节点实际CPU使用率长期超过70%说明可能要考虑扩容或者业务拆分。第二个习惯是给核心服务做资源审计。审计的结果不是“谁的Pod资源设大了要砍掉”而是从稳定性角度确认所有生产服务都进Guaranteed档位、内存limit和真实峰值有足够的余量、CPU requests不会因为设得太低导致HPA错乱。我见过有的团队为了追求高密度部署把CPU requests压得很低结果HPA隔三差五扩容节点被Pod数量打满调度出了大量Pending反而更不稳定。第三个习惯是把ResourceQuota和LimitRange变成准入模板的一部分。我们内部推行过一套“配额先行”的制度每个新命名空间上线的第一天平台就给它建好默认的Quota和LimitRange不给后补的机会。这样从制度上杜绝了“先用起来再说”的侥幸心理。配合CICD里的检查脚本凡是提交的YAML非白名单容器没有resources字段直接构建失败从源头拦住。最后再分享一个调试小技巧。很多人排查资源问题时只盯着kubectl describe和kubectl get events但有时候事件被刷新太快或者Pod已经被重新调度看不到当时的现场。建议提前给集群接入事件持久化把Kubernetes事件统一收集存储起来。真出问题的时候根据时间轴去翻当时的事件记录再配合节点监控绝大多数资源问题都能找到明确线索。资源控制这件事做好了看不出什么功劳做不好就是事故频发。希望这篇作业指导书能让你的集群少一点Evicted少一点OOM少一点凌晨三点被叫起来修容器的经历。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询