【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平米 │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
| 特性 | LimitRange | ResourceQuota |
|---|---|---|
| 作用范围 | 单个Pod/Container/PVC | 整个Namespace |
| 管什么 | 个体资源的上下限/默认值 | 资源总和上限 |
| 约束 | CPU/内存的 min/max/default | CPU/内存的requests/limits总和 |
| 额外能力 | 自动填充默认值 | 限制对象数量(Pod/Service等) |
| 生效时机 | 创建Pod时 | 创建任何资源时 |
| 类比 | 房间的施工标准 | 整层楼的面积上限 |
二、ResouceQuota的两种配额类型
2.1 计算资源配额——“你们团队总共能用多少CPU/内存”
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
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个Pod
count/services: "10" # 最多10个Service
count/services.nodeports: "2" # 最多2个NodePort Service
count/secrets: "20" # 最多20个Secret
count/configmaps: "30" # 最多30个ConfigMap
count/persistentvolumeclaims: "5" # 最多5个PVC
count/deployments.apps: "15" # 最多15个Deployment
count/ingresses.networking.k8s.io: "3" # 最多3个Ingress
count/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块GPU
limits.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: v1
kind: ResourceQuota
metadata:
name: object-counts
namespace: team-a
spec:
hard:
# 常用对象数量限制
count/pods: "50" # 最多50个Pod
count/services: "20" # 最多20个Service
count/configmaps: "50" # 最多50个ConfigMap
count/secrets: "30" # 最多30个Secret
count/persistentvolumeclaims: "10" # 最多10个PVC
# 特定类型的Service
count/services.loadbalancers: "1" # 最多1个LoadBalancer(贵!)
count/services.nodeports: "3" # 最多3个NodePort(端口范围有限)
# 工作负载对象
count/deployments.apps: "20" # 最多20个Deployment
count/statefulsets.apps: "5" # 最多5个StatefulSet
count/jobs.batch: "20" # 最多20个Job(包含已完成+运行中)
count/cronjobs.batch: "5" # 最多5个CronJob
# 创建后验证
kubectl apply -f team-a-quota.yaml
# 查看所有ResourceQuota
kubectl get resourcequota -n team-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 -n team-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: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
# 硬性要求:所有统计都是按Scope过滤的
hard:
# Scope 1: 只对长期服务Pod(NotTerminating)生效
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
count/pods: "20"
scopeSelector:
matchExpressions:
- operator: In
scopeName: NotTerminating # ← 只统计服务Pod
# Job Pod也算requests,但不计入这个Quota
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: besteffort-ban
namespace: production
spec:
hard:
count/pods: "0" # ← 0!直接禁止
scopeSelector:
matchExpressions:
- operator: In
scopeName: BestEffort # ← 只对BestEffort Pod生效
# 效果:谁创建没配resources的Pod,直接拒绝!
# 测试:创建BestEffort Pod——被拒
kubectl apply -n production -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: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
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: v1
kind: LimitRange
metadata:
name: team-a-limits
namespace: team-a
spec:
limits:
- type: Container
min:
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. 禁止BestEffort
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-no-besteffort
namespace: team-a
spec:
hard:
count/pods: "0"
scopeSelector:
matchExpressions:
- operator: In
scopeName: BestEffort
# ==========================================
# team-b: ResourceQuota + LimitRange 组合
# ==========================================
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-b-quota
namespace: team-b
spec:
hard:
requests.cpu: "6"
requests.memory: "16Gi"
limits.cpu: "10"
limits.memory: "24Gi"
count/pods: "30"
count/services: "10"
count/persistentvolumeclaims: "8"
---
apiVersion: v1
kind: LimitRange
metadata:
name: team-b-limits
namespace: team-b
spec:
limits:
- type: Container
min:
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 ==="
for ns in team-a team-b team-c; do
echo ""
echo "--- Namespace: $ns ---"
kubectl describe resourcequota -n $ns 2>/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的Pod
kubectl apply -n team-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 Pod
kubectl get events -n team-a --field-selector reason=FailedCreate
# 会看到类似 "exceeded quota" 的事件
要点:ResourceQuota配额超限时,API Server直接拒绝创建——不是Pending,是直接403 Forbidden。这意味着你的Deployment期望replicas=5,但由于配额不够只创建了3个,ReplicaSet Controller会不断重试创建那2个失败的Pod——Events里会刷屏"exceeded quota"。这时候要么加配额,要么减少副本数。
五、ResourceQuota常用排错
# 查看配额使用情况
kubectl describe resourcequota -n team-a
# 找出哪些Pod占用了配额
kubectl get pods -n team-a -o custom-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 -n team-a -o json | jq -r '
.items[] |
"\(.metadata.name) \(
(.spec.containers[].resources.requests.cpu // "0")
)"' | sort -k2 -r
# 注意:终止中的Pod(Terminating)仍然占用配额!
# 如果Pod卡在Terminating,配额被占着不放
kubectl get pods -n team-a --field-selector status.phase=Terminating
# → 如果发现僵尸Pod,用 --force --grace-period=0 删除
# 更新ResourceQuota
kubectl edit resourcequota team-a-quota -n team-a
# 或者patch
kubectl patch resourcequota team-a-quota -n team-a \
--patch '{"spec":{"hard":{"requests.cpu":"10"}}}'
本篇小结
ResourceQuota是集群级别的"公平秤",确保每个团队不会吃掉别人的资源:
- 管两类额度:计算资源总量(requests/limits的CPU和内存总和)+ 对象数量(Pod/Service/ConfigMap/PVC等最多多少个)
- requests和limits独立计算——两个池子互不干扰,用完一个不影响另一个
- Scope作用域让限制更精准——可以只限长期服务Pod、禁止BestEffort、给不同优先级分别配额
- ResourceQuota + LimitRange 组合拳——LimitRange管每个Pod怎么配,ResourceQuota管全Namespace能用多少
- 配额超限直接拒绝——不是Pending,是API Server直接403,需要扩容配额或减少资源消耗
至此咱们把资源管理的三件套(Requests/Limits → QoS → LimitRange → ResourceQuota)都聊完了——从单个Pod到全集群都有了规矩。下一篇切换视角,看看Pod自己的一生——从Pending到Terminating的每一步,Init容器、生命周期钩子这些细节。
上一篇【第31篇】LimitRange——给你的Namespace画个“圈“
下一篇【第33篇】Pod生命周期全解——从Pending到Terminating的每一步
更多推荐


所有评论(0)