结论先行
Pod 被 OOMKilled 的直接原因是容器内进程使用的内存超过了 cgroup 的 memory limit,内核 OOM killer 在容器自己的 cgroup 内选中了进程。
但更常见的根因是:requests 与 limits 设置错配,导致调度、QoS 等级和实际内存需求三者不一致。错配不会直接触发 OOM,而是让 OOM 更容易发生、更难排查。
requests 与 limits 分别管什么
- requests:调度依据。kube-scheduler 用
sum(requests)判断节点是否有足够资源放下 Pod。它不限制运行时用量。 - limits:运行时上限。容器运行时(Docker/containerd)把 limits 写入 cgroup:
memory.limit_in_bytes(cgroup v1)或memory.max(cgroup v2)。
内存与 CPU 的一个关键区别:
- CPU 是可压缩资源,超限只会被 throttle(限流),不会杀进程。
- 内存是不可压缩资源,超限无法“归还”,只能触发 OOM。
错配的三种典型形态
1. limits 远大于 requests
requests.memory: 256Mi
limits.memory: 2Gi
调度器按 256Mi 计算,节点上可以塞进大量 Pod。运行时每个 Pod 却可能吃到 2Gi,节点内存被超额分配(overcommit),最终节点级 OOM 或大量 Pod 被驱逐。
2. limits 设置过小
应用实际需要 800Mi,limits 只给了 512Mi。进程一旦增长到 512Mi 就被 cgroup OOM killer 杀掉,表现为反复 CrashLoopBackOff + OOMKilled。
3. requests 与 limits 相等
这种配置会让 Pod 进入 Guaranteed QoS 等级,节点内存紧张时最后被驱逐,通常是最稳妥的选择,但需要准确评估用量。
QoS 等级如何影响被杀的优先级
Kubernetes 按 requests/limits 把 Pod 分为三档,节点内存压力下的驱逐顺序是:
- BestEffort:所有容器都没有设置 requests 和 limits,最先被驱逐。
- Burstable:设置了但不相等,中间档。
- Guaranteed:每个容器的 CPU/内存 requests 都等于 limits(且都设置了),最后被驱逐。
注意:QoS 影响的是节点级驱逐的优先级,而容器级 OOM 由容器自己的 cgroup limit 决定,两者是不同层面的机制。
被 OOMKilled 之前发生了什么
- 容器内进程申请内存,cgroup 记账接近
memory.max。 - 超过 limit 时,内核先尝试回收(page cache 等),回收不动就进入 cgroup OOM。
- 内核 OOM killer 在该 cgroup 内按
oom_score选进程杀掉(通常选内存占用最大的)。 - 容器主进程死亡,kubelet 按
restartPolicy重启容器,kubectl describe pod中出现Last State: Terminated, Reason: OOMKilled。
排查与调整建议
确认是否真的 OOM:
kubectl describe pod <pod-name> | grep -A5 'Last State'
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].lastState}'
看实际用量: 用 kubectl top pod(依赖 metrics-server)或容器内 cat /sys/fs/cgroup/memory.max(cgroup v2)/ memory.limit_in_bytes(cgroup v1)核对 limit 值。
调整原则:
- 先观察一段时间的实际内存峰值,再设置 limits,留出合理余量(如峰值的 1.2~1.5 倍)。
- requests 尽量贴近稳态用量,避免调度过度乐观。
- 对内存敏感的服务,优先采用 requests = limits 的 Guaranteed 配置。
- 不要为了“省事”把 limits 设得极大,那等于把风险从容器转移到节点。
一句话总结
OOMKilled 是结果,requests/limits 错配是原因之一。理解 requests 管调度、limits 管运行时上限、QoS 管驱逐顺序这三层分工,才能把内存问题定位在正确的层面。