Requests 与 Limits 的语义

Kubernetes 中每个容器可以配置 CPU 和 Memory 的 requestslimits。很多开发者分不清二者的区别,导致 Pod 频繁被驱逐或 OOMKilled。

apiVersion: v1
kind: Pod
metadata:
  name: api-server
spec:
  containers:
    - name: app
      image: myapp:latest
      resources:
        requests:
          cpu: "250m"    # 0.25 核
          memory: "256Mi"
        limits:
          cpu: "500m"    # 0.5 核
          memory: "512Mi"

核心区别

维度RequestsLimits
作用阶段调度时运行时
含义Pod 需要的最小资源保证Pod 能使用的最大资源上限
调度器行为调度器根据 requests 判断节点是否有足够资源调度器不关心 limits
运行时行为cgroups 中的保障份额CPU 被节流(throttle),Memory 触发 OOMKilled
是否可超卖可以(节点上所有 Pod 的 limits 之和可超过节点容量)不建议超卖 Memory

简单理解:

  • requests 是调度时的承诺——“这个 Pod 至少需要 0.25 核 CPU 和 256Mi 内存,请找一个够用的节点”
  • limits 是运行时的限制——“这个 Pod 最多能用 0.5 核 CPU,内存超过 512Mi 就杀掉它”

CPU vs Memory 的本质差异

CPU 是可压缩资源(compressible):当容器超过 CPU limit 时,内核通过 CFS(Completely Fair Scheduler)进行限流(throttle),容器变慢但不会被杀。

Memory 是不可压缩资源(incompressible):当容器超过 Memory limit 时,内核直接触发 OOM Killer 杀死容器进程,退出码 137。

本文参考 Kubernetes 官方资源管理文档

QoS 等级

Kubernetes 根据每个 Pod 的 requests 和 limits 配置,自动为 Pod 分配 QoS(Quality of Service)等级。QoS 决定了节点资源紧张时 Pod 被驱逐的优先级。

三级 QoS 判定规则

                    ┌─────────────────────────┐
                    │  所有容器的 requests     │
                    │    == limits(CPU+Mem)? │
                    └──────┬──────────┬───────┘
                        Yes│          │No
                           ▼          ▼
                    ┌──────────┐  ┌──────────────────┐
                    │Guaranteed│  │ 有容器设了requests? │
                    │ (最高)    │  └────┬─────────┬─────┘
                    └──────────┘    Yes│         │No
                                       ▼         ▼
                                ┌───────────┐ ┌────────────┐
                                │Burstable  │ │BestEffort  │
                                │ (中等)     │ │ (最低)      │
                                └───────────┘ └────────────┘

Guaranteed(保证级)

条件:Pod 中每个容器都同时设置了 CPU 和 Memory 的 requests 和 limits,且 requests == limits。

resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

Guaranteed 级 Pod 在节点资源紧张时最后被驱逐,适合核心服务(数据库、API 网关等)。

Burstable(突发级)

条件:Pod 不满足 Guaranteed 条件,但至少有一个容器设置了 requests。

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "1Gi"

Burstable Pod 允许在空闲时使用更多资源,节点紧张时中等优先级被驱逐。大多数微服务适合这个等级。

BestEffort(尽力级)

条件:Pod 中所有容器都没有设置 requests 和 limits。

resources: {}

BestEffort Pod 最先被驱逐,且不保证任何资源。只适合临时任务、测试 Pod。

QoS 查看方法

# 查看单个 Pod 的 QoS
kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'

# 查看命名空间下所有 Pod 的 QoS
kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass

OOMKilled 问题诊断与排查

OOMKilled 的两种形态

类型原因表现
Container OOMKilled容器内存超过 limits.memoryExit Code: 137Reason: OOMKilled
Node OOMKilled节点整体内存耗尽,内核杀进程Pod 被驱逐,节点进入 MemoryPressure

诊断步骤

# 1. 查看 Pod 状态,确认是否 OOMKilled
kubectl describe pod <pod-name> | grep -A5 "Last State"

# 输出示例:
# Last State: Terminated
#   Reason: OOMKilled
#   Exit Code: 137

# 2. 查看退出码
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# 137

# 3. 查看节点资源使用情况
kubectl describe node <node-name> | grep -A10 "Allocated resources"

# 4. 查看节点是否有 MemoryPressure
kubectl describe node <node-name> | grep -i "MemoryPressure"

排查思路

# 情况一:容器被 OOMKilled,但节点内存充足
# → 说明 limits.memory 设置过低,需调大

# 情况二:容器被 OOMKilled,节点内存也紧张
# → 节点超卖严重,检查节点上所有 Pod 的 requests 之和

# 查看节点上所有 Pod 的内存 requests 总和
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].resources.requests.memory}{"\n"}{end}' --field-selector spec.nodeName=<node-name>

常见根因

  1. Memory Leak:应用存在内存泄漏,长时间运行后内存持续增长直至触发 limit。通过监控观察内存曲线是否单调上升来判断。
  2. limit 设置过低:JVM 应用未正确设置堆内存参数(-Xmx),容器 limit 小于 JVM 默认堆大小。
  3. 节点超卖:节点上所有 Pod 的 requests.memory 之和远超节点实际内存,导致调度时虽然"满足"了 requests 但实际不够用。
# 监控内存趋势(配合 Prometheus)
# 查询:过去 6 小时容器内存使用率
container_memory_working_set_bytes{pod="<pod-name>"} 
  / container_spec_memory_limit_bytes{pod="<pod-name>"} 
  * 100

ResourceQuota 与 LimitRange

ResourceQuota:命名空间级配额

ResourceQuota 限制命名空间能申请的资源总量,防止单个团队耗尽集群资源。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: production
spec:
  hard:
    requests.cpu: "20"          # 命名空间内所有 Pod CPU requests 总和上限
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    persistentvolumeclaims: "10" # PVC 数量上限
    count/deployments.apps: "20" # Deployment 数量上限
    count/pods: "50"
# 查看命名空间配额使用情况
kubectl describe resourcequota team-quota -n production

# 输出:
# Name:            team-quota
# Resource         Used  Hard
# --------         ----  ----
# requests.cpu     12    20
# requests.memory  28Gi  40Gi
# limits.cpu       24    40
# limits.memory    56Gi  80Gi
# pods             18    50

LimitRange:Pod/容器级默认值与约束

LimitRange 为命名空间内的 Pod 设置默认 requests/limits,并限制单个容器能申请的资源范围。

apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: production
spec:
  limits:
    - type: Container
      default:          # 未设 limits 时的默认值
        cpu: "500m"
        memory: "512Mi"
      defaultRequest:   # 未设 requests 时的默认值
        cpu: "100m"
        memory: "128Mi"
      max:              # 单容器最大可申请
        cpu: "4"
        memory: "8Gi"
      min:              # 单容器最小必须申请
        cpu: "50m"
        memory: "64Mi"

LimitRange 解决的问题是:

  • 防止忘记设置资源——未配置的 Pod 自动获得默认值,避免变成 BestEffort
  • 防止申请过大——max 限制单个容器不能超过 4 核 8Gi
  • 防止申请过小——min 确保至少 50m CPU,避免饿死
# 验证:创建一个不设 resources 的 Pod,观察自动注入的默认值
kubectl run test-pod --image=nginx
kubectl get pod test-pod -o jsonpath='{.spec.containers[0].resources}'
# {"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}

Vertical Pod Autoscaler(VPA)

VPA 自动调整 Pod 的 requests 和 limits,基于历史资源使用数据推荐合适的值。

安装 VPA

# 克隆 autoscaler 仓库
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler

# 安装 VPA 组件
./hack/vpa-up.sh

# 验证
kubectl get pods -n kube-system | grep vpa
# vpa-admission-controller-xxx   1/1 Running
# vpa-recommender-xxx            1/1 Running
# vpa-updater-xxx                1/1 Running

VPA 三种模式

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-server-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: api-server
  updatePolicy:
    updateMode: "Auto"  # Off | Initial | Auto
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 100m
          memory: 128Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi
模式行为适用场景
Off只给出推荐值,不修改 Pod评估阶段,观察推荐值是否合理
Initial只在 Pod 创建时设置 requests,不修改已运行的 Pod保守策略,避免重启
Auto自动修改 requests,会驱逐并重建 Pod生产使用(需接受重建)

VPA 推荐值解读

kubectl describe vpa api-server-vpa -n production

# 输出:
# Recommendation:
#   Container Recommendation:
#     Target:    cpu 250m, memory 384Mi    # 推荐值(最合适)
#     Lower Bound: cpu 100m, memory 256Mi  # 下界(最小可接受)
#     Upper Bound: cpu 500m, memory 768Mi  # 上界(最大可能需要)
#     Uncapped Target: cpu 250m, memory 384Mi  # 不受 minAllowed/maxAllowed 约束时的推荐

VPA 注意事项

  1. VPA 会驱逐 Pod——Auto 模式下修改 requests 需要重建 Pod,可能导致短暂不可用。不要对无法容忍重启的有状态服务使用 VPA Auto 模式。
  2. VPA 与 HPA 不能同时基于同一资源——如果 HPA 基于 CPU 伸缩,VPA 也在调 CPU requests,两者会互相干扰。可以让 VPA 只管 Memory,HPA 管 CPU。
  3. 先 Off 后 Auto——新部署的 VPA 先用 Off 模式观察 3-7 天推荐值,确认合理后再切 Auto
  4. 设置 minAllowed / maxAllowed——防止 VPA 推荐极端值。

生产实践:资源推荐值设定方法论

经验法则

没有放之四海而皆准的公式,但以下方法论适用于大多数场景:

第一步:基线测量

# 部署应用后,先不设 limits(但设 requests 保底)
resources:
  requests:
    cpu: "100m"
    memory: "256Mi"

# 运行 7 天,收集 Prometheus 指标
# P95 CPU 使用率、P95 Memory 使用率、峰值 Memory

第二步:设定公式

requests.cpu    = P95 CPU 使用值 × 1.5
requests.memory = P95 Memory 使用值 × 1.2
limits.cpu      = requests.cpu × 2 (或 P99 CPU × 2)
limits.memory   = requests.memory × 1.5 (或峰值 Memory × 1.2)

第三步:验证与迭代

# 部署后观察 7 天
# - CPU throttle 率 < 5%?  → limits.cpu 合理
# - Memory 使用率 < 80%?   → limits.memory 合理
# - Pod 被 OOMKilled?      → 加大 limits.memory
# - Pod 调度失败?           → 检查集群总 requests 容量

按应用类型推荐

应用类型requests.cpurequests.memorylimits.cpulimits.memoryQoS
API 服务250m256Mi500m512MiBurstable
数据库(MySQL/PG)1000m2Gi2000m4GiGuaranteed
消息队列(Redis)500m1Gi1000m2GiGuaranteed
日志采集(Fluentd)100m128Mi200m256MiBurstable
批处理任务500m512Mi2000m2GiBurstable
前端静态服务50m64Mi100m128MiBurstable

核心服务设为 Guaranteed

# 生产环境核心服务:requests == limits,QoS = Guaranteed
resources:
  requests:
    cpu: "1000m"
    memory: "2Gi"
  limits:
    cpu: "1000m"
    memory: "2Gi"

核心服务(数据库、网关、认证服务)建议设为 Guaranteed,确保:

  • 调度时资源 100% 保证,不会因超卖导致资源竞争
  • 节点资源紧张时最后被驱逐
  • CPU 不会被 throttle(因为 requests == limits,CFS quota 等于保障值)

监控资源使用率

# CPU 使用率(相对于 limits)
sum(rate(container_cpu_usage_seconds_total{pod=~"api-.*"}[5m])) by (pod)
  / 
sum(kube_pod_container_resource_limits{resource="cpu", pod=~"api-.*"}) by (pod)
  * 100

# Memory 使用率(相对于 limits)
container_memory_working_set_bytes{pod=~"api-.*"}
  /
container_spec_memory_limit_bytes{pod=~"api-.*"}
  * 100

# CPU throttle 检测
rate(container_cpu_cfs_throttled_seconds_total{pod=~"api-.*"}[5m]) * 100

总结

Kubernetes 资源管理是生产稳定性的基石。核心要点:

  1. requests 决定调度,limits 决定运行——二者缺一不可,不设资源的 Pod 是定时炸弹
  2. QoS 等级直接影响生存权——核心服务设 Guaranteed,普通服务设 Burstable,临时任务才用 BestEffort
  3. OOMKilled 是最常见的容器异常——退出码 137,排查路径是 describe pod → 看 limits → 看监控曲线 → 区分泄漏还是配置过小
  4. ResourceQuota + LimitRange 是集群级护栏——防止团队超卖资源,自动为忘记配置的 Pod 注入默认值
  5. VPA 是好工具但需谨慎——先 Off 观察,再 Auto 应用,与 HPA 避免管理同一资源
  6. 资源设定没有银弹——基线测量 → 公式估算 → 上线观察 → 持续迭代,这才是正确的方法论

资源管理做不好,再好的架构设计也会在流量高峰时崩塌。把 requests 和 limits 当作每个 Pod 的安全带,这是 SRE 的底线。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Kubernetes 官方资源管理文档 — Kubernetes 官方,参考了Kubernetes 官方资源管理文档相关内容