细粒度控制:使用 LimitRange 为 Pod 设置默认请求和限制
上一章我们聊了 ResourceQuota,它管的是命名空间的总盘子。但有个问题——如果某个 Pod 没写资源限制,它就能偷偷用掉整个节点的资源。你想想看,这跟没设配额有什么区别?
LimitRange 就是来解决这个问题的。它能在 Pod 或容器级别,给那些「裸奔」的 Pod 套上缰绳。说白了,它是 ResourceQuota 的补位选手,负责兜底。
为什么需要 LimitRange?
我在项目中遇到过好几次这样的场景:开发同学图省事,Deployment 里只写了镜像名,resources 字段直接省略。结果这个 Pod 跑起来后,把同节点的其他 Pod 挤得 OOMKilled。嗯,这就是典型的「一颗老鼠屎坏了一锅粥」。
LimitRange 的核心价值就两条:
- 兜底:给没设资源限制的 Pod 自动注入默认值
- 限高:限制单个 Pod 能申请的最大/最小资源,防止恶意或意外抢占
一句话总结:ResourceQuota 管总量,LimitRange 管个量。两者配合,才能做到「大池子不溢出,小管子不爆裂」。
LimitRange 的工作原理
LimitRange 是命名空间级别的资源对象。它会在 Pod 创建时,由准入控制器(Admission Controller)自动校验和注入。流程其实很简单:
- 用户提交 Pod YAML,没写 resources 或只写了部分字段
- 准入控制器检查该命名空间是否有 LimitRange
- 如果有,按照 LimitRange 定义的规则,给 Pod 补上默认值
- 如果 Pod 的请求/限制超出了 LimitRange 的 min/max 范围,直接拒绝创建
我习惯把 LimitRange 比作「安检闸机」——你带的行李不能太小(min),也不能太大(max),如果什么都没带,我给你发个标准包(default)。
实战:编写 LimitRange 配置
直接看一个完整的例子。这是我常用的生产级配置:
apiVersion: v1
kind: LimitRange
metadata:
name: production-limits
namespace: production
spec:
limits:
- type: Container
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "200m"
memory: "256Mi"
maxLimitRequestRatio:
cpu: 4
memory: 4
这里每个字段都有讲究:
| 字段 | 含义 | 我的建议 |
|---|---|---|
max |
单个容器能申请的最大资源 | 根据节点规格设置,一般不超过节点可分配资源的 50% |
min |
单个容器必须申请的最小资源 | 防止 sidecar 容器只写 1m CPU,导致调度碎片 |
default |
未设置 limits 时的默认值 | 建议设为中等偏下,避免资源浪费 |
defaultRequest |
未设置 requests 时的默认值 | 通常比 default 小,体现 requests ≤ limits 的原则 |
maxLimitRequestRatio |
limits 与 requests 的最大比值 | 防止 limits 设得很大但 requests 很小,导致超卖严重 |
我的习惯:defaultRequest 一般设为 default 的 50% 左右。比如 default 是 512Mi,defaultRequest 就设 256Mi。这样既保证了 Pod 的基本调度,又留了 burst 空间。
LimitRange 的生效范围
你可能注意到了,上面的配置里 type: Container。LimitRange 支持两种 scope:
- Container:作用于每个容器,最常用
- Pod:作用于整个 Pod 级别的资源总和(比如 Pod 的 overhead)
我建议优先用 Container 类型。Pod 类型用得少,而且容易跟 ResourceQuota 搞混。除非你有特殊的 QoS 保障需求,否则 Container 就够了。
多 LimitRange 的叠加规则
一个命名空间可以创建多个 LimitRange。我曾经踩过这个坑——以为后创建的会覆盖前面的,结果发现它们是叠加生效的。
规则很简单:
- 所有 LimitRange 的
min取最大值 - 所有 LimitRange 的
max取最小值 - 所有 LimitRange 的
default和defaultRequest互不干扰,各自生效
举个例子:
# LimitRange A
- type: Container
min:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
# LimitRange B
- type: Container
min:
cpu: "200m"
memory: "256Mi"
max:
cpu: "1"
memory: "2Gi"
最终生效的是:min 取 cpu 200m / memory 256Mi,max 取 cpu 1 / memory 2Gi。说白了,就是取最严格的交集。
⚠ 注意:多 LimitRange 叠加容易造成配置混乱。我建议一个命名空间只维护一个 LimitRange,把所有规则写在一起。如果确实需要分层管理,一定要做好文档记录。
验证 LimitRange 是否生效
创建完 LimitRange 后,怎么确认它起作用了?我一般用三步验证法:
- 查看 LimitRange 状态:
kubectl get limitrange -n production,确认已创建 - 创建测试 Pod:提交一个不带 resources 的 Pod,然后
kubectl describe pod test-pod,看是否自动注入了默认值 - 测试边界:提交一个 requests 超过 max 的 Pod,确认被拒绝
这是我常用的测试 YAML:
# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-limitrange
namespace: production
spec:
containers:
- name: test
image: nginx
# 故意不写 resources,看 LimitRange 是否注入
创建后查看:
kubectl describe pod test-limitrange -n production
# 输出中应该能看到:
# Limits:
# cpu: 500m
# memory: 512Mi
# Requests:
# cpu: 200m
# memory: 256Mi
如果没看到这些字段,说明 LimitRange 没生效。常见原因有两个:
- LimitRange 所在的命名空间不对
- Pod 的容器已经显式设置了 resources(哪怕只设了一个字段,LimitRange 也不会覆盖)
避坑指南
我用 LimitRange 这几年,踩过几个比较典型的坑,分享给你:
我曾经在一个生产集群里,给所有命名空间统一部署了 LimitRange,结果有个批处理任务因为需要申请 8 核 CPU,被 LimitRange 的 max 拦住了。后来我学乖了——对特殊 workload,要么单独建命名空间,要么在 Pod 里显式写 resources 来绕过默认值。
其他常见问题:
- default 和 defaultRequest 不匹配:如果 default 比 defaultRequest 小,Kubernetes 会报错。记住,limits 必须 ≥ requests。
- 忘记设置 maxLimitRequestRatio:有些开发会把 requests 设得很低(比如 10m CPU),limits 设得很高(比如 4 CPU),导致节点超卖严重。这个比值参数就是用来防这种操作的。
- LimitRange 不适用于 InitContainer:InitContainer 的资源限制需要单独设置,LimitRange 不会自动注入。
与 ResourceQuota 的配合策略
最后聊聊两者的配合。我一般这样设计:
| 场景 | ResourceQuota | LimitRange |
|---|---|---|
| 开发环境 | 设总配额,防止某个团队用光所有资源 | 设默认值,降低开发心智负担 |
| 生产环境 | 严格配额,每个团队按预算分配 | 设 min/max,防止单个 Pod 异常 |
| CI/CD 环境 | 设较小配额,避免构建任务堆积 | 设较小默认值,避免资源浪费 |
我个人习惯是:先部署 LimitRange,再部署 ResourceQuota。因为 LimitRange 能保证所有 Pod 都有资源限制,ResourceQuota 才能准确统计和限制总量。顺序反了的话,可能会出现 ResourceQuota 统计到一些没有限制的 Pod,导致配额计算不准。
嗯,LimitRange 的内容就这些。它虽然简单,但却是资源治理的基石。没有它,ResourceQuota 就像没有门的围墙——拦得住大象,挡不住蚂蚁。