第 36 篇 k8s之资源管理:Requests、Limits 与 QoS
IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在前面的文章中,我们为 Redis 配置了持久化存储,为 Flask 配置了健康检查和滚动更新。我们的应用越来越“生产化”了。但还有一个关键问题没有解决:Pod 应该占用多少 CPU 和内存?如果某个 Pod 突然内存泄漏,会不会拖垮同一台节点上的其他 Pod?
在 Docker Compose 时代,这个问题不那么突出——一台机器上跑的服务数量有限,资源竞争不激烈。但在 K8s 集群中,一台节点可能同时运行几十个 Pod,资源的合理分配直接决定了集群的稳定性和调度效率。
Kubernetes 提供了两个核心参数来管理容器资源:Requests(资源请求)和Limits(资源上限)。它们共同决定了 Pod 会被调度到哪个节点、运行时的资源使用上限,以及在资源紧张时 Pod 的“优先级”。今天我们就来彻底搞懂这套机制,并把它应用到贯穿案例的 Flask + Redis 应用中。
一、Requests 和 Limits:资源管理的两个维度
1.1 基本概念
Requests 和 Limits 分别回答了两个不同的问题:
Requests:“调度时,我需要至少多少资源?” Scheduler 根据 Requests 为 Pod 选择节点——节点上所有 Pod 的 Requests 总和不能超过节点的总资源。这是调度的“准入线”。
Limits:“运行时,我最多能用多少资源?” 容器运行时的资源使用不能超过 Limits。CPU 超限会被限流(变慢),内存超限会被杀掉(OOMKilled)。这是运行的“天花板”。
用一个日常类比来理解:Requests 是你在餐厅预订的座位数量——餐厅只有在你预订的座位有空时才让你进。Limits 是你能点的菜的上限——你不能超过这个上限消费。你可以点菜(实际使用)少于预订座位数,但不能超过菜的上限。
1.2 可管理的资源类型
K8s 主要管理两种资源:
CPU:以“核”(core)为单位。1 CPU = 1 个物理/虚拟核心。可以用小数表示,如
0.5(半个核)、250m(250 millicpu = 0.25 核)。m后缀表示千分之一核——100m是 0.1 核。这是 CPU 最常用的计量单位。内存(Memory):以字节为单位。常见单位:
Mi(Mebibyte,2^20 字节)、Gi(Gibibyte,2^30 字节)。注意M(Megabyte,10^6 字节)和Mi的区别——K8s 使用二进制单位Mi/Gi。
1.3 CPU 和内存的超额行为
这两种资源在超过 Limits 时的行为截然不同:
CPU 是可压缩资源:容器超过 CPU Limits 时,K8s 不会杀死容器,而是限流(throttle)——让容器变慢,等待下次调度。这就像高速公路的限速器,你没法超过限速,但车不会停。
内存是不可压缩资源:容器超过内存 Limits 时,K8s 必须立即杀死容器(OOMKill,Out of Memory Kill)。内存不像 CPU 可以“等一等再分配”——一旦进程申请了内存,就必须立即满足。如果容器占用的内存超过了 Limits,kubelet 会直接杀掉容器,Pod 的
RESTARTS计数加 1,LAST STATE显示OOMKilled。
二、三种 QoS 等级
K8s 根据容器的 Requests 和 Limits 配置,自动为 Pod 分配一个QoS(Quality of Service,服务质量)等级。这个等级决定了在节点资源紧张时,哪些 Pod 优先被驱逐(Eviction)。
三种 QoS 等级的驱逐优先级背后是一个简单的原则:为资源做出明确承诺(Guaranteed)的 Pod 享有最高的保护,完全不做承诺(BestEffort)的 Pod 在资源紧张时最先被牺牲。如果你有一个 BestEffort 等级的批处理任务和一个 Guaranteed 等级的核心业务 Pod,当节点内存不足时,kubelet 会优先驱逐批处理任务。
三、动手配置并验证
3.1 为贯穿案例配置资源限制
将 Requests 和 Limits 应用到 Flask 应用的生产级 Deployment 中:
apiVersion: apps/v1 kind: Deployment metadata: name: flask-deployment spec: replicas:3selector: matchLabels: app: flask-counter template: spec: containers: - name: flask image: flask-redis-counter:3.0 resources: requests: cpu:"100m"memory:"128Mi"limits: cpu:"500m"memory:"256Mi"配置解读:Requests 为每个 Flask Pod 请求 100m CPU 和 128Mi 内存,Scheduler 确保节点上有足够资源才调度。Limits 限制每个 Pod 最多使用 500m CPU 和 256Mi 内存。这个 Pod 的 QoS 等级是 Burstable(Requests ≠ Limits)。
部署后验证资源配置:
kubectl apply-fflask-deployment-resources.yaml kubectl get pods-lapp=flask-counter# NAME READY STATUS RESTARTS AGE# flask-deployment-xxxxxxxxx-xxxxx 1/1 Running 0 30s查看 QoS 等级:
kubectl get pod<pod-name>-ojsonpath='{.status.qosClass}'# Burstable3.2 对比三种 QoS 等级的行为
BestEffort(最不稳定):
containers: - name: besteffort-demo image: alpine command:["stress","--vm","1","--vm-bytes","200M"]# 不设置 resources → QoS = BestEffortBurstable(中等):
containers: - name: burstable-demo image: alpine command:["stress","--vm","1","--vm-bytes","200M"]resources: requests: memory:"64Mi"limits: memory:"128Mi"# Requests < Limits → QoS = BurstableGuaranteed(最稳定):
containers: - name: guaranteed-demo image: alpine command:["stress","--vm","1","--vm-bytes","200M"]resources: requests: memory:"128Mi"cpu:"100m"limits: memory:"128Mi"cpu:"100m"# Requests = Limits → QoS = Guaranteed当节点内存不足时,kubelet 会按 BestEffort → Burstable → Guaranteed 的顺序驱逐 Pod。对于生产环境的核心服务,建议设置 Requests = Limits 来获得 Guaranteed 等级。但注意,这也会导致资源预留更多——即使容器实际只用 50Mi 内存,K8s 也会为它预留完整的 128Mi。
3.3 如何合理设定 Requests 和 Limits?
Requests 的设定依据——监控数据:
Requests 应该基于应用在正常运行时的实际资源消耗来设定,而不是凭感觉。你可以通过kubectl top pod观察应用在稳定状态下的 CPU 和内存使用,将其作为 Requests 的基准值。例如,Flask 应用在无负载时使用约 30Mi 内存,那么 Requests 设为 64Mi 是合理的,既能保证调度正确,又不会预留过多空闲资源。
Limits 的设定依据——压测数据:
Limits 应该基于应用的峰值资源需求来设定。通过压测工具模拟高并发流量,观察 CPU 和内存的峰值,再留出一定的余量。对于内存,余量建议在 20%-30% 左右,以应对突发流量。对于 CPU,Limits 可以设为 Requests 的 3-5 倍,利用 CPU 的可压缩特性,让应用在流量高峰时能“借用”空闲 CPU 资源,平时则释放给其他 Pod。
避免过度限制:
Requests 设置得过高会浪费集群资源(节点上实际空闲的资源被预留,无法调度新 Pod)。Limits 设置得过低会导致 OOMKill(内存)或性能严重下降(CPU 被过度限流)。如果你不确定,可以用kubectl top pod观察一段时间后再做调整,而不是一开始就设置非常严格的 Limits。
四、LimitRange 与 ResourceQuota
单个容器的 Requests/Limits 解决了 Pod 级别的资源约束。但在多团队共享集群时,还需要命名空间级别的资源管控。
4.1 LimitRange
LimitRange 为命名空间中的 Pod 和容器设置默认的 Requests/Limits,防止团队创建没有任何资源约束的 Pod:
apiVersion: v1 kind: LimitRange metadata: name: default-limits spec: limits: - type: Container default: cpu:"500m"memory:"256Mi"defaultRequest: cpu:"100m"memory:"128Mi"max: cpu:"2"memory:"1Gi"min: cpu:"50m"memory:"64Mi"这样设置后,该命名空间中新创建的容器如果不指定resources,会自动使用defaultRequest和default。同时,任何容器都不能超过max或低于min。
4.2 ResourceQuota
ResourceQuota 限制整个命名空间的资源使用总量,防止某个团队过度消耗集群资源:
apiVersion: v1 kind: ResourceQuota metadata: name: team-quota spec: hard: requests.cpu:"10"requests.memory:"20Gi"limits.cpu:"20"limits.memory:"40Gi"persistentvolumeclaims:"10"当命名空间中的资源使用超过 Quota 时,新的 Pod 创建请求会被拒绝。LimitRange 和 ResourceQuota 通常一起使用,形成命名空间级别的资源管控体系。
五、ResourceQuota 与 QoS 的协同
ResourceQuota 和 QoS 等级在资源紧张时形成互补的决策链路。当节点内存不足时,kubelet 首先按照 QoS 等级排出 Pod 的驱逐优先级(BestEffort → Burstable → Guaranteed),但在同一 QoS 等级内部,会优先驱逐那些超出 Requests 更多的 Pod。
这意味着什么?如果你有两个 Burstable 等级的 Pod,一个内存 Requests 为 128Mi,实际使用了 500Mi(超出 372Mi),另一个 Requests 为 256Mi,实际使用了 300Mi(超出 44Mi),kubelet 会优先驱逐前者——它在同一 QoS 等级中“超量”使用更多。这也是为什么将 Requests 设定为真实平均使用量如此重要:它不仅仅是调度凭证,更是资源紧张时的保护机制。
六、与 Docker Compose 的对比
在 Docker Compose 中,资源限制是可选的,且相对简单:
deploy: resources: limits: cpus:'0.50'memory: 256M但在 K8s 中,Requests 和 Limits 是两个独立的维度,它们的交互产生了 QoS 等级、驱逐优先级、调度策略等复杂的机制。Compose 没有“资源请求”的概念,所以调度(选择哪台宿主机)完全是手动的。K8s 的 Scheduler 则依据 Requests 自动做出最优决策,大大降低了大规模集群中资源分配的运维成本。
七、命令速查表
八、本篇总结
Requests:调度依据,声明 Pod 需要的最小资源。Scheduler 确保节点资源足够,Requests 总和不能超过节点总资源。
Limits:运行上限,限制 Pod 能使用的最大资源。CPU 超限被限流,内存超限被 OOMKill。
QoS 等级:由 Requests 和 Limits 的配置关系自动决定,分为 Guaranteed、Burstable、BestEffort 三个等级,决定了资源紧张时 Pod 被驱逐的优先级。
配置原则:Requests 基于监控数据设定为平均使用量,Limits 基于压测数据设定为峰值余量。生产核心服务建议设置为 Guaranteed(Requests = Limits)。
LimitRange 和 ResourceQuota:从命名空间层面确保资源使用的规范和公平,是多团队共享集群的必备管控手段。
通过本篇,你的 Pod 不会再“无限制地吃资源”,集群的稳定性和可预测性得到了质的提升。下一篇——第 37 篇:调度进阶:亲和性、污点与容忍,我们将学习如何精细控制 Pod 应该被调度到哪些节点上,实现更高级的调度策略。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !
