K8s 资源限制配置指南
K8s 资源限制(Resource Limits)是 Pod 调度的核心依据,也是保障集群稳定性的关键配置。 配置错误会导致 OOMKill、CPU Throttling、资源浪费、调度不均等问题。
核心概念:Request vs Limit
每个容器可设置 CPU 和内存的 Request 和 Limit:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Request — 调度依据
- Request 是容器最少需要的资源量
- 调度器(kube-scheduler)只根据 Request 做调度决策,不看 Limit
- 调度器检查:Node 已分配 Request 总量 + 新 Pod 的 Request ≤ Node 实际容量
- Request 只影响调度,不影响实际资源使用
- 容器实际使用超过 Request 仍可运行,但 Node 会发生资源超售
Limit — 资源上限
- Limit 是容器可以使用的资源上限
- 内存 Limit 超限 → OOMKill,进程被强制终止
- CPU Limit 超限 → 无法获取更多 CPU 时间片,CPU 使用被节流(Throttling)
- Limit 不影响调度,只影响运行时行为
Request 和 Limit 的关系
- Request ≤ Limit(可以相等)
- CPU: 可以不相等(推荐 Request = Limit,或 Limit 是 Request 的整数倍)
- Memory: 强烈建议 Request = Limit,否则导致 OOMKill 优先级异常(见下文)
CPU:可压缩资源
CPU 是"可压缩资源"(compressible resources)。Node 资源紧张时,K8s 通过降低 CPU 时间片"挤出"更多资源, 容器只是变慢,但不会停。
CPU 计量单位
- 单位:millicores(毫核),简称 m
- 1 CPU = 1000m,500m = 0.5 CPU
- 可直接写小数,K8s 自动转 m
CPU Throttling 详解
CPU Throttling 是一个容易被忽视的性能问题。Linux CFS(Completely Fair Scheduler) 默认以 100ms 周期分配 CPU 时间片:
- 若 CPU Limit = 500m,容器在每个 100ms 周期内最多获 50ms 的 CPU 时间
- 超过 50ms 的使用请求被推迟到下一个周期
- 即使"名义上"有 500m CPU,也可能因 Throttling 导致响应延迟增加
减少 CPU Throttling 的方法:
- 适当提高 CPU Limit
- 使用 Burstable QoS(Request < Limit),让容器突发时有更多 CPU 可用
- 调整 kubelet 的
--cpu-cfs-quota和--cpu-cfs-period参数
内存:不可压缩资源
内存是"不可压缩资源"(non-compressible resources)。一旦容器申请了内存, K8s 无法像压缩 CPU 那样"回收"内存。超限即触发 OOMKill。
OOMKill 的机制
Linux OOM Killer 根据 oom_score 选择要杀掉的进程:
| QoS 等级 | oom_score_adj | OOM 优先级 |
|---|---|---|
| Guaranteed | -997 | 最低(最难杀) |
| Burstable | min(max(2, 1000 - 1000×memoryRequest/nodeMemory), 999) | 中等 |
| BestEffort | 1000 | 最高(最先杀) |
内存 Request ≠ Limit 的陷阱(关键)
当 Request < Limit 时,Pod 处于 Burstable QoS。此时 OOM Killer 会综合考虑 实际内存使用量和 Request 值 计算 oom_score。导致一个反直觉的现象:
实际使用内存很少、但 Request 设置得很高的 Pod,反而更容易被 OOMKill。
示例:
- Pod A (Burstable): requests: 2Gi, limits: 4Gi,实际只用 512Mi
- Pod B (Guaranteed): requests: 512Mi, limits: 512Mi,实际只用 512Mi
Node 内存紧张时,Pod A 反而更容易被 OOMKill,即使它的 Request 是 2Gi。
结论: 对于内存,设置 Request = Limit,使 Pod 处于 Guaranteed QoS, oom_score_adj 固定为 -997,最不容易被 OOMKill。
QoS 等级详解
K8s 为每个 Pod 自动分配 QoS(Quality of Service)等级:
| QoS 等级 | 条件 | OOM 优先级 | 调度优先级 |
|---|---|---|---|
| Guaranteed | 所有容器 CPU 和内存均设置了 Request = Limit | 最低 | 最高 |
| Burstable | 不满足 Guaranteed,但至少一个容器设置了 Request | 中等 | 中等 |
| BestEffort | 没有任何容器设置 Request 和 Limit | 最高 | 最低 |
# 查看 Pod 的 QoS 等级
kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'
kubectl describe pod <pod-name> | grep -E "QoS|Memory|Limit|Request"
注意: Guaranteed Pod 不会被 OOMKill 但不代表不会被驱逐(Eviction)。 节点压力过大时 kubelet 仍根据 eviction thresholds 驱逐,但 Guaranteed 是最后被驱逐的。
详情参见 pod-troubleshooting 的 Pending/OOMKilled 章节。
资源超售(Overcommit)
由于 Request < Limit 是常见做法(特别是 Burstable QoS),Node 上的"已分配 Requests 总和" 通常会超过 Node 实际容量,这叫资源超售。
示例:Node: 4 CPU cores
- Pod A: requests.cpu=1, limits.cpu=2
- Pod B: requests.cpu=1, limits.cpu=2
- Pod C: requests.cpu=1, limits.cpu=2
- 已分配 requests.cpu = 3(调度器认为 OK)
- 三个 Pod 全部跑满则需 6 CPU → 过载
资源超售本身不是问题,问题是超售 Pod 全部用到 Limit 时 Node 过载, 此时 CPU Throttling 和 OOMKill 会大量发生。
各类应用配置建议
Web 服务(Nginx / Apache)
resources:
requests:
memory: "64Mi" # Nginx 本身占用很小
cpu: "50m" # 基本只做转发
limits:
memory: "128Mi" # 给点余量
cpu: "200m"
Java 应用(Spring Boot / Tomcat)
Java 启动时需较多内存,稳定运行时相对稳定,但峰值可能更高。
env:
- name: JAVA_OPTS
value: "-Xmx512m -Xms256m" # 明确 JVM 堆内存
resources:
requests:
memory: "768Mi" # 覆盖 JVM heap + overhead
cpu: "500m"
limits:
memory: "768Mi" # Request = Limit → Guaranteed
cpu: "1000m" # CPU 可适当高于 memory
JVM 和容器内存配合: -Xmx 应等于或略小于容器 memory limit,
建议设置为 limit 的 75-80%,留出给 native 内存、direct buffer、mmap 等非堆内存。
Go 应用
Go 运行时管理自己的内存(GC),对容器内存限制配合较好。
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: GOMEMLIMIT # Go 1.19+,限制运行时可用内存
value: "400MiB"
数据库(MySQL / PostgreSQL)
强烈建议 Guaranteed QoS(Request = Limit):
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "2Gi" # Request = Limit → Guaranteed
cpu: "2000m"
Redis
内存密集型,对 memory limit 非常敏感:
command: ["redis-server", "--maxmemory", "1536mb", "--maxmemory-policy", "allkeys-lru"]
resources:
requests:
memory: "1536Mi" # 和 --maxmemory 对应
cpu: "500m"
limits:
memory: "1536Mi" # 两者必须对齐
cpu: "1000m"
Redis 关键原则: 容器 memory limit 必须 ≥ Redis maxmemory, 否则 Redis 不知道自己实际能用多少内存;反过来则浪费容器层隔离。
LimitRange:Namespace 级默认资源限制
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: my-app
spec:
limits:
- type: Container
default: # 容器未设置 limits 时的默认值
cpu: "100m"
memory: "128Mi"
defaultRequest: # 容器未设置 requests 时的默认值
cpu: "100m"
memory: "128Mi"
min: # 最小允许值
cpu: "10m"
memory: "16Mi"
max: # 最大允许值
cpu: "4"
memory: "8Gi"
maxLimitRequestRatio: # 限制 limit/request 比例
cpu: 10
memory: 10
ResourceQuota:限制 Namespace 总资源
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50" # Pod 数量限制
ResourceQuota 超限时新 Pod 无法创建。详情见 resource-rbac-scheduling-troubleshooting。
监控与排查命令
# 查看 Pod 资源使用(需 metrics-server)
kubectl top pods --all-namespaces
kubectl top pod <pod-name> -n <namespace> --containers
# 查看 Node 资源分配
kubectl describe node <node-name> | grep -A 10 "Allocated resources"
# 检查 Pod QoS 等级
kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass,\
CPU_REQ:.spec.containers[0].resources.requests.cpu,\
MEM_REQ:.spec.containers[0].resources.requests.memory
# 检查 OOMKill 事件
kubectl describe pod <pod-name> -n <namespace> | grep -E "Last State|Exit Code|OOMKilled"
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name> | grep -E "OOM|Kill"
ssh <node> "dmesg | grep -i 'killed process'"
# 排查 CPU Throttling(需登录 Node)
cat /sys/fs/cgroup/cpu/kubepods/burstable/<pod-id>/cpu.stat
# 关键指标: nr_throttled 和 throttled_time
常见问题与修复
| 问题 | 现象 | 根因 | 修复 |
|---|---|---|---|
| Pod Pending | 无法调度 | Node 资源不足满足 Requests | 降低 Requests / 扩容 Node / 清理低优 Pod |
| OOMKilled 不断 | 反复被杀 | memory limit 过小或有内存泄漏 | 增大 limit / 检查泄漏 / 设 Guaranteed QoS |
| 响应慢但资源未超限 | CPU Throttling | CPU limit 过紧 | 提高 CPU limit 或设 Guaranteed QoS |
| 内存用很少却被 OOMKill | Burstable QoS 陷阱 | Request < Limit 导致 | 内存设 Request = Limit |
| 多容器 Pod | 总资源需累加 | Sidecar 模式 | 调度按总 Request,OOM 按单容器 Limit |
核心要点总结
| 项目 | CPU | 内存 |
|---|---|---|
| 资源类型 | 可压缩(compressible) | 不可压缩(non-compressible) |
| 超限后果 | CPU Throttling(变慢) | OOMKill(进程被杀) |
| Request 用途 | 调度依据 | 调度依据 |
| Limit 用途 | 时间片上限 | 内存上限 |
| 配置建议 | Request = Limit 或 Request < Limit | 强烈建议 Request = Limit |
| QoS 影响 | Request = Limit → Guaranteed | Request = Limit → Guaranteed |
排查顺序:
kubectl top pod— 查看实际使用量- 检查 Pod 的 requests/limits 配置
- 检查 QoS 等级(Guaranteed / Burstable / BestEffort)
- 登录 Node 查看 cgroup 统计或 dmesg 看 OOM 事件
- 调整 requests/limits 或扩容
资源限制配置没有一劳永逸的标准值,需要结合业务特点和实际监控数据持续调整。 监控数据是优化资源限制的最终依据。
关联页面
| 页面 | 关联点 |
|---|---|
| docker-production-pitfalls | Docker 原生资源限制视角(与 K8s 资源限制互补) |
| jvm-container-oom-offheap-troubleshooting | JVM 堆外内存(DirectByteBuffer/Metaspace/线程栈)导致容器 OOMKi |
| k8s-capacity-planning-qos-cost-optimization | K8s 容量规划方法论与成本优化 — 从流量到资源预算的完整框架,含 QoS 策略、弹性伸缩协同、落 |
| k8s-java-memory-tuning-production-guide | Kubernetes 下 Java 内存调优完整指南 — 内存预算模型、生产参数配置、四层诊断流程、 |
| k8s-pod-pending-troubleshooting-guide | Pod Pending 排障指南 — 7 个排查方向(资源不足/污点/亲和性/存储/配额/选择器/端 |
| resource-rbac-scheduling-troubleshooting | 资源配额/OOMKilled 排障 |