ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Kubernetes从入门到精通】第32篇:ResourceQuota——多团队共享集群的“公平秤“

【Kubernetes从入门到精通】第32篇:ResourceQuota——多团队共享集群的“公平秤“

上一篇【第31篇】LimitRange——给你的Namespace画个“圈“
下一篇【第33篇】Pod生命周期全解——从Pending到Terminating的每一步


摘要

上篇LimitRange解决了"单个Pod不能太离谱"的问题,但新问题来了:就算每个Pod都规规矩矩地配了500m CPU和512Mi内存,一个团队可以创建100个这样的Pod啊!团队A不小心创建了200个Pod,团队B的Pod就调度不下了——这就是"公地悲剧"。

ResourceQuota就是来管这事的。它作用在Namespace级别,给整个Namespace设资源上限——你们团队总共只能用20核CPU、40Gi内存、最多50个Pod。超过配额?拒绝创建。而且它不光管计算资源(requests/limits的CPU和内存),还能管"对象数量"——这个Namespace最多创建10个Service、5个Ingress、3个PVC。

本文把ResourceQuota的两种配额掰碎讲清楚,带你看Scope作用域的精妙之处,最后用工ResourceQuota+LimitRange的组合拳实现按团队划分资源池——团队A分12核24Gi,团队B分8核16Gi,各玩各的互不抢。


一、ResourceQuota vs LimitRange——“总面积"vs"房间标准”

1.1 两者的作用域对比

【ResourceQuota vs LimitRange——一个管总量,一个管个体】 公寓楼比喻: ┌─────────────────────────────────────────────────────────┐ │ │ │ LimitRange = 房间装修标准 │ │ ┌────────────────────────────────────────────┐ │ │ │ "每间房的面积必须在 20-50 平米之间" │ │ │ │ "每间房的层高不能超过 3 米" │ │ │ │ "如果没标注面积,默认30平米" │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ResourceQuota = 整层楼的总面积上限 │ │ ┌────────────────────────────────────────────┐ │ │ │ "这层楼的总面积不能超过 500 平米" │ │ │ │ "这层楼最多住 20 个租户" │ │ │ │ "这层楼最多 5 个独立卫生间" │ │ │ └────────────────────────────────────────────┘ │ │ │ │ 两者配合: │ │ ┌────────────────────────────────────────────┐ │ │ │ LimitRange 保证每间房不会太大或太小 │ │ │ │ ResourceQuota 保证整层楼的使用不超过上限 │ │ │ │ → 不管租户怎么装修,总面积就是500平米 │ │ │ └────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘
特性LimitRangeResourceQuota
作用范围单个Pod/Container/PVC整个Namespace
管什么个体资源的上下限/默认值资源总和上限
约束CPU/内存的 min/max/defaultCPU/内存的requests/limits总和
额外能力自动填充默认值限制对象数量(Pod/Service等)
生效时机创建Pod时创建任何资源时
类比房间的施工标准整层楼的面积上限

二、ResouceQuota的两种配额类型

2.1 计算资源配额——“你们团队总共能用多少CPU/内存”

apiVersion:v1kind:ResourceQuotametadata:name:team-a-quotanamespace:team-aspec:hard:# ==========================================# 计算资源配额——requests# ==========================================requests.cpu:"12"# 所有Pod的CPU requests总和 ≤ 12核requests.memory:"24Gi"# 所有Pod的内存requests总和 ≤ 24Gi# ==========================================# 计算资源配额——limits# ==========================================limits.cpu:"24"# 所有Pod的CPU limits总和 ≤ 24核limits.memory:"48Gi"# 所有Pod的内存limits总和 ≤ 48Gi# ==========================================# 对象数量配额# ==========================================count/pods:"30"# 最多30个Podcount/services:"10"# 最多10个Servicecount/services.nodeports:"2"# 最多2个NodePort Servicecount/secrets:"20"# 最多20个Secretcount/configmaps:"30"# 最多30个ConfigMapcount/persistentvolumeclaims:"5"# 最多5个PVCcount/deployments.apps:"15"# 最多15个Deploymentcount/ingresses.networking.k8s.io:"3"# 最多3个Ingresscount/jobs.batch:"10"# 最多10个Job# ==========================================# 存储配额# ==========================================requests.storage:"100Gi"# 所有PVC的存储requests总和 ≤ 100Gi# 特定StorageClass的配额fast-ssd.storageclass.storage.k8s.io/requests.storage:"50Gi"# SSD类型的PVC总和不超过50Gi# ==========================================# 扩展资源配额(如GPU)# ==========================================requests.nvidia.com/gpu:"4"# 最多请求4块GPUlimits.nvidia.com/gpu:"4"# 最多限制4块GPU
【计算资源配额——requests和limits各算各的】 名称格式: <动作>.<资源> ┌────────────────────────────────────────────┐ │ │ │ requests.cpu = 所有Pod cpu.requests 的和 │ │ requests.memory = 所有Pod mem.requests 的和 │ │ limits.cpu = 所有Pod cpu.limits 的和 │ │ limits.memory = 所有Pod mem.limits 的和 │ │ │ │ 注意:requests 和 limits 是独立计算的! │ │ requests 用完不影响 limits 余额 │ │ limits 用完不影响 requests 余额 │ │ │ │ 举例: │ │ requests.cpu: 10, limits.cpu: 20 │ │ 创建 Pod (requests=2, limits=4) │ │ → requests 剩余: 10-2=8 │ │ → limits 剩余: 20-4=16 │ │ 两个池子独立,互不干扰 │ └────────────────────────────────────────────┘

2.2 对象数量配额——“不光是资源,数量也有限”

apiVersion:v1kind:ResourceQuotametadata:name:object-countsnamespace:team-aspec:hard:# 常用对象数量限制count/pods:"50"# 最多50个Podcount/services:"20"# 最多20个Servicecount/configmaps:"50"# 最多50个ConfigMapcount/secrets:"30"# 最多30个Secretcount/persistentvolumeclaims:"10"# 最多10个PVC# 特定类型的Servicecount/services.loadbalancers:"1"# 最多1个LoadBalancer(贵!)count/services.nodeports:"3"# 最多3个NodePort(端口范围有限)# 工作负载对象count/deployments.apps:"20"# 最多20个Deploymentcount/statefulsets.apps:"5"# 最多5个StatefulSetcount/jobs.batch:"20"# 最多20个Job(包含已完成+运行中)count/cronjobs.batch:"5"# 最多5个CronJob
# 创建后验证kubectl apply-fteam-a-quota.yaml# 查看所有ResourceQuotakubectl get resourcequota-nteam-a# NAME AGE REQUEST LIMIT# team-a-quota 5m requests.cpu: 0/12, requests.memory: 0/24Gi limits.cpu: 0/24# 查看详细使用情况kubectl describe resourcequota team-a-quota-nteam-a# Name: team-a-quota# Namespace: team-a# Resource Used Hard# -------- ---- ----# limits.cpu 5 24 ← 已用5核/总共24核# limits.memory 10Gi 48Gi# requests.cpu 3 12# requests.memory 6Gi 24Gi# count/pods 8 30# count/services 3 10

要点:对象数量配额里count/pods包括所有状态的Pod——Running、Pending甚至Completed的Job Pod。如果你的Job创建了大量Completed Pod后没清理,它们会一直占用count/pods配额,导致新Pod创建失败。所以记得给Job设ttlSecondsAfterFinished自动清理。


三、Scope作用域——“限制更精准”

3.1 四种Scope——“我只管某一类资源”

【Scope 作用域——"不是所有Pod都算在内"】 ┌─────────────────────────────────────────────────────────┐ │ │ │ Terminating vs NotTerminating: │ │ ┌───────────────────────────────────────────────┐ │ │ │ Terminating: 只统计 activeDeadlineSeconds > 0 │ │ │ │ 的Pod(即Job/定时任务Pod) │ │ │ │ NotTerminating: 只统计没有设activeDeadline的 │ │ │ │ Pod(即长期运行的服务Pod) │ │ │ │ │ │ │ │ 举个例子: │ │ │ │ 你希望团队最多10个长期Pod + 不限量临时Job Pod │ │ │ │ → 两条独立Quota:NotTerminating=10, │ │ │ │ Terminating=不限 │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ BestEffort vs NotBestEffort: │ │ ┌───────────────────────────────────────────────┐ │ │ │ BestEffort: 只统计BestEffort QoS的Pod │ │ │ │ NotBestEffort: 只统计Burstable或Guaranteed的 │ │ │ │ Pod(即至少配了requests的) │ │ │ │ │ │ │ │ 举个例子: │ │ │ │ 你希望核心服务都用Guaranteed QoS │ │ │ │ → BestEffort: 0(禁止创建不配资源的Pod) │ │ │ └───────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘

3.2 Scope实战——“禁止BestEffort Pod,给长期服务单独配额”

apiVersion:v1kind:ResourceQuotametadata:name:production-quotanamespace:productionspec:# 硬性要求:所有统计都是按Scope过滤的hard:# Scope 1: 只对长期服务Pod(NotTerminating)生效requests.cpu:"10"requests.memory:"20Gi"limits.cpu:"20"limits.memory:"40Gi"count/pods:"20"scopeSelector:matchExpressions:-operator:InscopeName:NotTerminating# ← 只统计服务Pod# Job Pod也算requests,但不计入这个Quota---apiVersion:v1kind:ResourceQuotametadata:name:besteffort-bannamespace:productionspec:hard:count/pods:"0"# ← 0!直接禁止scopeSelector:matchExpressions:-operator:InscopeName:BestEffort# ← 只对BestEffort Pod生效# 效果:谁创建没配resources的Pod,直接拒绝!
# 测试:创建BestEffort Pod——被拒kubectl apply-nproduction-f-<<EOF apiVersion: v1 kind: Pod metadata: name: no-resources spec: containers: - name: nginx image: nginx # 没配resources → BestEffort EOF# Error: pods "no-resources" is forbidden:# exceeded quota: besteffort-ban, requested: count/pods=1,# used: count/pods=0, limited: count/pods=0

要点:用Scope禁止BestEffort是生产环境的标配操作——你可以在ResourceQuota里加一条scopeName: BestEffort, count/pods: 0,确保团队里没有人能创建没配resources的Pod。加上上一节的LimitRange自动补默认值,双保险——从源头杜绝BestEffort。

3.3 四种Scope详解表格

Scope匹配的Pod典型用途
Terminating设了activeDeadlineSeconds的Pod(Job Pod)给批处理任务单独配额,不跟Web服务抢
NotTerminating没设activeDeadlineSeconds的Pod(服务Pod)给长期运行的服务设配额
BestEffort没有任何resources的Pod禁止BestEffort Pod(设为0)
NotBestEffort至少配了requests或limits的Pod只对有资源配置的Pod设配额
PriorityClass指定优先级的Pod(如high-priority)给不同优先级的Pod分别配额

四、实战——ResourceQuota + LimitRange组合拳

4.1 按团队划分资源池

【多团队资源池规划——5台Node,总计20C/64Gi】 集群物理资源: ┌─────────────────────────────────────────────────────────┐ │ Node-1: 4C/16Gi Node-2: 4C/16Gi Node-3: 4C/16Gi │ │ Node-4: 4C/16Gi Node-5: 4C/16Gi │ │ │ │ 可分配总量:20C CPU / 64Gi 内存 │ └─────────────────────────────────────────────────────────┘ 资源分配计划: ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │ 团队 │ CPURq │ MemRq │ CPULim │ MemLim │ Pod上限 │ ├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤ │ team-a │ 8核 │ 20Gi │ 12核 │ 30Gi │ 40 │ │ (核心) │ │ │ │ │ │ ├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤ │ team-b │ 6核 │ 16Gi │ 10核 │ 24Gi │ 30 │ │ (业务) │ │ │ │ │ │ ├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤ │ team-c │ 3核 │ 8Gi │ 6核 │ 12Gi │ 20 │ │ (数据) │ │ │ │ │ │ ├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤ │ 系统 │ 3核 │ 20Gi │ 12核 │ 30Gi │ — │ │ (预留) │ │ │ │ │ │ ├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤ │ 合计 │ 20核 │ 64Gi │ 40核 │ 96Gi │ — │ └──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘

4.2 完整实现——一个团队一个"保险柜"

# ==========================================# team-a: ResourceQuota + LimitRange 组合# ==========================================# 1. ResourceQuota——总量控制apiVersion:v1kind:ResourceQuotametadata:name:team-a-quotanamespace:team-aspec:hard:# 计算资源requests.cpu:"8"requests.memory:"20Gi"limits.cpu:"12"limits.memory:"30Gi"# 对象数量count/pods:"40"count/services:"15"count/services.nodeports:"2"# NodePort有限,省着用count/configmaps:"30"count/secrets:"20"count/persistentvolumeclaims:"10"# 存储requests.storage:"200Gi"---# 2. LimitRange——质量规范apiVersion:v1kind:LimitRangemetadata:name:team-a-limitsnamespace:team-aspec:limits:-type:Containermin:cpu:"100m"memory:"128Mi"max:cpu:"2000m"# 单个容器最多2核memory:"8Gi"default:cpu:"500m"memory:"512Mi"defaultRequest:cpu:"200m"memory:"256Mi"maxLimitRequestRatio:cpu:"2"memory:"1.5"---# 3. 禁止BestEffortapiVersion:v1kind:ResourceQuotametadata:name:team-a-no-besteffortnamespace:team-aspec:hard:count/pods:"0"scopeSelector:matchExpressions:-operator:InscopeName:BestEffort
# ==========================================# team-b: ResourceQuota + LimitRange 组合# ==========================================apiVersion:v1kind:ResourceQuotametadata:name:team-b-quotanamespace:team-bspec:hard:requests.cpu:"6"requests.memory:"16Gi"limits.cpu:"10"limits.memory:"24Gi"count/pods:"30"count/services:"10"count/persistentvolumeclaims:"8"---apiVersion:v1kind:LimitRangemetadata:name:team-b-limitsnamespace:team-bspec:limits:-type:Containermin:cpu:"50m"memory:"64Mi"max:cpu:"2000m"memory:"8Gi"default:cpu:"500m"memory:"512Mi"defaultRequest:cpu:"200m"memory:"256Mi"maxLimitRequestRatio:cpu:"3"memory:"2"
# 查看各团队资源使用情况汇总echo"=== Resource Usage Summary ==="fornsinteam-a team-b team-c;doecho""echo"--- Namespace:$ns---"kubectl describe resourcequota-n$ns2>/dev/null|grep-E"Name:|Resource|Used|Hard"done# 输出示例:# --- Namespace: team-a ---# Name: team-a-quota# Resource Used Hard# limits.cpu 6 12# limits.memory 15Gi 30Gi# requests.cpu 3 8# requests.memory 8Gi 20Gi# count/pods 12 40# --- Namespace: team-b ---# Name: team-b-quota# Resource Used Hard# limits.cpu 4 10# ...

4.3 配额超限时的表现

# 模拟team-a配额用满# 假设team-a的requests.cpu已用到7.8核(配额8核)# 尝试创建一个request为500m的Podkubectl apply-nteam-a-f-<<EOF apiVersion: v1 kind: Pod metadata: name: new-app spec: containers: - name: nginx image: nginx resources: requests: cpu: "500m" # 7.8 + 0.5 = 8.3 > 8 → 超了! memory: "256Mi" EOF# 结果:Pod创建失败!# Error from server (Forbidden): error when creating "STDIN":# pods "new-app" is forbidden: exceeded quota: team-a-quota,# requested: requests.cpu=500m, used: requests.cpu=7800m,# limited: requests.cpu=8# 查看ReplicaSet/DaemonSet里的Pending Podkubectl get events-nteam-a --field-selectorreason=FailedCreate# 会看到类似 "exceeded quota" 的事件

要点:ResourceQuota配额超限时,API Server直接拒绝创建——不是Pending,是直接403 Forbidden。这意味着你的Deployment期望replicas=5,但由于配额不够只创建了3个,ReplicaSet Controller会不断重试创建那2个失败的Pod——Events里会刷屏"exceeded quota"。这时候要么加配额,要么减少副本数。


五、ResourceQuota常用排错

# 查看配额使用情况kubectl describe resourcequota-nteam-a# 找出哪些Pod占用了配额kubectl get pods-nteam-a-ocustom-columns=\NAME:.metadata.name,\CPU_REQ:.spec.containers[*].resources.requests.cpu,\MEM_REQ:.spec.containers[*].resources.requests.memory,\CPU_LIM:.spec.containers[*].resources.limits.cpu,\MEM_LIM:.spec.containers[*].resources.limits.memory# 按CPU requests排序找大头kubectl get pods-nteam-a-ojson|jq-r' .items[] | "\(.metadata.name) \( (.spec.containers[].resources.requests.cpu // "0") )"'|sort-k2-r# 注意:终止中的Pod(Terminating)仍然占用配额!# 如果Pod卡在Terminating,配额被占着不放kubectl get pods-nteam-a --field-selectorstatus.phase=Terminating# → 如果发现僵尸Pod,用 --force --grace-period=0 删除# 更新ResourceQuotakubectl edit resourcequota team-a-quota-nteam-a# 或者patchkubectl patch resourcequota team-a-quota-nteam-a\--patch'{"spec":{"hard":{"requests.cpu":"10"}}}'

本篇小结

ResourceQuota是集群级别的"公平秤",确保每个团队不会吃掉别人的资源:

  1. 管两类额度:计算资源总量(requests/limits的CPU和内存总和)+ 对象数量(Pod/Service/ConfigMap/PVC等最多多少个)
  2. requests和limits独立计算——两个池子互不干扰,用完一个不影响另一个
  3. Scope作用域让限制更精准——可以只限长期服务Pod、禁止BestEffort、给不同优先级分别配额
  4. ResourceQuota + LimitRange 组合拳——LimitRange管每个Pod怎么配,ResourceQuota管全Namespace能用多少
  5. 配额超限直接拒绝——不是Pending,是API Server直接403,需要扩容配额或减少资源消耗

至此咱们把资源管理的三件套(Requests/Limits → QoS → LimitRange → ResourceQuota)都聊完了——从单个Pod到全集群都有了规矩。下一篇切换视角,看看Pod自己的一生——从Pending到Terminating的每一步,Init容器、生命周期钩子这些细节。


上一篇【第31篇】LimitRange——给你的Namespace画个“圈“
下一篇【第33篇】Pod生命周期全解——从Pending到Terminating的每一步


返回列表