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,资源配额各自独立。这样即使离线分析组把配额打满,实时计算组也不会受影响。
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 浪费调度资源。
我在项目中遇到过一个问题:某个团队在 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 小一些,保证调度灵活性 |
我曾经遇到过一个案例:数据开发同学写了个 Spark Driver,忘记配 memory limit,结果默认 limit 是 2Gi,但 Driver 实际需要 8Gi,运行几分钟就 OOMKilled 了。后来我们在 LimitRange 里把 default 调高到 4Gi,同时要求所有生产环境的 Pod 必须显式声明 resources,才算彻底解决。
3.4 三者关系:一张图看懂
下面这张图展示了 Namespace、ResourceQuota、LimitRange 的协作关系:
3.5 避坑指南:我踩过的几个坑
静态配额看起来简单,但实际用起来有不少细节。我把自己踩过的坑列出来,你遇到了可以直接抄答案:
- ResourceQuota 不限制临时存储:默认只限制 CPU、内存和持久卷。如果 Pod 写大量日志到 emptyDir,磁盘可能被打满。记得加上
requests.ephemeral-storage和limits.ephemeral-storage。 - LimitRange 不覆盖 InitContainer:InitContainer 的资源限制需要单独配置。我见过一个 InitContainer 把内存拉到 32Gi,而主 Container 只有 4Gi,结果节点 OOM。
- 配额变更不会影响已有 Pod:这是 Kubernetes 的设计,不是 bug。如果你需要动态调整,得配合 HPA 或 VPA 使用,这部分我们后面章节会讲。
- Namespace 删除后配额也消失:如果你用 GitOps 管理集群,记得把 ResourceQuota 和 LimitRange 的 YAML 也纳入版本控制。我曾经手滑删了一个 Namespace,结果配额配置全丢了,恢复花了一下午。
3.6 小结:静态配额的适用场景
静态配额模型最适合以下场景:
- 环境隔离:开发、测试、生产环境用不同 Namespace,配额互不影响
- 多租户集群:每个业务线一个 Namespace,防止资源争抢
- 预算管控:根据团队预算分配资源,超配额时自动拒绝创建
但它也有局限:配额是静态的,不会根据实际负载自动调整。比如白天实时计算任务多,晚上离线批处理任务多,静态配额无法在两者之间动态调配。嗯,这就是我们后面要讲的动态调整要解决的问题了。