Kubernetes 基础体系 · 第 53/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
ResourceQuota 与 LimitRange:租户预算、默认值、对象数量和治理
在 Kubernetes 中,ResourceQuota 和 LimitRange 都是 Namespace 级别的准入控制资源,但它们解决的是两个不同问题:
LimitRange约束单个对象或单个容器的资源形态,并可以补齐默认值;ResourceQuota约束整个 Namespace 的累计使用量,包括计算资源、存储资源和对象数量。
可以先用一句形式化描述区分它们:
其中:
- 是某个容器或 Pod 对资源的请求、限制或其他属性;
- 、 是
LimitRange对单个对象的约束; - 是 Namespace 当前已经使用的资源 ;
- 是本次创建或更新带来的增量;
- 是
ResourceQuota为资源 设置的硬上限。
因此,Namespace 可能出现以下状态:
- 每个容器都符合
LimitRange,但 Namespace 总 CPU 已超过ResourceQuota; - Namespace 还有充足的总预算,但某个容器超过了
LimitRange的单对象上限; - Pod 没有显式写
resources,但LimitRange自动补齐默认值,使它开始消耗ResourceQuota; - 计算资源没有超额,但 Pod 数量或 PVC 数量达到对象数量配额。
1. Namespace 是预算和默认策略的边界
ResourceQuota 与 LimitRange 都是 Namespace-scoped 资源。它们不会跨 Namespace 统计,也不会直接限制集群级对象。
例如:
ResourceQuota在team-a中统计 Pod、PVC 和 CPU;team-b有自己的配额;ClusterRole、Node、StorageClass、CustomResourceDefinition等集群级对象不属于某个 Namespace,不能通过普通 NamespaceResourceQuota直接限制。
这意味着 Namespace 通常是 Kubernetes 多租户中的预算边界,但不是完整的安全边界。它不能单独提供:
- 用户身份隔离;
- 对 API 操作的权限控制;
- Pod 之间的网络隔离;
- 节点、内核、设备和宿主机层面的强隔离;
- 对集群级资源创建的限制。
生产租户模型通常还需要 RBAC、NetworkPolicy、Pod Security Admission,以及必要时的独立节点池、虚拟集群或独立集群。
2. LimitRange:限制单个对象,并在准入时补默认值
2.1 LimitRange 的作用范围
LimitRange 的每一个条目通过 type 指定作用对象。当前稳定的 v1 API 中,常见类型包括:
Container:约束容器的 CPU、内存、临时存储等资源;Pod:约束 Pod 内所有容器资源请求或限制的聚合值;PersistentVolumeClaim:约束 PVC 的存储请求。
一个典型的容器级 LimitRange 如下:
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-defaults
namespace: team-a
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
min:
cpu: "50m"
memory: "64Mi"
max:
cpu: "2"
memory: "2Gi"
maxLimitRequestRatio:
cpu: "10"
memory: "4"
这些字段含义不同:
| 字段 | 含义 |
|---|---|
default |
容器未指定 resources.limits 时使用的默认限制 |
defaultRequest |
容器未指定 resources.requests 时使用的默认请求 |
min |
单个容器允许设置的最小值 |
max |
单个容器允许设置的最大值 |
maxLimitRequestRatio |
limit / request 的最大比例 |
例如,上面的配置要求:
并且:
如果某个容器请求 100m CPU,限制为 2 CPU,那么比例为:
这会被拒绝,因为超过了 maxLimitRequestRatio.cpu=10。
2.2 默认值如何生成
下面的 Pod 没有写 resources:
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: team-a
spec:
containers:
- name: app
image: nginx:1.27
在 LimitRange 生效后,准入阶段可能得到等价的资源配置:
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
这里有两个重要规则。
第一,默认值是在对象进入集群时写入对象的,不是 kubelet 在节点上临时计算出来的。可以通过以下命令观察最终对象:
kubectl -n team-a get pod web -o yaml
第二,如果只配置了 default,没有配置 defaultRequest,Kubernetes 的常见行为是把默认限制同时作为默认请求使用。例如:
default:
cpu: "500m"
memory: "512Mi"
可能导致:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
这会显著影响:
ResourceQuota的请求资源消耗;- 调度器为 Pod 预留的节点容量;
- Pod 的 QoS 分类。
因此,生产环境不能把 default 当作“仅仅限制最大值”的配置。若希望请求和限制不同,应显式设置 defaultRequest。
2.3 LimitRange 不会修改现有对象
LimitRange 主要在对象创建或相关更新的准入过程中生效。创建它之后,已有 Pod 不会自动被重写:
kubectl apply -f limitrange.yaml
这不会遍历 Namespace 内的 Pod 并补上 resources。
如果工作负载由 Deployment、StatefulSet 或 Job 管理,需要修改控制器的 Pod 模板,使后续创建的 Pod 带上符合规则的配置。已有 Pod 是否被替换,则取决于控制器的滚动更新或重建行为。
这也解释了一个常见现象:管理员创建 LimitRange 后,旧 Pod 仍然没有请求和限制,而新发布的 Pod 开始出现默认资源字段。
3. LimitRange 的校验对象:容器、Pod 和 PVC
3.1 容器级资源
对 type: Container 而言,min、max 和比例约束通常应用于每个容器,而不是整个 Pod。
如果一个 Pod 有两个容器:
containers:
- name: app
resources:
requests:
cpu: "500m"
- name: sidecar
resources:
requests:
cpu: "100m"
那么容器级请求分别是 500m 和 100m,Pod 的总请求是:
某个容器的请求符合限制,不代表 Pod 总请求一定符合 Namespace 配额;反过来也一样。
3.2 Pod 级资源
type: Pod 可以表达 Pod 内容器资源总和的约束。例如:
apiVersion: v1
kind: LimitRange
metadata:
name: pod-budget
namespace: team-a
spec:
limits:
- type: Pod
max:
cpu: "4"
memory: "8Gi"
对于一个有两个容器的 Pod,假设它们的 CPU limit 分别为 2 和 3:
那么即使每个容器都没有超过各自的 Container 级最大值,Pod 仍可能因为超过 Pod 级最大值而被拒绝。
Pod 级条目适合防止“多个合法容器组合成一个过大的 Pod”。但它与调度器、节点容量和 Namespace 总配额仍是不同层次的约束。
3.3 PVC 级存储限制
LimitRange 也可以限制单个 PVC:
apiVersion: v1
kind: LimitRange
metadata:
name: pvc-size-range
namespace: team-a
spec:
limits:
- type: PersistentVolumeClaim
min:
storage: "1Gi"
max:
storage: "100Gi"
这表示每个 PVC 的请求存储量必须满足:
它不表示 Namespace 总共只能使用 100Gi。Namespace 总量需要由 ResourceQuota 约束。
4. ResourceQuota:限制 Namespace 的累计预算
4.1 基本计算模型
一个资源配额可以表示为:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-budget
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
requests.storage: 100Gi
persistentvolumeclaims: "10"
pods: "20"
对 CPU 请求配额,Namespace 的使用量近似为:
一次创建或更新带来的增量为 。准入检查要求:
如果结果大于 4,对象会被拒绝。
requests.memory、limits.cpu、limits.memory 采用同样的累计逻辑。对 Pod 而言,资源需求通常按 Pod 内容器的总和参与统计;实际还应考虑 Kubernetes 对 init container、终止状态对象等资源统计规则,不能简单把所有 YAML 字段机械相加。
4.2 完整算例:默认值如何消耗配额
假设 team-a 有以下配额:
hard:
requests.cpu: "1"
requests.memory: 2Gi
limits.cpu: "2"
limits.memory: 4Gi
pods: "5"
Namespace 中已运行两个 Pod:
| Pod | CPU request | 内存 request | CPU limit | 内存 limit |
|---|---|---|---|---|
api-1 |
250m | 512Mi | 500m | 1Gi |
api-2 |
250m | 512Mi | 500m | 1Gi |
当前使用量为:
此时再创建一个没有 resources 的 Pod。如果 LimitRange 自动补齐:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
新的使用量为:
这个 Pod 可以创建,因为所有结果都没有超过硬上限。
但如果同一个 Pod 显式设置:
resources:
requests:
cpu: "600m"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
则:
已经超过 1,创建请求会被 ResourceQuota 拒绝。此时问题不是单个容器违反了 LimitRange,而是 Namespace 没有足够的累计预算。
4.3 ResourceQuota 不只限制 CPU 和内存
常见资源名称包括:
hard:
requests.cpu: "4"
limits.cpu: "8"
requests.memory: 8Gi
limits.memory: 16Gi
requests.ephemeral-storage: 50Gi
limits.ephemeral-storage: 100Gi
requests.storage: 500Gi
persistentvolumeclaims: "20"
pods: "50"
services: "20"
secrets: "100"
configmaps: "100"
这些资源含义不同:
requests.cpu、requests.memory:累计调度请求;limits.cpu、limits.memory:累计资源限制;requests.ephemeral-storage:容器本地临时存储请求;requests.storage:PVC 请求的存储总量;pods:Namespace 中匹配范围的 Pod 数量;services、secrets、configmaps:对应对象数量。
ResourceQuota 记录的是 Kubernetes 对象声明的资源请求或对象数量,并不是实时测量到的 CPU 使用率、内存工作集或磁盘实际写入量。它不能替代监控系统,也不保证底层云盘、节点磁盘或集群总容量一定充足。
5. 对象数量配额:防止控制面和资源对象无限增长
5.1 数量配额的语法
对象数量可以使用短名称:
hard:
pods: "20"
services: "10"
secrets: "50"
configmaps: "100"
persistentvolumeclaims: "10"
也可以使用资源组形式限制其他 Namespaced 资源,例如:
hard:
count/deployments.apps: "10"
count/statefulsets.apps: "5"
count/jobs.batch: "50"
count/ingresses.networking.k8s.io: "20"
实际可用资源名称取决于 API server 发现到的 Namespaced API 资源,以及当前 Kubernetes 版本对配额评估器的支持。可以先检查资源名称:
kubectl api-resources --namespaced=true
对象数量配额限制的是 API 对象的数量,不是对象背后的运行实例数量。例如:
deployments.apps: 10限制 Deployment 对象数量;pods: 20限制 Pod 对象数量;- 一个 Deployment 的
replicas: 20不会消耗 20 个 Deployment 配额,但会创建最多 20 个 Pod,消耗 Pod 配额。
5.2 Deployment 与 Pod 配额的失败路径
假设:
hard:
count/deployments.apps: "10"
pods: "3"
创建一个:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: team-a
spec:
replicas: 5
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
Deployment 对象本身可能能够成功创建,因为当前 Deployment 数量没有超过 10。随后 Deployment 控制器尝试创建 Pod:
- 第一个 Pod 创建成功;
- 第二个 Pod 创建成功;
- 第三个 Pod 创建成功;
- 第四、第五个 Pod 的创建请求被
ResourceQuota拒绝; - Deployment 持续处于未达到期望副本数的状态,并产生事件。
此时不能仅检查:
kubectl get deployment -n team-a
还需要查看:
kubectl describe deployment web -n team-a
kubectl get events -n team-a --sort-by=.lastTimestamp
kubectl get quota -n team-a
常见事件会包含类似“exceeded quota”的信息。恢复方法取决于目标:
- 降低
spec.replicas; - 删除不再需要的 Pod 或其他对象;
- 增加 Pod 配额;
- 为该租户分配更多预算。
滚动更新也可能临时需要更多 Pod。新 ReplicaSet 和旧 ReplicaSet 在切换期间可能同时存在,因而 pods 配额过紧时,发布会卡住,即使最终副本数理论上并不超限。
6. 配额匹配范围:Scopes 和 ScopeSelector
一个 Namespace 可以有多个 ResourceQuota,每个配额可以针对不同对象集合。常见范围包括:
BestEffort:没有任何资源请求和限制的 Pod;NotBestEffort:不是 BestEffort 的 Pod;Terminating、NotTerminating:是否设置了activeDeadlineSeconds;PriorityClass:匹配指定优先级;- 某些版本支持基于 Pod 优先级的
scopeSelector; - 与跨 Namespace Pod 亲和性相关的范围能力也受版本和配置影响。
例如,单独限制 BestEffort Pod:
apiVersion: v1
kind: ResourceQuota
metadata:
name: besteffort-pods
namespace: team-a
spec:
hard:
pods: "2"
scopes:
- BestEffort
再限制带资源请求的 Pod:
apiVersion: v1
kind: ResourceQuota
metadata:
name: requested-pods
namespace: team-a
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
scopes:
- NotBestEffort
一个对象可能同时匹配多个配额。此时必须同时满足所有匹配到的配额:
因此,添加一个更窄范围的配额不会绕过已有的通用配额。
scopeSelector 可进一步按 PriorityClass 等条件匹配。配置前应使用当前集群版本的 API 文档和:
kubectl explain resourcequota.spec.scopeSelector
kubectl explain resourcequota.spec.scopes
确认字段和可用值。不同 Kubernetes 版本、启用的特性门控和托管平台发行版可能存在差异。
7. ResourceQuota、LimitRange 与准入流程
一个简化的请求路径如下:
sequenceDiagram
participant C as kubectl/控制器
participant A as kube-apiserver
participant L as LimitRanger
participant Q as ResourceQuota
participant S as etcd
participant K as 控制器/kubelet
C->>A: 创建或更新 Namespace 内对象
A->>L: 校验并补齐 LimitRange 默认值
L-->>A: 返回修改后的对象或拒绝
A->>Q: 计算配额增量并检查 ResourceQuota
Q-->>A: 允许或拒绝
A->>S: 持久化对象
S-->>A: 返回成功
A-->>C: 请求结果
K->>A: 读取已接受的 Pod
K->>K: 调度、启动并执行容器
这个图表达的是语义顺序,而不是所有内部 admission 插件的完整实现细节。实际 API server 还可能经过:
- Kubernetes 内置字段默认化;
- MutatingAdmissionWebhook;
- ValidatingAdmissionWebhook;
- 认证和授权;
- 其他内置准入插件。
关键点是:ResourceQuota 判断的不是客户端最初提交的“意图”,而是经过相关默认化和变更准入后的对象形态。于是,一个没有写资源字段的 Pod 也可能因为 LimitRange 默认值而消耗配额。
ResourceQuota 的使用状态通常由配额控制器维护,配额对象中可以看到:
kubectl get resourcequota tenant-budget -n team-a -o yaml
典型输出结构包括:
status:
hard:
pods: "20"
requests.cpu: "1"
used:
pods: "3"
requests.cpu: 600m
status.used 是控制面观察和计算出的状态,不应被当作外部计费系统的精确、实时计量账本。发生高并发创建、控制器重试或对象快速删除时,状态显示可能存在短暂延迟;最终是否接受某个请求仍应以 API server 的准入结果为准。
8. LimitRange 和 ResourceQuota 的错误边界
8.1 只创建 LimitRange,不代表有租户预算
下面的 LimitRange:
spec:
limits:
- type: Container
max:
cpu: "1"
memory: "1Gi"
只能限制每个容器的最大资源,不能防止租户创建无限多个容器或 Pod。若希望限制 Namespace 总量,仍要创建 ResourceQuota。
8.2 只创建 ResourceQuota,不代表请求一定合理
如果只设置:
hard:
requests.cpu: "4"
requests.memory: 8Gi
那么用户可以提交:
resources:
requests:
cpu: "4"
memory: 8Gi
一个 Pod 就消耗掉整个 Namespace 的预算。也可能提交没有 requests 的 Pod,具体是否能够创建取决于配额是否包含适用资源、是否配置了 LimitRange,以及 Pod 的最终资源形态。
ResourceQuota 控制总量,不能替代单对象的 LimitRange。
8.3 配置 max 不会自动设置 limit
下面的配置:
max:
cpu: "1"
表示超过 1 CPU 时拒绝,不表示缺失的 CPU limit 会自动变成 1。要产生默认值,必须设置 default。
8.4 requests 和 limits 不是同一个预算
如果配额同时配置:
hard:
requests.cpu: "4"
limits.cpu: "8"
则两个维度分别计算。一个容器请求 500m、限制 2,消耗:
requests.cpu:500m;limits.cpu:2。
不能用 limit 去推导 request,也不能因为 request 配额有剩余,就认为 limit 配额一定有剩余。
9. 资源配额与 QoS、调度和运行时的关系
ResourceQuota 主要在 API 准入阶段工作;调度器随后使用 Pod 的资源请求选择节点;kubelet 和 Linux cgroup 在运行时执行限制。它们是三个不同阶段。
9.1 CPU
CPU request 主要用于调度和配额统计,CPU limit 主要用于运行时上限。CPU 超过 limit 时通常会被 throttling,而不是像内存那样直接 OOM。
因此:
requests.cpu配得过小,可能让调度器过度装箱;limits.cpu配得过小,可能导致应用 CPU throttle;- ResourceQuota 只能限制租户声明的总量,不能保证租户实际获得固定 CPU 性能。
9.2 内存
内存 limit 由运行时通过 cgroup 等机制执行。容器超过限制时可能被 OOM kill;节点整体内存压力还可能触发 eviction。
因此:
limits.memory是容器级运行时约束;requests.memory影响调度和配额;ResourceQuota不会防止节点内存压力,也不会替代 eviction 机制;- 一个租户未超过配额,仍可能因为节点不足而 Pending 或被驱逐。
9.3 QoS 分类
在常见 Kubernetes 规则下:
- 所有容器都设置了 CPU 和内存的 request、limit,且对应值相等,Pod 倾向于属于
Guaranteed; - 至少有一个容器设置了 request 或 limit,但不满足 Guaranteed 条件,通常属于
Burstable; - 没有任何容器资源 request 和 limit,通常属于
BestEffort。
LimitRange 的默认值会直接影响 QoS 分类。例如,只设置默认 limit,且系统把它同时作为 request 使用,可能使容器表现为 request 等于 limit;设置不同的 defaultRequest 和 default 则通常会产生 Burstable Pod。
QoS 分类不等于配额范围。只有在 ResourceQuota 显式使用 BestEffort 或 NotBestEffort scope 时,QoS 类别才会影响该配额的匹配。
10. PVC 配额、动态供给和真实存储容量
对于 PVC,常见配置如下:
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage-budget
namespace: team-a
spec:
hard:
requests.storage: 500Gi
persistentvolumeclaims: "20"
假设当前 PVC 请求总量为 420Gi,再次创建一个请求 100Gi 的 PVC:
PVC 会在准入阶段被拒绝,动态供给器通常不会有机会创建对应的 PV。
还可以按 StorageClass 分别限制存储请求,形式类似:
hard:
fast.storageclass.storage.k8s.io/requests.storage: 100Gi
standard.storageclass.storage.k8s.io/requests.storage: 400Gi
这里的资源名称必须与集群实际支持的配额资源形式一致,应先使用当前版本文档或测试 Namespace 验证。
存储配额仍有边界:
- 配额约束的是 PVC 声明的请求量;
- 不代表底层云盘一定有相同的可用容量;
- 不代表应用实际只写入了该容量;
- StorageClass 的扩容、快照、克隆和厂商实现可能有额外行为;
- 云厂商可能在 CSI、云 API 和计费层引入不同的限制。
因此,存储配额需要与存储后端容量、扩容策略和备份策略一起设计。
11. 一个可运行的端到端示例
下面的示例创建 Namespace、默认资源策略、资源配额和一个 Deployment。
11.1 创建 Namespace、LimitRange 和 ResourceQuota
apiVersion: v1
kind: Namespace
metadata:
name: team-a
---
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-defaults
namespace: team-a
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
min:
cpu: "50m"
memory: "64Mi"
max:
cpu: "1"
memory: "1Gi"
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-budget
namespace: team-a
spec:
hard:
requests.cpu: "1"
requests.memory: 2Gi
limits.cpu: "2"
limits.memory: 4Gi
pods: "3"
services: "5"
persistentvolumeclaims: "2"
应用:
kubectl apply -f tenant-policy.yaml
kubectl get limitrange,resourcequota -n team-a
检查具体规则:
kubectl describe limitrange tenant-defaults -n team-a
kubectl describe resourcequota tenant-budget -n team-a
11.2 创建一个不写资源字段的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: team-a
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
执行:
kubectl apply -f web.yaml
kubectl get pods -n team-a
kubectl get pod -n team-a -l app=web -o yaml
kubectl get resourcequota tenant-budget -n team-a -o yaml
每个 Pod 预计因 LimitRange 得到:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
两个副本大约消耗:
- CPU request:
200m; - 内存 request:
256Mi; - CPU limit:
1; - 内存 limit:
1Gi; - Pod 数量:
2。
如果把副本数改为 4,Pod 数量将超过 pods: "3"。Deployment 对象本身可能创建成功,但其中部分 Pod 创建会被拒绝,最终表现为 Deployment 不可用而不是 Deployment 创建命令本身必然失败。
11.3 验证单容器上限
创建一个请求 2 CPU 的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: too-large
namespace: team-a
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "2"
memory: "256Mi"
limits:
cpu: "2"
memory: "512Mi"
验证:
kubectl apply --dry-run=server -f too-large.yaml
--dry-run=server 会把请求发送到 API server,但不持久化对象,适合验证服务端默认化和准入规则。该对象应因 LimitRange 的 max.cpu: "1" 被拒绝。
如果改成 requests.cpu: "900m"、limits.cpu: "2",则可能先满足 max,但违反:
这个值没有超过 CPU 比例上限 10;如果改成 requests.cpu: "100m"、limits.cpu: "2",则:
会因 maxLimitRequestRatio 被拒绝。
12. 诊断失败:先区分 LimitRange、Quota、调度和运行时
12.1 对象在 API 创建阶段被拒绝
优先检查:
kubectl apply --dry-run=server -f object.yaml
kubectl get limitrange -n team-a -o yaml
kubectl get resourcequota -n team-a -o yaml
kubectl explain limitrange.spec.limits
典型原因包括:
- 容器 request 小于
min; - 容器 limit 大于
max; - limit/request 比例过大;
- Namespace 配额中的 request、limit 或对象数量已耗尽;
- PVC 超过单 PVC 上限或 Namespace 存储配额;
- 对象数量配额使用了不适用于当前资源的名称。
12.2 控制器对象成功,但 Pod 没有全部出现
检查:
kubectl describe deployment web -n team-a
kubectl get rs -n team-a
kubectl get pods -n team-a
kubectl get events -n team-a --sort-by=.lastTimestamp
如果事件显示配额超限,通常是:
- Deployment 副本数超过 Pod 配额;
- 滚动更新期间新旧 ReplicaSet 临时共存;
- LimitRange 默认值使每个 Pod 的请求高于预期;
- Namespace 中有其他团队对象消耗了共享配额。
12.3 Pod 已创建但处于 Pending
这通常不是 ResourceQuota 准入错误。需要区分:
- 调度器找不到满足 request 的节点;
- 节点 taint、亲和性或拓扑约束不满足;
- PVC 尚未绑定;
- 节点资源不足。
检查:
kubectl describe pod <pod-name> -n team-a
kubectl get nodes
kubectl get pvc -n team-a
配额允许创建,只说明 API 对象通过了累计预算检查,不代表集群一定能调度和运行它。
12.4 Pod 运行后被限速、OOM 或驱逐
这属于运行时和节点压力路径,不是 ResourceQuota 直接执行的行为:
kubectl get pod <pod-name> -n team-a -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}'
kubectl describe pod <pod-name> -n team-a
kubectl top pod -n team-a
需要结合:
- CPU limit 是否过低导致 throttling;
- 内存 limit 是否过低导致 OOM;
- 节点是否存在内存或临时存储压力;
- kubelet 是否触发 eviction;
- 容器运行时和云平台监控。
13. 并发、删除和配额状态的实际边界
配额检查涉及对象的创建、更新和删除,控制器还要异步维护 status.used。在高并发场景下,不能把客户端读取到的 status.used 当作绝对实时值。
例如,两个控制器同时创建 Pod:
- 两个请求都读取到接近的配额状态;
- API server 的准入逻辑必须防止最终接受超过硬配额的结果;
- 配额控制器随后重新计算并更新
status.used; - 客户端可能在短时间内看到状态更新滞后或出现重试。
规范保证的是配额准入不能被正常请求流程随意绕过;实现细节和状态更新时延不应被用于构建精确计费账本。
删除对象也不是“立即释放所有预算”的同步操作。对象删除可能经历:
- 删除请求被接受;
- 对象进入 terminating 状态;
- finalizer 阻止完全删除;
- 控制器清理关联对象;
- 配额控制器重新计算使用量。
因此,在自动扩缩容或高频 Job 场景中,应为配额释放延迟和控制器重试设计容错。
14. 多租户治理中的生产取舍
14.1 租户预算要覆盖“突发”还是“稳定容量”
如果把 requests.cpu 配得过高,租户可能很快耗尽配额,即使实际 CPU 使用率并不高;如果配得过低,调度器会低估资源需求,运行时可能频繁争抢资源。
常见预算模型是分别设置:
requests.cpu: "8"
limits.cpu: "16"
requests.memory: 16Gi
limits.memory: 24Gi
它表达的是:
- 稳定调度承诺按 8 CPU、16 GiB 内存计算;
- 允许声明的上限总和达到 16 CPU、24 GiB;
- 实际是否能持续获得突发资源,仍取决于节点余量和调度结果。
这不是对租户的性能 SLA。若需要性能隔离,还要控制节点池、拓扑、优先级、抢占和底层平台容量。
14.2 对象数量配额要考虑控制器行为
限制 pods、jobs、configmaps 和 secrets 可以防止错误脚本或租户程序无限创建对象,但过小的配额会影响:
- Deployment 滚动更新;
- Job 重试和历史保留;
- Helm 或 Operator 产生的辅助对象;
- 服务发现和配置发布;
- 失败对象清理之前的短暂堆积。
对象配额应按“峰值并发对象数”估算,而不是只按稳定状态估算。
14.3 ResourceQuota 不是计费系统
配额适合做准入保护:
- 防止一个 Namespace 消耗掉集群全部声明容量;
- 为租户建立资源上限;
- 在创建阶段快速失败。
它不适合单独承担:
- 按时间计费;
- 统计实际 CPU 使用;
- 统计实际内存工作集;
- 统计云盘实际占用;
- 判断服务是否达到 SLO。
这些需求需要 Metrics Server、Prometheus、云平台账单或专门的成本系统。
14.4 版本和发行版差异
本文示例使用当前稳定的 Kubernetes 核心 API:
ResourceQuota:apiVersion: v1;LimitRange:apiVersion: v1;- Deployment:
apps/v1。
但以下行为应按集群版本验证:
- 新增的资源类型是否能使用
count/<resource>配额; scopeSelector支持的匹配维度;- Pod 级资源字段和相关特性门控;
- CSI 存储驱动对扩容、快照和克隆的实现;
- 云厂商对节点、云盘和配额的额外限制。
可使用以下命令检查当前 API:
kubectl version
kubectl api-resources --namespaced=true
kubectl explain resourcequota
kubectl explain limitrange
不要因为某个云厂商控制台提供了“租户配额”功能,就假定它与 Kubernetes ResourceQuota 统计同一组资源;两者可能分别位于云 API、集群控制面和 Kubernetes Namespace 层。
15. 一套可验证的治理关系
可以把两者的职责归纳为四个连续问题:
-
这个容器的资源字段是否完整?
由LimitRange的默认值和校验规则处理。 -
这个 Pod 或 PVC 是否超过单对象边界?
由LimitRange的Container、Pod或PersistentVolumeClaim条目处理。 -
这个 Namespace 是否还有累计预算?
由ResourceQuota的hard和匹配范围处理。 -
集群是否真的有能力运行它?
由调度器、节点资源、存储供给器、kubelet 和运行时共同决定。
一个完整的租户策略通常至少包含:
# 单对象边界和默认值
kind: LimitRange
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
cpu: 500m
memory: 512Mi
max:
cpu: "2"
memory: 2Gi
# Namespace 累计预算
kind: ResourceQuota
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
requests.storage: 100Gi
persistentvolumeclaims: "10"
其中 LimitRange 负责让单个工作负载的资源声明具有可预测形态,ResourceQuota 负责把所有工作负载的累计消耗控制在租户预算内。二者共同作用时,默认资源才会进入可计算的预算模型;但它们仍然不能替代 RBAC、NetworkPolicy、Pod 安全策略、调度隔离和运行时监控。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 审计日志:Policy、Stage、Backend、性能和合规留存
- 下一篇:Helm 完整指南:Chart、Template、Values、Release、Hook 和回滚
- 延伸:Kubernetes Resource 与 QoS:Request、Limit、CPU Throttle、OOM 和 Eviction
- 延伸:Kubernetes 多租户:Namespace、RBAC、NetworkPolicy、Quota 和隔离极限
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论