3. 静态配额模型:Namespace 固定配额、ResourceQuota 与 LimitRange 详解

静态配额模型,说白了就是给每个团队或项目划定一个“资源天花板”。你想想看,如果大家共用一套 Kubernetes 集群,谁也不管谁,一个业务线突然把 CPU 打满,其他团队全得跟着遭殃。我刚开始搭数据平台时,就吃过这个亏——某个离线任务半夜把集群内存吃光,第二天早上才发现线上服务全挂了。

所以,静态配额是资源治理的第一步。它不聪明,但很管用。今天我们就来拆解三个核心工具:Namespace、ResourceQuota 和 LimitRange。

3.1 Namespace:资源隔离的第一道墙

Namespace 是 Kubernetes 里最基础的隔离单元。每个 Namespace 就像一栋楼里的独立房间,房间里的 Pod、Service、ConfigMap 互不干扰。我个人习惯,每个业务线、每个环境(dev、staging、prod)都单独建一个 Namespace。

举个例子,我们数据平台有三个核心团队:

  • 数据采集组:负责日志和埋点数据入仓
  • 实时计算组:跑 Flink 和 Spark Streaming 任务
  • 离线分析组:跑 Hive 和 Spark SQL 批处理

每个组一个 Namespace,资源配额各自独立。这样即使离线分析组把配额打满,实时计算组也不会受影响。

我的经验:Namespace 命名最好统一规范,比如 team-env 格式(data-ingestion-prod、realtime-staging)。我曾经见过一个团队用中文拼音命名,结果 CI/CD 脚本里各种转码问题,折腾了两天才改完。

3.2 ResourceQuota:给 Namespace 装上“水龙头”

ResourceQuota 就是 Namespace 级别的资源总量限制。你可以限制 CPU、内存、存储卷数量,甚至 Pod 数量。一旦 Namespace 内的资源使用量达到配额,Kubernetes 就会拒绝新的创建请求。

下面是一个典型的 ResourceQuota 配置:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: data-platform-quota
  namespace: data-offline
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "80Gi"
    limits.cpu: "40"
    limits.memory: "160Gi"
    persistentvolumeclaims: "10"
    pods: "50"

这里我解释一下几个关键字段:

  • requests.cpu / requests.memory:所有 Pod 的 request 总和不能超过这个值。这是“最低保障”的配额。
  • limits.cpu / limits.memory:所有 Pod 的 limit 总和不能超过这个值。这是“硬上限”。
  • pods:限制 Namespace 内最多能跑多少个 Pod。防止有人创建大量空 Pod 浪费调度资源。
注意:ResourceQuota 只限制创建时的资源总和,不会回收已运行的资源。如果你把配额从 80Gi 降到 40Gi,已经在跑的 Pod 不会被杀死,但新的 Pod 无法创建,直到现有 Pod 释放出空间。

我在项目中遇到过一个问题:某个团队在 Namespace 里创建了 200 个 Job,每个 Job 只跑几秒钟,但 Pod 数量瞬间打满配额,导致其他正常服务无法调度。后来我加上了 count/jobs.batch: "20" 和 count/cronjobs.batch: "10" 来限制 Job 和 CronJob 的数量,问题才解决。

3.3 LimitRange:给每个 Pod 戴上“紧箍咒”

ResourceQuota 管的是总量,但管不了单个 Pod 的行为。如果某个开发者提交了一个 YAML 文件,里面没写 resource request 和 limit,或者写了个离谱的 128Gi 内存,怎么办?

LimitRange 就是干这个的。它定义了 Namespace 内每个 Pod 或 Container 的资源上下限,以及默认值。

来看一个实际配置:

apiVersion: v1
kind: LimitRange
metadata:
  name: data-offline-limits
  namespace: data-offline
spec:
  limits:
  - max:
      cpu: "4"
      memory: "16Gi"
    min:
      cpu: "100m"
      memory: "256Mi"
    default:
      cpu: "500m"
      memory: "2Gi"
    defaultRequest:
      cpu: "200m"
      memory: "512Mi"
    type: Container

这里的关键字段:

字段 含义 我的建议
max 单个 Container 的资源上限 防止有人申请过大资源,导致其他 Pod 饿死
min 单个 Container 的资源下限 避免申请过小资源导致 OOM 或 CPU 节流
default 未指定 limit 时的默认 limit 我习惯设成和 max 一样,避免意外超限
defaultRequest 未指定 request 时的默认 request 建议设成比 default 小一些,保证调度灵活性
核心逻辑:LimitRange 在 Pod 创建时自动注入默认值,并校验是否在 min/max 范围内。如果 Pod 的某个 Container 资源声明超出范围,API Server 会直接拒绝创建。

我曾经遇到过一个案例:数据开发同学写了个 Spark Driver,忘记配 memory limit,结果默认 limit 是 2Gi,但 Driver 实际需要 8Gi,运行几分钟就 OOMKilled 了。后来我们在 LimitRange 里把 default 调高到 4Gi,同时要求所有生产环境的 Pod 必须显式声明 resources,才算彻底解决。

3.4 三者关系:一张图看懂

下面这张图展示了 Namespace、ResourceQuota、LimitRange 的协作关系:

静态配额模型核心结构 Namespace: data-offline ResourceQuota 总量限制:CPU 40核 / 内存 160Gi Pod 上限:50 个 LimitRange 单 Pod 上限:CPU 4核 / 内存 16Gi 默认值:CPU 500m / 内存 2Gi Pod A request: 200m / 512Mi Pod B request: 1核 / 4Gi Pod C request: 500m / 2Gi ResourceQuota 控制总量,LimitRange 控制单 Pod 上下限,两者配合实现双层资源管控 Pod 创建时先过 LimitRange 校验,再累加到 ResourceQuota 计数

3.5 避坑指南:我踩过的几个坑

静态配额看起来简单,但实际用起来有不少细节。我把自己踩过的坑列出来,你遇到了可以直接抄答案:

  1. ResourceQuota 不限制临时存储:默认只限制 CPU、内存和持久卷。如果 Pod 写大量日志到 emptyDir,磁盘可能被打满。记得加上 requests.ephemeral-storage 和 limits.ephemeral-storage。
  2. LimitRange 不覆盖 InitContainer:InitContainer 的资源限制需要单独配置。我见过一个 InitContainer 把内存拉到 32Gi,而主 Container 只有 4Gi,结果节点 OOM。
  3. 配额变更不会影响已有 Pod:这是 Kubernetes 的设计,不是 bug。如果你需要动态调整,得配合 HPA 或 VPA 使用,这部分我们后面章节会讲。
  4. Namespace 删除后配额也消失:如果你用 GitOps 管理集群,记得把 ResourceQuota 和 LimitRange 的 YAML 也纳入版本控制。我曾经手滑删了一个 Namespace,结果配额配置全丢了,恢复花了一下午。
我的习惯:每次新建 Namespace,我都会先 apply 一个 ResourceQuota 和一个 LimitRange 的模板,哪怕初始值设得很大。这样至少保证所有 Pod 都有资源约束,不会出现“裸奔”的情况。

3.6 小结:静态配额的适用场景

静态配额模型最适合以下场景:

  • 环境隔离:开发、测试、生产环境用不同 Namespace,配额互不影响
  • 多租户集群:每个业务线一个 Namespace,防止资源争抢
  • 预算管控:根据团队预算分配资源,超配额时自动拒绝创建

但它也有局限:配额是静态的,不会根据实际负载自动调整。比如白天实时计算任务多,晚上离线批处理任务多,静态配额无法在两者之间动态调配。嗯,这就是我们后面要讲的动态调整要解决的问题了。