细粒度控制:使用 LimitRange 为 Pod 设置默认请求和限制

上一章我们聊了 ResourceQuota,它管的是命名空间的总盘子。但有个问题——如果某个 Pod 没写资源限制,它就能偷偷用掉整个节点的资源。你想想看,这跟没设配额有什么区别?

LimitRange 就是来解决这个问题的。它能在 Pod 或容器级别,给那些「裸奔」的 Pod 套上缰绳。说白了,它是 ResourceQuota 的补位选手,负责兜底。

为什么需要 LimitRange?

我在项目中遇到过好几次这样的场景:开发同学图省事,Deployment 里只写了镜像名,resources 字段直接省略。结果这个 Pod 跑起来后,把同节点的其他 Pod 挤得 OOMKilled。嗯,这就是典型的「一颗老鼠屎坏了一锅粥」。

LimitRange 的核心价值就两条:

  • 兜底:给没设资源限制的 Pod 自动注入默认值
  • 限高:限制单个 Pod 能申请的最大/最小资源,防止恶意或意外抢占

一句话总结:ResourceQuota 管总量,LimitRange 管个量。两者配合,才能做到「大池子不溢出,小管子不爆裂」。

LimitRange 的工作原理

LimitRange 是命名空间级别的资源对象。它会在 Pod 创建时,由准入控制器(Admission Controller)自动校验和注入。流程其实很简单:

  1. 用户提交 Pod YAML,没写 resources 或只写了部分字段
  2. 准入控制器检查该命名空间是否有 LimitRange
  3. 如果有,按照 LimitRange 定义的规则,给 Pod 补上默认值
  4. 如果 Pod 的请求/限制超出了 LimitRange 的 min/max 范围,直接拒绝创建

我习惯把 LimitRange 比作「安检闸机」——你带的行李不能太小(min),也不能太大(max),如果什么都没带,我给你发个标准包(default)。

LimitRange 准入控制流程 用户提交 Pod 未设置 resources 准入控制器 检查 LimitRange 有 LimitRange 注入默认值 / 校验范围 无 LimitRange Pod 裸奔通过 Pod 创建 带限制 Pod 创建 无限制 ⚠ 关键点: LimitRange 只影响创建时未显式设置资源的 Pod。如果 Pod 已经写了 requests/limits,LimitRange 不会覆盖。

实战:编写 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。我曾经踩过这个坑——以为后创建的会覆盖前面的,结果发现它们是叠加生效的。

规则很简单:

  1. 所有 LimitRange 的 min 取最大值
  2. 所有 LimitRange 的 max 取最小值
  3. 所有 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 后,怎么确认它起作用了?我一般用三步验证法:

  1. 查看 LimitRange 状态:kubectl get limitrange -n production,确认已创建
  2. 创建测试 Pod:提交一个不带 resources 的 Pod,然后 kubectl describe pod test-pod,看是否自动注入了默认值
  3. 测试边界:提交一个 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 就像没有门的围墙——拦得住大象,挡不住蚂蚁。