requests 与 limits 到底在管什么
requests.cpu 是调度器分配的依据:把 Pod 排到有足够可分配 CPU 的节点上,也是节点满载时算「谁的份额」的权重。requests.memory 同理,但内存不可压缩,所以它同时是节点内存压力的实际占用。
limits.cpu 是硬上限,超过后容器不会被杀,而是被 CFS 限流(throttling):进程仍可运行,但在每个 100ms 配额周期内只被分配到限额对应的 CPU 时间,超出部分被挂起。requests 与 limits 相等时才是「保证份额」。
limits.memory 是不可逾越的硬墙。容器内存占用触碰上限,内核 OOM Killer 会立刻杀掉容器进程,Pod 重启,退出码 137。这个机制与 CPU 完全不同,也因此不能靠「调大一点」来掩盖内存泄漏。
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1GiQoS 三档与驱逐优先级
QoS 由 kubelet 计算,不是你填的标签。Guaranteed 要求每个容器的 CPU 和内存都设了 limits 且等于 requests;缺一个不等就是 Burstable;完全不设是 BestEffort。
节点内存紧张时驱逐按 QoS 反序进行:BestEffort 先被驱逐,Burstable 依据「实际用量超出 request 的程度」排序,Guaranteed 最后。这解释了为什么给关键服务设 Guaranteed 能在节点压力下活下来,而没设 requests 的 Pod 总是第一个被赶走。
实践中「limits = requests」的 CPU 是最容易被误解的一项:设了之后容器确实独占那一份配额,但如果应用实际只用 100m,就会长期闲置被占用的 500m。判断方法是看 node-pressure 和容器 CPU 使用率,而不是凭直觉。
apiVersion: v1
kind: LimitRange
metadata:
name: default-container-limits
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: 50m
memory: 64MiOOMKilled 的正确归因
先分清两种 OOM。container OOMKilled 是容器自身内存超了 limits,reason 字段为 OOMKilled,退出码 137;node OOM 是整机内核杀进程,Pod 事件里通常出现 Out of memory: Killed process,节点上其它 Pod 可能同时受影响。
容器 OOM 的标准排查链路是:`kubectl describe pod` 确认 reason 为 OOMKilled,再 `kubectl logs --previous` 看崩溃前的业务日志,最后确认容器是不是**没有**设 memory limit(未设限额的容器一旦被节点压力驱逐也会被内核杀掉,容易被误记为 OOMKilled)。
内存上限的正确做法是先用 profiling 定位泄漏再定值。若只是缓存偏大,明确把缓存上限写进代码参数(例如 JVM 的 -XX:MaxRAMPercentage 或显式堆上限),而不是把 limits.memory 一路调大到让 OOM 永远不发生,那只是把故障推给节点。
kubectl get pod <pod> -o jsonpath="{.status.containerStatuses[*].lastState.terminated.reason}"
kubectl describe pod <pod> | grep -A5 "Last State"
kubectl logs <pod> --previous --tail=200
kubectl top pod <pod> --containersCPU 限流怎么观测
CPU limit 造成的问题不会表现为重启,只表现为延迟上升,所以更容易被忽略。可靠的信号是 CFS 限流指标 container_cpu_cfs_throttled_seconds_total 与 container_cpu_cfs_periods_total 的比值。
用 Prometheus 查询该比值:如果 throttled periods 占比持续偏高,说明配额不足。典型场景是 init 阶段的突发计算、批处理任务和 GC 密集的应用,它们的 P99 延迟会被限流周期整段拉长。
调节顺序建议是:先看 requests 与实际使用量的差距确认调度浪费,再决定是抬高 limits.cpu 还是优化代码。注意抬高 limits 会降低 QoS 保障等级,也可能让节点更容易压力,所以更倾向于对延迟敏感的负载显式设 Guaranteed,而对吞吐型后台任务放宽。
还要留意 init 容器的资源语义。init 容器按顺序执行、峰值内存单独计算,它的需求会叠加进 Pod 的有效请求,写得过大会让调度器把整个 Pod 排到大节点上。多个 init 容器之间是取最大值而非求和,但与业务容器的叠加关系仍需按实际峰值估算。
container_cpu_cfs_throttled_seconds_total / container_cpu_cfs_periods_total
kubectl describe node <node> | sed -n "/Allocated resources:/,/Events:/p"从监控数据反推 requests
requests 的合理值来自实测分布,而不是拍脑袋。取一段时间内 CPU 使用率的 P95 或 P99 作为 requests 起点,能让绝大多数时间都不被限流,同时避免为偶发尖峰预留过多导致调度浪费。
内存要按峰值而非均值定:内存不可回收,limits.memory 必须高于实际峰值的 1.3 到 1.5 倍以容纳 GC 波动与请求突发。用平均内存设 request 会在突发时被 OOMKill,用平均设 limit 则会在运行期被不断回收。
定完之后要做一次压力验证:在目标节点上按 requests 总量超额部署,看 Pod 是否仍能启动。如果节点连 requests 都装不下,调度器会一直 Pending,此时限制粒度应下沉到容器级;如果能装下但节点被压满,说明 Burstable 服务需要在集群层面加节点而不是改单个 Pod 的数字。
# 用 metrics-server 抓当前用量分布,再据此确定 requests
kubectl top pod -l app=api --containers
# 确认调度可行性:Pending 通常是节点 requests 装不下
kubectl get pods -l app=api -o jsonpath="{.items[*].status.conditions[?(@.type=='PodScheduled')].status}"
kubectl describe pod <pending-pod> | sed -n "/Events:/,$ p"