欢迎光临

Kubernetes ResourceQuota 与 LimitRange 深度指南:多租户场景下的资源配额管理与公平调度实战

在 Kubernetes 集群从”试验田”走向”生产平台”的过程中,一个绕不开的问题就是:如何防止某个团队或某个应用吃光整个集群的资源?当你把 K8s 集群开放给多个业务团队使用时,如果没有资源配额管控,一场”公地悲剧”几乎是不可避免的——某个写错了 resources 配置的 Pod 可能会独占节点内存,某个没有设置 limit 的批处理任务可能在凌晨跑满 CPU,导致核心服务全面降级。

Kubernetes 原生提供了两个关键的资源管控机制:ResourceQuota(命名空间级配额)和 LimitRange(默认值与上下限约束)。它们配合使用,可以构建一套完整的多租户资源公平调度体系。本文将从底层原理到生产实战,带你彻底搞懂这两个对象的正确用法。

Kubernetes 资源配额管理

一、资源管控的必要性:为什么裸奔的集群迟早会出事

先看一个真实的故障场景。某电商平台将所有业务部署在同一个 K8s 集群上,没有做任何资源配额限制。某天凌晨,数据分析团队上线了一个新的离线计算任务,配置了

1
resources.requests.cpu: "16"

,但没有设置 limits。这个任务调度到一个核心交易服务所在的节点上,迅速占满了 CPU,导致交易服务的 P99 延迟从 50ms 飙升到 3 秒以上。

这类问题的根源在于:Kubernetes 的调度器只看 requests 做调度决策,但运行时容器实际消耗的资源可以远超 requests(如果没有设置 limits)。这就产生了”调度承诺”和”实际消耗”之间的巨大鸿沟。

没有资源管控时的典型问题

  • 资源饥饿:低优先级应用占用大量资源,高优先级服务无节点可调度
  • Noisy Neighbor:同一节点上的应用互相干扰,CPU throttling 导致延迟毛刺
  • 成本失控:每个团队都申请最大资源”以防万一”,集群被迫不断扩容
  • 安全风险:恶意或失控的容器可以耗尽节点资源,影响同节点的所有工作负载

ResourceQuota 和 LimitRange 就是 Kubernetes 给出的答案——前者限制”总量”,后者约束”单个体”。

二、ResourceQuota:命名空间级的资源总闸

ResourceQuota 是作用于 命名空间 级别的资源配额对象。它就像一个预算审批员,在你提交资源创建请求时检查:”这个命名空间的所有资源加起来有没有超额?”

2.1 ResourceQuota 管控的范围

ResourceQuota 可以管控三大类资源:

类别 可管控的资源 说明
计算资源 cpu, memory, requests.cpu, requests.memory, limits.cpu, limits.memory 控制命名空间内所有 Pod 的 CPU/内存总量
存储资源 requests.storage, persistentvolumeclaims, requests.存储类名.storage 控制 PVC 数量和存储总量
对象数量 pods, services, replicationcontrollers, resourcequotas, secrets, configmaps 等 限制各类 API 对象的创建数量

一个关键细节:ResourceQuota 只对设置了 resources 的 Pod 生效。如果一个 Pod 没有设置任何 resources 字段,它将不受 ResourceQuota 的限制——这恰恰是最危险的情况。这就是为什么 LimitRange(设置默认值)必须和 ResourceQuota 配合使用。

2.2 ResourceQuota 的 Admission 控制流程

当你创建或更新 Pod 时,Kubernetes API Server 的准入控制器会执行以下检查:

  1. 如果 Pod 没有设置 resources,先从 LimitRange 获取默认值填充
  2. 将 Pod 的 requests/limits 加上命名空间已使用的配额
  3. 检查是否超过 ResourceQuota 中定义的硬性限制
  4. 如果超额,拒绝创建并返回 403 Forbidden
  5. 如果未超额,更新 ResourceQuota 的 status.used 字段,允许创建

注意:这个检查是原子性的,不存在两个 Pod 同时创建”抢额度”的竞态条件。API Server 通过 etcd 的事务机制保证了配额扣减的一致性。

2.3 生产级 ResourceQuota 配置示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-backend-quota
  namespace: team-backend
spec:
  hard:
    # 计算资源配额
    requests.cpu: "20"          # 最多申请 20 核 CPU
    requests.memory: 40Gi       # 最多申请 40GB 内存
    limits.cpu: "40"            # 最多限制 40 核 CPU
    limits.memory: 80Gi         # 最多限制 80GB 内存
    # 对象数量配额
    pods: "50"                  # 最多 50 个 Pod
    services: "10"              # 最多 10 个 Service
    persistentvolumeclaims: "20" # 最多 20 个 PVC
    requests.storage: "500Gi"   # 最多申请 500GB 存储
    # 配置与安全
    secrets: "20"               # 最多 20 个 Secret
    configmaps: "30"            # 最多 30 个 ConfigMap
    # 作用域限定:仅对 BestEffort 以外的 Pod 计数
    # (配合 scopes 使用)
  scopes:
    - NotTerminating            # 仅限非终止型 Pod
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-backend-besteffort
  namespace: team-backend
spec:
  hard:
    pods: "5"                   # BestEffort Pod 最多 5 个
  scopes:
    - BestEffort                # 仅对未设置 resources 的 Pod

集群资源配额架构

2.4 ResourceQuota Scopes 详解

Scopes 让你可以对不同类型的资源分别设置配额,Kubernetes 内置了四种 scope:

  • Terminating:匹配
    1
    spec.activeDeadlineSeconds >= 0

    的 Pod(如 Job、CronJob)

  • NotTerminating:匹配没有设置
    1
    activeDeadlineSeconds

    的 Pod(长期运行的服务)

  • BestEffort:匹配没有设置任何 resources 的 Pod(最危险的类型)
  • NotBestEffort:匹配设置了 resources 的 Pod(受管控的 Pod)

生产环境建议 严格限制 BestEffort Pod 的数量(甚至设为 0),强制所有 Pod 都必须设置 resources,这样 ResourceQuota 才能真正发挥作用。

三、LimitRange:单 Pod 的默认值与边界约束

如果说 ResourceQuota 是命名空间的”总预算”,那么 LimitRange 就是”单项开支标准”——它为命名空间内的每个 Pod/Container 定义了资源的默认值、最小值和最大值。

3.1 LimitRange 的三种约束类型

约束类型 作用 适用场景
default 为未设置 limits 的容器自动填充默认 limits 防止容器无限使用资源
defaultRequest 为未设置 requests 的容器自动填充默认 requests 确保调度器有合理的调度依据
max / min 限制单个容器的 resources 上下界 防止单个容器过大或过小
maxLimitRequestRatio 限制 limits 与 requests 的最大比值 防止超配(overcommit)过度

3.2 生产级 LimitRange 配置示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: LimitRange
metadata:
  name: team-backend-limits
  namespace: team-backend
spec:
  limits:
  - type: Container
    # 默认值:未设置 resources 时的自动填充
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    # 上下限:单个容器的资源边界
    max:
      cpu: "4"
      memory: "8Gi"
    min:
      cpu: "50m"
      memory: "64Mi"
    # 超配比限制:limits 最多是 requests 的 4 倍
    maxLimitRequestRatio:
      cpu: "4"
      memory: "4"
  - type: Pod
    max:
      cpu: "8"
      memory: "16Gi"
  - type: PersistentVolumeClaim
    max:
      storage: "50Gi"
    min:
      storage: "1Gi"

3.3 LimitRange 的自动填充机制

LimitRange 的自动填充是很多人理解不透彻的地方。让我用一个具体的例子说明:

假设你提交了以下 Pod 定义,没有设置任何 resources


1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: team-backend
spec:
  containers:
  - name: app
    image: my-app:v1
    # 没有 resources 字段!

Kubernetes 的准入控制器会根据上面定义的 LimitRange 自动填充为:


1
2
3
4
5
6
7
8
9
10
11
spec:
  containers:
  - name: app
    image: my-app:v1
    resources:
      requests:
        cpu: "100m"        # 来自 defaultRequest
        memory: "128Mi"    # 来自 defaultRequest
      limits:
        cpu: "500m"        # 来自 default
        memory: "512Mi"    # 来自 default

填充规则总结:

  • 未设置
    1
    limits

    → 用

    1
    default

    填充

  • 未设置
    1
    requests

    → 用

    1
    defaultRequest

    填充;如果没有

    1
    defaultRequest

    ,则用

    1
    default

    填充

  • 如果设置了
    1
    limits

    但没设

    1
    requests

    → requests 默认等于 limits

资源配额数据可视化

四、ResourceQuota 与 LimitRange 的协作模式

单独使用任何一个都无法实现完整的资源管控。它们的协作关系如下:

4.1 三层防护体系


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────┐
│         Layer 1: LimitRange (个体约束)        │
│  · 设置默认值,确保无 resources 的 Pod 被填充  │
│  · 限制单个容器的资源上下界                     │
│  · 限制超配比(maxLimitRequestRatio)          │
├─────────────────────────────────────────────┤
│       Layer 2: ResourceQuota (总量约束)       │
│  · 限制命名空间的总资源消耗                     │
│  · 限制各类对象的数量                          │
│  · 配合 scopes 细化管控粒度                   │
├─────────────────────────────────────────────┤
│       Layer 3: PriorityClass (优先级)         │
│  · 资源不足时,低优先级 Pod 被抢占              │
│  · 保证核心服务的资源供给                       │
└─────────────────────────────────────────────┘

4.2 常见配合模式

模式一:严格管控型(金融/支付场景)


1
2
3
4
5
6
7
8
9
10
11
# LimitRange: 小超配比,强约束
maxLimitRequestRatio:
  cpu: "1.5"       # limits 最多是 requests 的 1.5 倍
  memory: "1"      # 内存不允许超配(limits = requests)

# ResourceQuota: 精确配额
hard:
  requests.cpu: "30"
  limits.cpu: "45"    # 30 * 1.5
  requests.memory: "60Gi"
  limits.memory: "60Gi"  # 不允许超配

模式二:弹性管控型(互联网/Web 场景)


1
2
3
4
5
6
7
8
9
10
11
# LimitRange: 较大超配比,适度约束
maxLimitRequestRatio:
  cpu: "8"         # CPU 允许 8 倍超配(大部分时间 CPU 不会满载)
  memory: "2"      # 内存 2 倍超配

# ResourceQuota: 按超配比设定 limits 上限
hard:
  requests.cpu: "50"
  limits.cpu: "400"   # 50 * 8
  requests.memory: "100Gi"
  limits.memory: "200Gi"  # 100 * 2

五、生产实战:从零构建多租户资源管控体系

5.1 命名空间模板化配置

在多租户场景下,每个团队应该有独立的命名空间,并配套一致的 ResourceQuota 和 LimitRange。以下是一个可复用的命名空间初始化模板:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# namespace-init.yaml — 每个新团队初始化时应用
apiVersion: v1
kind: Namespace
metadata:
  name: {{TEAM_NAME}}
  labels:
    env: production
    team: {{TEAM_NAME}}
    resource-quota: enabled
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: {{TEAM_NAME}}
spec:
  hard:
    requests.cpu: "{{CPU_REQUESTS}}"
    requests.memory: "{{MEMORY_REQUESTS}}"
    limits.cpu: "{{CPU_LIMITS}}"
    limits.memory: "{{MEMORY_LIMITS}}"
    pods: "{{MAX_PODS}}"
    services: "10"
    secrets: "20"
    configmaps: "30"
    persistentvolumeclaims: "15"
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: besteffort-quota
  namespace: {{TEAM_NAME}}
spec:
  hard:
    pods: "0"       # 禁止 BestEffort Pod
  scopes:
    - BestEffort
---
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: {{TEAM_NAME}}
spec:
  limits:
  - type: Container
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    max:
      cpu: "4"
      memory: "8Gi"
    min:
      cpu: "50m"
      memory: "64Mi"
    maxLimitRequestRatio:
      cpu: "4"
      memory: "3"

5.2 配额动态调整策略

业务发展过程中,团队的资源需求会变化。配额调整应该遵循以下原则:

  • 基于监控数据:使用
    1
    kubectl get resourcequota -n <ns>

    查看 used/hard 比率,当使用率持续超过 80% 时考虑扩容

  • 预留 buffer:ResourceQuota 的 hard 值应该留 20%-30% 的余量,避免正常扩容被拒绝
  • 渐进式调整:先调大 ResourceQuota,观察一周实际消耗后再决定是否永久生效
  • 成本归属:结合 Kubernetes 成本分析工具(如 Kubecost),让每个团队”看到”自己的资源消耗和成本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看命名空间配额使用情况
kubectl get resourcequota -n team-backend -o yaml

# 输出示例:
# status:
#   hard:
#     requests.cpu: "20"
#     requests.memory: 40Gi
#   used:
#     requests.cpu: "14.5"      # 72.5% 使用率
#     requests.memory: 28Gi     # 70% 使用率

# 当使用率 > 80% 时,考虑扩容:
kubectl patch resourcequota compute-quota -n team-backend   --type merge -p '{"spec":{"hard":{"requests.cpu":"30","requests.memory":"60Gi"}}}'

资源监控仪表盘

六、常见坑与排障指南

6.1 Pod 处于 Pending 状态且事件提示 ExceededQuota

这是最常见的配额问题。当 ResourceQuota 不足以容纳新 Pod 时:


1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看事件
kubectl describe pod my-app -n team-backend
# Events:
#   Warning  FailedCreate  ...  exceeded quota: compute-quota,
#   requested: requests.cpu=1,requests.memory=1Gi,
#   used: requests.cpu=19.5,requests.memory=39Gi,
#   limited: requests.cpu=20,requests.memory=40Gi

# 解决方案:
# 1. 调大 ResourceQuota
# 2. 删除不需要的 Pod 释放配额
# 3. 减小新 Pod 的 requests
# 4. 联系平台团队申请更多配额

6.2 LimitRange 阻止了 Pod 创建

当 Pod 的 resources 超过 LimitRange 的 max 或低于 min 时:


1
2
3
4
5
6
kubectl describe pod my-app -n team-backend
# Warning  FailedCreate  ...  LimitRange
#   max limit to admit: cpu=4,memory=8Gi
#   but request: cpu=8,memory=16Gi

# 解决方案:调整 LimitRange 的 max,或者减小 Pod 的资源申请

6.3 maxLimitRequestRatio 校验失败


1
2
3
4
5
6
7
# 错误:limits.cpu=4, requests.cpu=500m,比值 8 超过了 maxLimitRequestRatio 4
# 解决:要么增大 requests,要么减小 limits
resources:
  requests:
    cpu: "1"       # 4/1 = 4,刚好满足
  limits:
    cpu: "4"

6.4 删除命名空间后配额”残留”

ResourceQuota 的 status.used 是实时计算的,不存在真正的”残留”问题。但如果你发现 used 值不为 0 且命名空间内没有 Pod,检查是否有以下对象占用配额:


1
2
3
4
5
# 检查所有占用配额的对象
kubectl get all,pvc,secret,configmap -n team-backend

# 常见遗漏:ReplicaSet 的副本虽然 Pod 未运行,但可能已经占用了配额
kubectl get replicasets -n team-backend

七、进阶:结合 PriorityClass 和 Preemption 实现资源分级保障

ResourceQuota 和 LimitRange 解决了”总量控制”的问题,但没有解决”资源不足时谁该让路”的问题。PriorityClass + Preemption(抢占)机制填补了这个空白。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 定义优先级
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "核心交易服务"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: normal
value: 100000
globalDefault: true
description: "普通业务服务"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: batch
value: 10000
preemptionPolicy: PreemptLowerPriority
description: "批处理任务,可被抢占"

配合 ResourceQuota 使用时,可以为不同优先级的 Pod 设置不同的配额:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: ResourceQuota
metadata:
  name: critical-quota
  namespace: team-backend
spec:
  hard:
    pods: "20"
    requests.cpu: "15"
    requests.memory: "30Gi"
  scopeSelector:
    matchExpressions:
    - operator: In
      scopeName: PriorityClass
      values: ["critical"]

这样,critical 级别的 Pod 有独立的配额池,不会被普通 Pod 占满;当集群资源紧张时,低优先级的 batch Pod 会被抢占,为 critical Pod 腾出空间。

八、监控与告警:让配额管理可视化

配额设置了不等于万事大吉,持续监控才是关键。以下是推荐的监控指标和告警规则:

8.1 关键监控指标


1
2
3
4
5
6
# Prometheus 指标
kube_resourcequota{type="hard"}     # 配额硬限制
kube_resourcequota{type="used"}     # 配额已使用量

# 计算使用率
rate_used = kube_resourcequota{type="used"} / kube_resourcequota{type="hard"}

8.2 推荐告警规则


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 配额使用率超过 85%
- alert: ResourceQuotaNearLimit
  expr: |
    kube_resourcequota{type="used"} / kube_resourcequota{type="hard"} > 0.85
  for: 15m
  labels:
    severity: warning
  annotations:
    summary: "Namespace {{ $labels.namespace }} 的 {{ $labels.resource }} 配额使用率超过 85%"

# 配额使用率超过 95%
- alert: ResourceQuotaCritical
  expr: |
    kube_resourcequota{type="used"} / kube_resourcequota{type="hard"} > 0.95
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "Namespace {{ $labels.namespace }} 的 {{ $labels.resource }} 配额即将耗尽"

此外,建议在 Grafana 上建立配额使用率的趋势图,帮助团队提前规划扩容需求,而不是等到 Pod 创建失败时才紧急处理。

总结

ResourceQuota 和 LimitRange 是 Kubernetes 多租户资源管控的基石,但它们需要配合使用才能发挥完整效果:

  • LimitRange 先行:为所有容器设置默认值和边界,确保没有”裸奔”的 Pod
  • ResourceQuota 守门:限制命名空间的总资源消耗,防止单个团队过度占用
  • PriorityClass 兜底:资源不足时保证高优先级服务优先获得资源
  • 监控告警持续运行:让配额管理从被动响应变为主动规划

记住一个核心原则:永远不要让用户的 Pod 不设置 resources 就能创建成功。通过 LimitRange 的 default/defaultRequest 自动填充 + BestEffort scope 的 ResourceQuota 设为 0,可以从机制上杜绝无资源约束的 Pod。这不仅是稳定性保障,更是成本优化的起点——只有当你清楚每个 Pod 需要多少资源时,才能准确规划集群容量,避免过度配置带来的浪费。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » Kubernetes ResourceQuota 与 LimitRange 深度指南:多租户场景下的资源配额管理与公平调度实战
分享到: 更多 (0)