
说实话那么多人在用 Kubernetes真正把资源管理搞明白的却很有限。Requests、Limits、QoS 这三个词天天见但大部分人只是照着模板写几个数字从来没想过它们背后如何影响调度、驱逐和 OOM。结果就是线上服务动不动 OOMKilled节点一紧张就把关键业务给杀了一问原因全都栽在资源配错上。这篇内容就是要把 Requests、Limits 与 QoS 之间的逻辑彻底讲透结合我自己的实操经历把调度原理、配置方法、排障套路和面试点一次说清楚。适合刚开始接触 Kubernetes 的运维、后端开发也适合那些已经跑了一段时间但经常被资源问题折腾的人。1. 从一次 Pod 驱逐说起为什么资源管理这么重要先讲个真实案例。前两年我维护过一套微服务集群节点规格是 8C16G上面跑了十几个 Pod每个 Pod 配置的时候都没怎么在意资源字段有人写 limits有人不写还有人写得特别大比如内存 Limits 直接给 12G。刚开始没啥事服务也正常。结果某天业务高峰期某个节点上的 Java 应用内存飙起来直接把整台节点内存打满kubelet 开始按策略驱逐 Pod先是那些没有设置任何 requests/limits 的 Pod 被干掉紧接着一些设置了少量 requests 的 Pod 也开始被标记为 Evicted。最尴尬的是一个核心支付服务因为资源设置不够“硬”在驱逐顺序里排在前面莫名其妙就挂了业务方一脸蒙。这件事之后我才真正意识到在 Kubernetes 里资源管理不是“写几个参数”那么简单它直接决定了调度器怎么选节点、kubelet 怎么给容器分配资源、以及节点压力来临时谁先死。你写得不好等于把命运交给了运气。1.1 什么是 Requests 和 Limits先分清“要多少”和“最多用多少”Requests 和 Limits 是定义在容器或者 Pod 层面的一组资源声明核心字段就两个cpu和memory。用大白话说Requests表示“我至少要这么多资源”调度器在选节点时会累加所有 Pod 的 Requests保证节点上 Requests 的总和不超过节点实际容量。Limits表示“我最多只能用这么多资源”超过之后CPU 会被限制内存则可能直接被杀掉。可以类比成公司团建订酒店。Requests 就是“我保证给你预留一个标间”Limits 就是“这家酒店最多只允许你住这个房间大小你不可能占用整个楼层”。在 Kubernetes 里CPU 和内存的“脾气”不一样CPU 是可压缩资源如果请求的 Limits 超过实际配额系统只会对你的线程做限流容器还在跑但是变慢内存是不可压缩资源一旦容器试图分配超过 Limits 的内存内核会直接触发 OOM 杀掉进程没有讨价还价的余地。举一个最简单的 YAML 例子apiVersion: v1 kind: Pod metadata: name: demo-pod spec: containers: - name: demo-container image: nginx resources: requests: cpu: 250m memory: 256Mi limits: cpu: 1 memory: 512Mi250m表示 0.25 个 CPU 核心这是 Kubernetes 的标准单位。内存的Mi是 MiB不是 MB1Mi 约等于 1.048MB这点在小内存场景下会差出来不少配置时别搞混。1.2 背后的两个核心机制调度决策与压缩/驱逐Requests 和 Limits 不止是“写进 etcd 的配置”它们会被两个核心组件用到。第一个是kube-scheduler。当一个新的 Pod 要调度时scheduler 会遍历所有节点过滤掉那些剩余可分配资源Allocatable不足以满足 Pod Requests 的节点然后按优先级打分选一个最优节点。注意这里用的是 Requests不是 Limits。也就是说你一个 Pod 的 Requests 写得很小但 Limits 写得巨大调度器并不关心它会认为你“占不了多大地方”结果就是多个大 Limits 的 Pod 可能被塞到同一个节点后面跑起来时资源一冲节点直接过载。第二个是kubelet。在容器运行阶段kubelet 会通过容器运行时把 Requests 和 Limits 转成更底层的配置CPU Limits 会通过 CFS 带宽控制限制容器可以使用的时间片所以超过了 Limits 只会被节流不会立刻死。内存 Limits 会转换成 cgroup 的memory.limit_in_bytes容器进程的内存页分配超过这个值内核就会将其判为 OOM 候选。这里有个很容易被忽略的点内存没有“压缩”一说。CPU 超了系统可以把任务挂起分的时间片少一点容器还在。内存超了内核必须立刻腾出页面它唯一能做的就是杀进程。所以配置内存 Limits 时要格外谨慎宁可给大一点预留缓冲也不要让实际使用量总在临界线边缘试探。2. 理解 QoS 等级三种命运的差异Requests 和 Limits 组合在一起会为每个 Pod 计算出一种 QoSQuality of Service服务质量等级。Kubernetes 里的 QoS 一共三种从高到低分别是Guaranteed、Burstable和BestEffort。这个等级决定了节点资源紧张时Pod 被驱逐的优先级以及在 OOM 场景下进程被杀的先后顺序。规则很简单GuaranteedPod 里每一个容器都同时设置了 requests 和 limits并且同一个资源维度上 requests 和 limits 的值相等。例如cpu.requests cpu.limits且memory.requests memory.limits。Burstable至少有一个容器设置了 requests 或 limits但不满足 Guaranteed 的条件。这是最常见的类型比如只设置了 requests没设置 limits或者 requests 和 limits 不相等。BestEffort所有容器都没有设置任何 requests 和 limits。最惨的一类没有任何资源保障。看几个片段就明白了# Guaranteed resources: requests: cpu: 500m memory: 512Mi limits: cpu: 500m memory: 512Mi # Burstable resources: requests: cpu: 200m memory: 256Mi # limits 没写妥妥 Burstable # BestEffort # 整个 resources 字段都不存在你可能会想那我要不要所有 Pod 都配成 Guaranteed答案是看场景。Guaranteed 的 Pod 在节点资源不足时最后才会被驱逐这对于数据库、核心业务无疑是很好的保护但它也意味着你每个 Pod 都要预留等于 Limits 大小的资源集群整体的资源利用率会偏低。BestEffort 适合批处理、可重跑的任务挂了也不心疼关键是别和核心业务混在同一个节点上否则它会优先变成“替死鬼”。2.1 QoS 与 OOM 评分的关系很多同学知道 QoS 影响驱逐顺序但不知道它还会影响 Linux 内核的 OOM 评分。kubelet 在创建容器时会根据 Pod 的 QoS 等级设置oom_score_adj这个值会影响内核 OOM Killer 选择杀哪个进程。对于Guaranteed级别的容器oom_score_adj通常被设置为-998或-997具体取决于是否开启了PodSecurityPolicy等较新版本统一用 -997。分数越低进程被杀的概率越小所以 Guaranteed 的进程在内存压力下存活率极高。对于BestEffort的容器oom_score_adj通常设为1000这个分很高意味着内核在内存严重不足时几乎必然先杀它。对于Burstable的容器会按照内存申请占节点可分配内存的比例动态计算申请越少分数越高越容易被杀。有一个真实案例能说明这个设计多重要。早期我负责的一个推荐服务内存请求是 1Gilimits 给了 2Gi节点内存 32G实际服务跑起来稳定在 700Mi 左右看似很安全。结果有一天同一节点上有个大数据任务突然吃满了几 G 内存节点开始进入 memory pressure内核 OOM Killer 挑来挑去把这个推荐服务给杀了。查日志发现 OOM Killer 的评分里推荐服务因为 oom_score_adj 比较高排在了前面。后来我把推荐服务的 requests 和 limits 拉齐改成 Guaranteed再遇到内存压力时它稳如泰山被杀的变成了那个任务 Pod。所以关键业务尽量做成 Guaranteed本质上是让它在系统危急关头拥有“优先存活权”。2.2 使用 QoS 的经典误区资源管理最容易踩的是几个组合坑第一只设 requests 不设 limits。这样会让 Pod 被调度到一个“看起来正好”的节点但实际运行时它很可能会占用远超 requests 的内存。因为调度器只看 requests而 kubelet 对内存的硬限制来自 limits没有 limits 就等于容器不设上限可以一路吃满节点内存最终把整个节点拖垮。第二limits 远大于 requests。比如 requests 给 100m CPUlimits 给 8 核调度时它会被找一个剩余资源较多的节点塞进去运行时一冲高就把节点 CPU 跑满其他 Pod 跟着遭殃。我见过不少同学为了“保险”盲目把 limits 放大最后得不偿失。第三多容器 Pod 的 QoS 取最差等级。Pod 里有两个容器一个 Guaranteed一个 Burstable整个 Pod 就是 Burstable而不是两个容器各自独立。这意味着你精心为核心容器配了 Guaranteed只要旁边有个小容器没配齐 resourcesPod 整体的 QoS 就降级了驱逐优先级也随之下降。排查问题时记得看整个 Pod 定义。3. 资源管理实操从配置到监控前面讲了很多概念下面聊聊实际怎么操作。我给团队定了一套相对稳妥的资源管理流程核心就三步先量化业务需求再配 requests/limits最后持续监控调整。3.1 如何给服务设计合理的 Requests/Limits很多人在配资源时全靠拍脑袋这肯定不行。比较好的做法是在一个灰度环境或者测试环境先不设 limits只设一个保守的 requests然后运行两三天用监控记录服务的 CPU 和内存实际使用曲线。取 P9595 分位或 P99 的值作为请求量评估的依据。举个例子服务运行三天内存监控显示 P50 是 300MiP95 是 450MiP99 是 520Mi。那 requests 就可以设置为 512Milimits 设置为 768Mi 到 1Gi 之间。这样既保证调度器能按常规水位预留资源也给峰值波动留了缓冲。CPU 同理requests 按平均负载来limits 按峰值负载的 1.5~2 倍来。不要怕 Pod 被节流CPU 节流最多是性能下降不会造成进程崩溃但内存超限会直接 OOM。下面的 Deployment 片段是一个标准做法apiVersion: apps/v1 kind: Deployment metadata: name: orders-api spec: replicas: 3 selector: matchLabels: app: orders-api template: metadata: labels: app: orders-api spec: containers: - name: orders-api image: registry.example.com/orders-api:v1.2.3 ports: - containerPort: 8080 resources: requests: cpu: 250m memory: 512Mi limits: cpu: 500m memory: 1Gi readinessProbe: httpGet: path: /health port: 8080配完资源后第一时间用kubectl describe node看节点剩余可分配情况kubectl describe node node01输出里的Allocatable是节点可调度的资源总量Allocated resources是当前已分配出去的 requests 总和。这部分常常被忽略其实它才是你可以直接判断节点饱和度的地方。3.2 用 LimitRange 和 ResourceQuota 给团队立规矩光靠每个人自觉配置资源字段在多人协作的集群里根本不现实。Kubernetes 提供了两个原生机制帮你“强制”资源管理落地LimitRange作用于 Pod 或容器设置默认的 requests/limits也可以限制单个容器资源的最小/最大值。ResourceQuota作用于命名空间限制该命名空间所有 Pod 加在一起的资源总量。举个例子在development命名空间下我想让所有不写资源的 Pod 自动带上默认值apiVersion: v1 kind: LimitRange metadata: name: dev-default-resource spec: limits: - default: cpu: 500m memory: 512Mi defaultRequest: cpu: 200m memory: 256Mi type: Container再配合一个命名空间级别的 QuotaapiVersion: v1 kind: ResourceQuota metadata: name: dev-quota spec: hard: requests.cpu: 10 requests.memory: 20Gi limits.cpu: 20 limits.memory: 40Gi这样开发环境的资源总量就被框死了任何再多的 Pod 调度进去都会报FailedCreate或ErrorQuotaExceeded从根上避免“谁都能塞一个 Pod 进来把集群打爆”的失控局面。注意LimitRange 只影响新创建的 Pod已有的 Pod 不会被自动注入默认值。3.3 用 Terraform 管好“资源声明”这一层在日常实践中很多人会把 Kubernetes 资源文件用 Helm 管理但也有人喜欢用 Terraform 统一管理基础设施。Terraform 在管理资源声明时有个明显优势你可以把 Kubernetes 资源模板模块化参数化 requests/limits并通过 CI 流水线审核。比如用 Terraform 的kubernetes_deployment_v1资源resource kubernetes_deployment_v1 web { metadata { name web namespace production } spec { replicas 3 selector { match_labels { app web } } template { metadata { labels { app web } } spec { container { image nginx:1.27 name web resources { limits { cpu 500m memory 512Mi } requests { cpu 250m memory 256Mi } } } } } } }这样把资源声明纳入 Terraform 的plan和apply流程后任何修改都要先走代码评审不会有人悄悄用 kubectl 改掉 limits。我们团队通过这种方式让所有新服务的资源字段不再靠自觉而是成为“默认合规”。3.4 安装 Dashboard 看资源更直观命令行的kubectl top能看实时数据但要看历史趋势、对比不同命名空间的资源水位建议装一个 Kubernetes Dashboard或者直接用 Prometheus Grafana。Dashboard 的安装相对简单步骤就三步拉取 manifest、创建 ServiceAccount 和 ClusterRoleBinding、获取 token 登录。kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml然后创建一个管理员账号用于登录kubectl create serviceaccount admin-user -n kubernetes-dashboard kubectl create clusterrolebinding admin-user --clusterrolecluster-admin --serviceaccountkubernetes-dashboard:admin-user拿到 token 的方式是kubectl -n kubernetes-dashboard create token admin-userDashboard 的“工作负载”页面会列出每个 Pod 的 CPU/内存请求限额以及当前实际用量一眼就能看出哪些服务资源设得明显不合理。配合 Prometheus 的持续监控我甚至可以基于历史数据做每个服务的资源画像为后面精细化调参提供依据。4. 常见坑资源配错导致的故障与排查实录资源配错之后的故障现象归纳起来其实就几类Pod 反复 OOMKilled、节点持续内存压力导致驱逐、调度时节点不足但实际节点负载很低。下面结合我遇到的案例逐一说明排查路径。4.1 案例一内存超限被 OOMKilled当时有个 Python 爬虫服务部署后每隔一段时间就重启一次。kubectl get pod看到状态是CrashLoopBackOff我先是习惯性地去看日志结果啥也没看到因为进程是被内核杀的应用自己没来得及记录。这时需要看 Pod 的详细事件kubectl describe pod pod-name在输出的事件区域能看到类似这样的记录State: Running Started: ... Last State: Terminated Reason: OOMKilled Exit Code: 137看到OOMKilled就知道是内存超了 limits。接着可以登录节点查看 cgroup 的统计确认内存峰值cat /sys/fs/cgroup/memory/kubepods/pod-id/memory.max_usage_in_bytes最后根据实际使用量上调 limits并把 requests 也相应调到一个更合理的水位问题解决。这里有个经验如果 Pod 一重启就 OOM而且发生在启动阶段往往不是业务内存涨太快而是 limits 给得太小连 JVM 堆都不够用先把 limits 放大两倍再说。4.2 案例二节点内存压力导致核心 Pod 被驱逐这类问题的现象是节点上大量 Pod 状态变成Evicted但业务日志里没有异常。排除方法同样是看事件事件里会写The node was low on resource: memory。如果节点宿主机本身内存是够的为什么 kubelet 会判定 low on resource常见原因是节点的system-reserved或kube-reserved预留量太少kubelet 认为可用内存少于某个阈值就会触发驱逐。可以用kubectl describe node查看Allocatable和System Reserved如果发现预留过低就在 kubelet 配置文件里调整这些值。同时调整各 Pod 的 requests 和 QoS 也能有效降低节点压力。顺便说一句很多人听到“limits”这个词时会联想到 Linux 系统里的 ulimit。比如在 AIX 系统上常用ulimit -a查看用户资源限制这和 Kubernetes 的 Limits 本质上是同一类思想——都是给进程或用户设定一个资源上限防止某一个实体拖垮整个系统。只是 AIX 的 ulimit 是操作系统层面的Kubernetes 的 Limits 是容器和集群层面的两者解决的问题非常相似排查问题时可以互相启发。4.3 案例三外部 API 429 Too Many Requests资源管理并不只在容器内部。很多云服务或外部 API 也有一套“限额”机制最常见的报错就是429 Too Many Requests。它和 Kubernetes 的关系在于你的应用如果配置了过高的 limits 或并发控制不当会在某个时段向外部 API 突发大量请求被对方限流后返回 429。处理办法通常是在代码里做重试退避而不是无限重试。另一个层面Kubernetes API Server 本身也有请求限流保护比如--max-requests-inflight和--max-mutating-requests-inflight参数超过了会直接拒绝并返回 429。换句话说整个云原生体系里的“资源管理”从 Pod 的 CPU/内存到 API 的 QPS本质上都是同一种哲学给资源设限防止系统失控。排查这类问题时的几条命令可以快速收敛范围# 查看 Pod 当前状态和重启原因 kubectl get pod pod-name -o wide kubectl describe pod pod-name # 查看节点资源水位 kubectl top node kubectl top pod -n production # 查看控制器事件 kubectl get events --sort-by.lastTimestamp记住一个原则先看事件后看日志。事件里有调度失败、驱逐、OOM 这些系统级结论比慢慢翻业务日志高效得多。4.4 面试高频题Requests/Limits/QoS 怎么答资源管理几乎是 Kubernetes 面试必考内容这里挑几个高频问题做个答题模板既不啰嗦又能踩中考点。问题一Pod 内存超过 limits 会发生什么答容器会被内核 OOM Killer 杀掉Pod 重启或者进入 CrashLoopBackOff。因为内存是不可压缩资源内核没有别的办法释放内存只能杀掉超过 cgroup 限制的进程。QoS 等级会影响被杀的优先级Guaranteed 的进程 oom_score_adj 很低不容易被杀BestEffort 的进程几乎必然先被杀。问题二调度器根据 requests 还是 limits 调度答根据 requests。调度器只关心你“至少要多少”不关心“最多能用多少”。所以 limits 设得再大也不会影响调度决策但是会影响运行时的资源上限。这也是为什么很多人发现节点明明 saturate 了实际 CPU 利用率却不高——就是 requests 写得太小调度时把太多 Pod 塞进来了。问题三不同 QoS 的驱逐顺序是什么答节点内存压力或磁盘压力时驱逐顺序是 BestEffort Burstable Guaranteed。并且驱逐时会优先选择使用超 requests 资源最多的 Burstable Pod。这些问题看似简单但每个背后都要能展开讲出原理面试官最反感只会背结论不会说原因的人。5. 资源治理的进阶方向设置好 requests/limits 只是开始真正的资源管理是一个长期迭代的过程。下面几条是我在实践中总结出的进阶思路适合已经有一定 Kubernetes 基础、想进一步改进集群资源利用率的团队。5.1 从手动配置到 VPA 自动推荐手动调参最大的问题是不持续。服务容量变了、流量变了原来配的资源就不合适了。Vertical Pod AutoscalerVPA可以分析 Pod 的历史资源使用情况给出推荐的 requests/limits。它有两种工作模式Off 模式只推荐不自动修改。Auto 模式更新 Deployment 的资源配置并重建 Pod让新配置生效。VPA 的 YAML 也简单核心是一个 CustomResourceDefinitionapiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: orders-api-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: orders-api updatePolicy: updateMode: Auto resourcePolicy: containerPolicies: - containerName: * minAllowed: cpu: 100m memory: 128Mi maxAllowed: cpu: 2 memory: 4Gi我在生产环境推荐先跑几天Off模式等 VPA 给出建议后人工审核一次再把模式切成Auto。直接上 Auto 有风险因为 VPA 的推荐值偶尔会在某些极端监控数据上出偏差把 requests 调得过高导致调度失败或者成本上升。5.2 HPA 与资源预留的配合Horizontal Pod AutoscalerHPA解决的是“需要多少个副本”的问题而 requests/limits 解决的是“每个副本多大”的问题。两者之间必须配合。如果每个 Pod 的 requests 设得过大HPA 扩容时节点很容易就被塞满扩不出新的副本如果设得过小HPA 会频繁扩容造成大量不必要的 Pod。我建议给业务设置 HPA 时requests 要按“稳态负载”来设而不是按峰值来设。扩缩容本身的机制就是应对峰值的你要是给 requests 留了太多余量HPA 永远也触发不了扩容等于白配。5.3 结合业务特征的资源分层不同业务对资源保障的需求千差万别我用一个简单的表格总结一下我自己常用的配置策略业务类型QoS 目标requests 策略limits 策略数据库、注册中心等有状态核心组件Guaranteed等于 limits等于 requests在线 API 服务Burstable接近 Guaranteed按 P95 使用量设置requests 的 1.5~2 倍异步消费者、定时任务Burstable按 P50 使用量设置requests 的 2~3 倍开发测试环境任务BestEffort可不设置用 LimitRange 默认一次性批处理任务Burstable按最大使用量设置可等于 requests这里的核心思想是核心业务不求资源利用率高但求稳定边缘任务尽量压低 requests提高整体资源效率。不要奢望一个公式打天下。5.4 持续治理路线图资源管理最忌“配完不再看”。我建议每个团队至少每个月做一次资源复盘具体动作如下用监控看每个工作负载过去 7 天的 CPU/内存水位找出长期严重超限或者长期低负载的 Pod。用kubectl get deploy -A -o custom-columns导出每个 Deployment 的 requests/limits和监控数据交叉比对标记明显不合理的配置。对标记出的服务执行 VPA 推荐经过评审后统一修改。在 CI 流水线中加入资源字段校验比如强制要求生产环境 Deployment 必须同时设置 requests 和 limits否则不通。最后分享一个小技巧想快速找出集群里没有配置资源请求的 Pod一行命令搞定kubectl get pods -A -o jsonpath{range .items[?(.spec.containers[*].resources.requestsnil)]}{.metadata.namespace}/{.metadata.name}{\n}{end}输出结果就是所有未设置 requests 的 Pod这个清单往往是最容易出问题的一批。我每次巡检集群总会先从这份名单开始处理。资源管理没有一劳永逸的答案但只要把 Requests、Limits 和 QoS 这几个基本概念吃透再结合实际数据持续调优你就能让集群跑得更稳也让自己在排障时多一分从容。