上一篇【第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: 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是集群级别的"公平秤",确保每个团队不会吃掉别人的资源:

  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的每一步


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐