Kubernetes 基础体系 · 第 21/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes Resource 与 QoS:Request、Limit、CPU Throttle、OOM 和 Eviction
在 Kubernetes 中,容器资源配置同时影响四件事:
- Scheduler 是否允许 Pod 进入某个节点;
- 节点上的运行时是否限制容器使用 CPU 或内存;
- 节点发生资源压力时,哪些 Pod 更可能被驱逐;
- 容器被内核 OOM Kill、被 kubelet 驱逐或仅仅被 CPU 限速时,故障表现有何不同。
requests、limits、QoS、CPU throttle、OOM 和 eviction 不是互相独立的配置项。它们分别位于调度、Linux cgroup 运行时控制、内核内存回收以及 kubelet 节点治理等不同层次。要正确诊断问题,必须先区分这些层次。
一、先建立资源模型:谁读取 Request,谁执行 Limit
一个 Pod 的资源配置通常写在容器上:
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
这里有四个独立的数值:
cpu request:调度器为该容器计算所需 CPU 的依据;memory request:调度器为该容器计算所需内存的依据;cpu limit:运行时允许该容器使用的 CPU 上限;memory limit:运行时允许该容器使用的内存上限。
它们的关系可以概括为:
Pod 提交
│
├── API Server / Admission:默认值、校验、配额
│
├── Scheduler:主要使用 requests 选择节点
│
└── Kubelet + 容器运行时 + Linux cgroup
├── 执行 CPU limit
├── 执行 memory limit
└── 监控节点压力并执行 eviction
因此,以下两个结论都不正确:
- “设置了
request,容器就能稳定获得这部分内存或 CPU。” - “设置了
limit,调度器就一定为容器预留了这部分资源。”
更准确的表述是:
request主要是调度和资源计算的声明;limit主要是运行时的使用上限。二者都不是对物理资源进行绝对、无条件预留的通用语义。
二、CPU 和内存资源的单位
2.1 CPU:核心数和 millicore
CPU 的资源单位是 CPU 核心数:
requests:
cpu: "1"
表示请求一个逻辑 CPU 单位。也可以用 millicore:
requests:
cpu: "250m"
其中:
1 CPU = 1000m
250m = 0.25 CPU
250m 不是“固定绑定四分之一颗 CPU”。在多核节点上,它通常表示容器在调度和 CPU 时间分配中拥有相当于 0.25 CPU 的资源权重或上限,而不是绑定到某个物理核上。
CPU request 在 Linux cgroup 中通常还会影响 CPU 权重:当多个容器竞争 CPU 时,请求较大的容器通常获得更高的相对权重。但这属于运行时和 cgroup 实现提供的资源分配机制,不等于实时预留,也不保证业务延迟。
2.2 内存:字节数量,而不是 CPU 时间
内存可以使用 Mi、Gi 等二进制单位:
memory: "256Mi"
也可以使用 M、G 等十进制单位:
1 Mi = 2^20 bytes
1 M = 10^6 bytes
256Mi 和 256M 并不相同。
内存 request 和 limit 通常包含进程匿名内存、文件页缓存以及由 cgroup 计量的其他内存类型。具体统计范围会受 Linux 内核、cgroup v1/v2、容器运行时和 Kubernetes 版本影响,因此不能简单把 ps 输出的 RSS 当成 Kubernetes 计量值。
例如,容器使用的 emptyDir 如果介质是内存:
volumes:
- name: cache
emptyDir:
medium: Memory
这部分数据会占用节点内存,并通常计入 Pod 的内存压力;它不是“免费缓存”。
2.3 其他资源
Kubernetes 还支持:
ephemeral-storage:容器可写层和临时卷等本地临时存储;- 扩展资源:例如 GPU;
- HugePages 等特殊资源。
这些资源不完全遵循 CPU 和内存的运行时行为。例如 GPU 通常是不可超卖的整数资源,不能把 GPU 当成可通过 CPU throttle 方式共享的资源。本文重点讨论 CPU、内存和与驱逐相关的临时存储。
三、Request 如何影响调度
3.1 节点是否可调度,首先看 Pod 的 Request
设节点对某资源的可分配容量为:
节点上已经调度的 Pod 对该资源的 request 总和为:
新 Pod 的 request 为:
调度器在资源过滤阶段通常要求:
这个判断是“声明式容量计算”,不是实时读取节点上当前进程实际使用量。
例如,某节点的可分配 CPU 为 4 核,已经有两个 Pod:
| Pod | CPU request | 当前实际使用 |
|---|---|---|
| A | 2 CPU | 0.5 CPU |
| B | 1.5 CPU | 0.3 CPU |
虽然当前实际使用只有 0.8 CPU,但调度器已经认为该节点有:
被 request 占用,因此最多只能再调度 request 不超过 0.5 CPU 的 Pod。一个 request 为 1 CPU 的新 Pod 可能因为 Insufficient cpu 无法调度,即使节点监控看起来非常空闲。
反过来,如果现有 Pod 的 request 总和只有 2 CPU,但它们的实际使用或 limit 总和可以达到 6 CPU,而节点只有 4 CPU,调度器仍可能接受它们。这就是资源超卖的基础。
3.2 节点可分配容量不是硬件总容量
调度器使用的通常是 Node 的 allocatable,而不是机器的物理总容量:
kubectl describe node <node-name>
常见输出片段类似:
Capacity:
cpu: 8
memory: 32745256Ki
Allocatable:
cpu: 7500m
memory: 30000000Ki
两者之间的差额可能用于:
- 操作系统;
- kubelet;
- 容器运行时;
- Kubernetes 系统组件;
- 云厂商代理;
- 管理员配置的
systemReserved、kubeReserved; - eviction 相关的安全余量。
allocatable 的具体值取决于节点配置和发行版,不能用“物理内存减去一个固定百分比”推断。
3.3 Pod request 的计算不是简单逐项相加
对于普通容器,某资源的 Pod request 大致是所有应用容器 request 之和。但包含 init container 时,必须考虑 init container 的串行执行特性。
对某资源 ,Pod 的有效 request 可以表示为:
例如:
spec:
initContainers:
- name: init
image: busybox:1.36
resources:
requests:
cpu: "2"
memory: "1Gi"
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "500m"
memory: "256Mi"
应用容器合计为 500m CPU、256Mi 内存,但 init container 需要 2 CPU、1 GiB,因此调度时该 Pod 的有效 request 至少要按 init container 的峰值计算。否则,init 阶段可能无法在节点容量内运行。
这也是一个常见反例:应用长期只需要 0.5 CPU,却因为启动迁移任务的 init container request 为 4 CPU,导致 Pod 长期难以调度。
四、Limit 如何影响运行时
4.1 CPU Limit 是可被强制执行的时间上限
CPU 是可压缩资源。所谓“可压缩”,是指系统可以减少进程获得的 CPU 时间,而不必立即杀死进程。
在常见 Linux cgroup 实现中,CPU limit 通常通过 CFS bandwidth control 实现。可以用一个简化模型理解:
- CPU quota:一个周期内允许容器使用的 CPU 时间;
- CFS period:CPU 配额的统计周期;
- 当周期内的 CPU 时间耗尽,容器在剩余时间内被限制运行。
例如:
limits:
cpu: "500m"
它通常意味着每个调度周期最多使用约半个 CPU 的计算时间。具体的 period、cgroup 文件以及 cgroup v1/v2 行为由内核、容器运行时和 kubelet 实现决定,不应依赖某个固定 cgroup 路径。
如果进程持续需要 1 CPU,而 limit 只有 500m,运行时可能让它运行一部分时间,再暂停一部分时间。结果通常是:
- CPU 使用率接近 limit;
- 请求延迟升高;
- 工作队列堆积;
container_cpu_cfs_throttled_*等指标升高(如果监控系统暴露这些 cAdvisor 指标)。
CPU limit 造成的 throttle 与节点是否空闲没有必然关系。即使节点还有大量空闲 CPU,容器自己的 cgroup quota 已经用完,它仍可能被限制。
4.2 CPU Request 不是 CPU Limit
下面的配置表示“调度按 250m 计算,运行时最多使用 2 CPU”:
resources:
requests:
cpu: "250m"
limits:
cpu: "2"
当节点有空闲 CPU 时,容器可能短时间使用接近 2 CPU;当节点竞争激烈时,它通常只能按 request 产生的权重参与竞争,且不能超过 2 CPU。
而下面的配置:
resources:
requests:
cpu: "2"
limits:
cpu: "2"
并不表示容器获得了物理 CPU 独占,只表示:
- 调度器按 2 CPU 计算;
- 容器最多使用 2 CPU;
- 在 CPU 竞争时通常拥有更高的相对权重;
- 是否有稳定延迟仍取决于节点超卖、内核调度、CPU manager、NUMA、频率变化和业务负载。
4.3 CPU Limit 为省略时的含义
如果容器没有 CPU limit,通常不会有该资源的 cgroup 上限,但仍受节点总容量、其他 cgroup 权重和系统负载影响。
这不等于“可以无限使用 CPU”。它最多只能使用节点上实际可获得的 CPU 时间。对于突发型批处理任务,这种配置可能允许更好地利用空闲资源;对于多租户服务,则可能增加邻居干扰和延迟抖动。
4.4 内存 Limit 是不可压缩资源的边界
内存不像 CPU 那样可以简单“暂停后继续”。当容器持续申请内存,达到 cgroup memory limit,Linux 内核通常会触发该 cgroup 范围内的内存回收或 OOM 处理。
例如:
resources:
requests:
memory: "128Mi"
limits:
memory: "256Mi"
这表示调度器至少按 128 MiB 计算,但运行时允许的内存边界约为 256 MiB。超过边界时,容器可能被内核杀死,而不是被自动降速。
内存 limit 不是严格的“应用变量大小上限”。应用还需要为:
- 堆外内存;
- 线程栈;
- mmap;
- 动态库;
- 文件页缓存;
- 临时文件;
- 运行时内部结构;
预留空间。只为 Java heap 或 Python 对象设置容量,往往会低估 Kubernetes 看到的总内存。
五、QoS Class:Kubernetes 如何给 Pod 分类
Kubernetes 会根据 Pod 中容器的 CPU、内存 request 和 limit 计算 QoS Class。它不是用户直接设置的字段,而是由 Pod 资源配置推导出的结果:
kubectl get pod <pod-name> \
-o jsonpath='{.status.qosClass}{"\n"}'
QoS 主要有三类。
5.1 Guaranteed
一个 Pod 要成为 Guaranteed,通常需要满足:
- Pod 中每个容器都设置了 CPU 和内存的
requests; - Pod 中每个容器都设置了 CPU 和内存的
limits; - 对每个容器而言,CPU request 等于 CPU limit;
- 对每个容器而言,内存 request 等于内存 limit。
示例:
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-demo
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
Guaranteed 并不表示:
- 永远不会被 OOM Kill;
- 永远不会被驱逐;
- 获得独占 CPU;
- 节点发生故障时自动存活;
- 业务不会因为自身超过 limit 而失败。
它主要改变调度计算、cgroup 配置以及资源压力下的相对处理方式。
5.2 BestEffort
如果 Pod 中所有容器都没有 CPU 和内存 request,也没有 CPU 和内存 limit,该 Pod 通常属于 BestEffort:
apiVersion: v1
kind: Pod
metadata:
name: besteffort-demo
spec:
containers:
- name: app
image: nginx:1.27
这种 Pod 不会因为没有 request 而“不可运行”。它可以被调度到节点上,但调度器不会为它计入 CPU 或内存预算。节点压力发生时,它通常处于最不利的位置。
5.3 Burstable
不满足 Guaranteed,但至少有一个容器设置了 CPU 或内存 request/limit 的 Pod,通常属于 Burstable。
apiVersion: v1
kind: Pod
metadata:
name: burstable-demo
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "1"
memory: "512Mi"
一个容器只设置 request、不设置 limit,也通常是 Burstable:
resources:
requests:
cpu: "250m"
memory: "128Mi"
另一个容易忽略的反例是多容器 Pod:
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"
- name: sidecar
image: busybox:1.36
虽然 app 的 request 和 limit 相等,但 sidecar 没有完整配置,因此整个 Pod 不是 Guaranteed,而是 Burstable。
5.4 QoS 的判断必须以实际对象为准
Admission webhook、LimitRange 或其他默认策略可能修改 Pod 的资源字段。不要仅根据提交前的 YAML 推断 QoS,应查看 API Server 中实际保存的对象:
kubectl get pod <pod-name> -o yaml
kubectl get pod <pod-name> \
-o jsonpath='{.status.qosClass}{"\n"}'
另外,QoS 分类主要基于 CPU 和内存资源;它不能概括所有资源治理策略。临时存储、GPU、ResourceQuota、PriorityClass 都是另外的维度。
六、CPU Throttle:被限速不等于被杀死
6.1 CPU Throttle 的故障路径
CPU throttle 的典型路径如下:
应用线程持续运行
│
▼
达到容器 cgroup 的 CPU quota
│
▼
Linux CFS 暂停该 cgroup 的可运行线程
│
▼
请求处理变慢、队列堆积、延迟升高
│
└── 周期结束后恢复,不一定重启容器
它的关键特征是:
- 容器仍然处于 Running;
- 进程通常没有退出;
kubectl describe pod不一定出现错误事件;- CPU 使用率可能“卡”在 limit 附近;
- 延迟和吞吐先恶化,随后可能引发健康检查失败。
因此,下面两个现象不能混为一谈:
| 现象 | 典型原因 |
|---|---|
| CPU 使用率达到 limit,throttled 时间增加 | CPU limit 过低或周期性突发 |
| CPU 使用率不高,但请求很慢 | I/O、锁竞争、GC、下游依赖或 CPU 配额周期行为 |
| 进程退出,状态为 OOMKilled | 内存压力或 cgroup 内存上限 |
| Pod 被删除并重新创建 | kubelet eviction、控制器重建或其他调度行为 |
6.2 一个可观察的 CPU 示例
下面的 Pod 会持续消耗 CPU,但 limit 只有 100m:
apiVersion: v1
kind: Pod
metadata:
name: cpu-throttle-demo
spec:
containers:
- name: cpu
image: busybox:1.36
command:
- sh
- -c
- |
while true; do
:;
done
resources:
requests:
cpu: "100m"
limits:
cpu: "100m"
创建并查看:
kubectl apply -f cpu-throttle-demo.yaml
kubectl top pod cpu-throttle-demo
kubectl describe pod cpu-throttle-demo
前置条件:
- 集群安装了 Metrics Server,
kubectl top才能工作; busybox镜像可以从节点拉取;- 节点允许创建该 Pod。
预期现象是 CPU 使用量接近 100m,但容器不会因为 CPU 用满而自动退出。若监控系统暴露 cAdvisor 指标,可以进一步查看常见指标:
container_cpu_cfs_throttled_seconds_total
container_cpu_cfs_periods_total
container_cpu_cfs_throttled_periods_total
这些指标名称和采集方式属于常见实现,不是所有 Kubernetes 发行版都保证以相同方式提供。应以实际监控系统和 cgroup 版本为准。
生产环境中不能只看到 CPU limit 被使用就断定“limit 太低”。如果业务本来就需要 100m CPU,使用率高是正常的;真正需要关注的是 throttling、延迟、吞吐和队列是否同时恶化。
七、OOM:内存超限、节点 OOM 与业务自杀
OOM 是 Out Of Memory,表示系统在满足内存分配请求时无法继续提供内存。Kubernetes 环境中至少要区分三种情况。
7.1 cgroup 内存 OOM
如果某容器超过自己的 memory limit,内核可能在该 cgroup 内选择进程杀死。常见结果是:
kubectl get pod <pod-name>
看到容器重启,进一步查看:
kubectl describe pod <pod-name>
可能出现:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
137 通常对应进程收到 SIGKILL,但诊断时应优先看 Reason: OOMKilled、事件以及容器运行时日志,不能仅凭退出码判断所有情况。
下面的示例用于实验,不适合生产:
apiVersion: v1
kind: Pod
metadata:
name: memory-oom-demo
spec:
restartPolicy: Always
containers:
- name: allocator
image: python:3.12-alpine
command:
- python
- -c
- |
chunks = []
while True:
chunks.append(bytearray(10 * 1024 * 1024))
resources:
requests:
memory: "32Mi"
limits:
memory: "64Mi"
应用会不断申请内存,接近或超过 64 MiB 后可能被杀死。由于 restartPolicy: Always,Pod 中的容器可能被重新启动,表现为 RESTARTS 持续增加:
kubectl get pod memory-oom-demo -w
kubectl describe pod memory-oom-demo
这个实验有几个边界:
- Python 解释器自身已经占用一部分内存;
- cgroup 统计不一定与 Python 分配的字节数完全相等;
- 镜像、内核和 cgroup 版本会影响触发时机;
- OOM Kill 的具体受害进程由 Linux 内核选择。
7.2 节点级 OOM
如果节点整体内存耗尽,可能发生 node-level OOM。此时内核可能杀死:
- 普通容器进程;
- kubelet;
- 容器运行时;
- 节点上的系统进程。
这比单个容器达到自己的 limit 更危险。因为 kubelet 可能来不及先执行 eviction,内核就已经开始杀进程。
因此:
Memory limit 主要提供 cgroup 层面的边界;它不能保证节点不会发生整体内存不足。
诊断节点级 OOM 时,应查看:
kubectl describe node <node-name>
kubectl get events --sort-by=.lastTimestamp
并在节点上查看系统日志,例如:
journalctl -k
journalctl -u kubelet
不同操作系统、云镜像和日志系统的命令与日志位置可能不同。
7.3 应用主动退出不一定是 OOM
应用可能因为以下原因主动退出:
- 自己的内存保护逻辑;
- JVM、Go runtime 或数据库的内部限制;
- 探针失败后被重启;
- 配置错误;
- 收到 SIGTERM 或 SIGKILL;
- 进程内部 panic。
这类情况不能直接归因于 Kubernetes OOM。应同时检查:
kubectl logs <pod-name> --previous
kubectl describe pod <pod-name>
kubectl get pod <pod-name> -o json
--previous 对于容器已经重启的场景尤其重要,因为当前容器日志可能已经不是发生故障的那一次。
八、QoS 与 OOM 优先级:重要但不是绝对规则
在节点内存压力下,Linux 内核需要选择进程;Kubernetes kubelet 也可能主动驱逐 Pod。QoS 会影响 Kubernetes 对 Pod 的相对保护程度,kubelet 还会通过 oom_score_adj 等机制影响内核的选择倾向。
通常可以理解为:
- BestEffort 更容易成为受害者;
- Burstable 中超过自身 request 的 Pod 更容易受到影响;
- Guaranteed 通常受到更高保护;
- 但节点级 OOM、系统进程、优先级和实际内存使用仍可能改变结果。
不能把它简化为“BestEffort 一定先死,Guaranteed 永远不会死”。内核 OOM 选择的是进程,不是 Kubernetes 抽象层中的 Pod;kubelet 也可能因节点压力、系统保留不足或特定驱逐条件采取行动。
一个关键反例是:
Burstable Pod:
memory request = 100Mi
memory limit = 8Gi
actual usage = 2Gi
它远远超过自己的 request,因此在内存压力下不一定比另一个使用 200Mi、request 为 1Gi 的 Pod 更安全。仅看 QoS Class 不够,还要看“实际使用是否超过 request”。
九、Eviction:kubelet 主动驱逐 Pod
9.1 Eviction 与 OOM 的区别
Eviction 是 kubelet 根据节点资源压力主动采取的 Pod 淘汰行为;OOM Kill 是 Linux 内核在内存分配无法满足时杀死进程。
| 对比项 | Eviction | OOM Kill |
|---|---|---|
| 主要执行者 | kubelet | Linux 内核 |
| 触发依据 | 节点压力信号、驱逐阈值 | cgroup 或节点内存耗尽 |
| 对象 | Kubernetes Pod | 进程 |
| 是否有 Pod 终止流程 | 通常有,可执行优雅终止 | 可能直接 SIGKILL |
| 常见表现 | Pod 被驱逐、状态变化、事件 | 容器 OOMKilled、退出码常见为 137 |
| 是否一定等 kubelet 处理 | 是 kubelet 路径 | 不一定,内核可先处理 |
如果节点内存已经耗尽,内核 OOM 可能先于 kubelet eviction 发生。因此设置 eviction threshold 不能保证绝对避免 OOM。
9.2 kubelet 监控的压力信号
kubelet 会监控节点压力,例如:
memory.available;nodefs.available;imagefs.available;nodefs.inodesFree;pid.available。
具体启用的信号、阈值以及文件系统布局与 kubelet 配置和发行版有关。
查看节点状态:
kubectl describe node <node-name>
可能看到:
Conditions:
MemoryPressure True
DiskPressure False
PIDPressure False
节点压力还可能反映在事件中:
kubectl get events -A --sort-by=.lastTimestamp
9.3 内存驱逐的判断逻辑
对内存压力,kubelet 不是单纯按照 QoS 名称排序。实际排序会综合考虑:
- Pod 是否超过自己的 memory request;
- Pod 的优先级;
- Pod 的实际使用量与 request 的关系;
- QoS 等相关信息。
因此,一个 Burstable Pod 如果实际使用没有超过 request,可能比另一个严重超过 request 的 Burstable Pod 更不容易被驱逐。PriorityClass 也会参与排序,不能只看 QoS。
Pod 被驱逐后,控制器的行为取决于工作负载类型:
- Deployment 通常创建替代 Pod;
- StatefulSet 通常按序维护副本;
- Job 可能重试;
- 独立 Pod 不一定有人重新创建。
“Pod 被重新调度了”通常不是 eviction 本身完成的,而是上层控制器观察到副本数不足后创建了新的 Pod。
9.4 磁盘压力与 ephemeral-storage
节点磁盘压力可能来自:
- 容器可写层;
emptyDir;- 容器日志;
- 镜像层和镜像缓存;
- inode 耗尽。
可以为容器设置临时存储 request 和 limit:
resources:
requests:
ephemeral-storage: "1Gi"
limits:
ephemeral-storage: "2Gi"
但这不等同于为 Pod 分配了一块独占磁盘。kubelet 通常依据节点文件系统信号和本地临时存储使用情况进行管理。不同节点可能使用独立的 imagefs、containerfs 或统一文件系统,统计和驱逐行为会有差异。
内存、CPU 和临时存储的故障表现也不同:
CPU 超限 → throttle,进程通常继续运行
内存超限 → OOM Kill,进程可能退出
节点内存压力 → kubelet eviction 或 node-level OOM
磁盘压力 → DiskPressure,可能驱逐使用临时存储较多的 Pod
十、一个完整资源配置示例
下面的 Deployment 展示了应用容器、sidecar、探针和资源配置之间的关系:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
ephemeral-storage: "1Gi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 2
periodSeconds: 5
- name: log-sidecar
image: busybox:1.36
command: ["sh", "-c", "while true; do sleep 3600; done"]
resources:
requests:
cpu: "50m"
memory: "32Mi"
limits:
cpu: "100m"
memory: "64Mi"
ephemeral-storage: "128Mi"
对每个 Pod:
CPU request = 250m + 50m = 300m
内存 request = 256Mi + 32Mi = 288Mi
CPU limit = 1 + 100m = 1.1 CPU
内存 limit = 512Mi + 64Mi = 576Mi
该 Pod 不是 Guaranteed,因为应用容器的 request 与 limit 不相等,且 sidecar 也不满足等值条件,所以它通常是 Burstable。
三个副本在调度时大致贡献:
CPU request = 3 × 300m = 900m
内存 request = 3 × 288Mi = 864Mi
调度器不会把三个 Pod 的 CPU limit 3 × 1.1 = 3.3 CPU 当作必须预留的容量,但节点上的运行时仍可能允许它们竞争最多约 3.3 CPU。如果节点可运行容量小于这种潜在峰值,就存在 CPU 超卖和 throttle 风险;如果所有容器都同时使用高内存,则存在内存压力甚至 OOM/eviction 风险。
十一、如何区分 Pending、Throttle、OOM 和 Eviction
11.1 Pod 长期 Pending:先看 Request 和调度事件
kubectl describe pod <pod-name>
重点看:
Events:
Warning FailedScheduling
常见原因包括:
Insufficient cpu
Insufficient memory
node(s) had untolerated taint
node(s) didn't match Pod's node affinity
如果是资源不足,应比较:
kubectl get nodes
kubectl describe node <node-name>
kubectl get pod -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,NODE:.spec.nodeName,CPU_REQ:.spec.containers[*].resources.requests.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory'
最后一个命令对多容器结果不一定直观,生产诊断时应通过脚本逐容器求和,并结合 init container 的峰值计算。
11.2 Pod Running 但延迟高:检查 CPU throttle
kubectl top pod <pod-name>
kubectl describe pod <pod-name>
如果使用 Prometheus,可以观察:
- CPU 使用量;
- CPU throttled seconds;
- throttled periods;
- 请求延迟;
- 运行队列和应用线程池排队。
只看 kubectl top 的 CPU 百分比不够。容器使用 100m CPU 可能是正常完成了工作,也可能是被 100m limit 持续压住;需要把使用量与 limit、throttle 和业务指标放在一起判断。
11.3 容器频繁重启:检查 OOMKilled 和上一次日志
kubectl get pod <pod-name>
kubectl describe pod <pod-name>
kubectl logs <pod-name> -c <container-name> --previous
重点关注:
Last State:
Reason: OOMKilled
同时查看节点:
kubectl describe node <node-name>
如果多个不相关 Pod 同时出现异常,或者 kubelet、容器运行时也不稳定,应怀疑节点级内存压力,而不只是某个 Pod 的 limit 太小。
11.4 Pod 被驱逐:看 Pod 原因、节点条件和事件
kubectl get pod <pod-name> -o wide
kubectl describe pod <pod-name>
kubectl describe node <node-name>
kubectl get events -A --sort-by=.lastTimestamp
需要区分:
Evicted;OOMKilled;NodeLost;- 探针失败导致重启;
- 控制器滚动更新;
- 用户手动删除。
这些状态可能都表现为“Pod 不见了”或“Pod 重启了”,但恢复方法完全不同。
十二、Request、Limit 与 QoS 的常见误解
误解一:Request 是应用能稳定拿到的资源
CPU request 主要参与调度和相对竞争权重;内存 request 主要参与调度和驱逐判断。它们不是物理资源预留合同。
如果一个容器 request 为 1Gi、limit 为 4Gi,它可以被调度到一个按 1Gi 预算仍然有空间的节点上,但四个 Pod 同时使用 4Gi 时,节点可能无法承受。
误解二:Limit 越大,Pod 越安全
更大的内存 limit 允许单个 Pod 使用更多内存,但也增加节点超卖和节点级 OOM 风险。更大的 CPU limit 可以容纳突发,但如果节点 CPU 竞争激烈,也不能保证低延迟。
Limit 是故障边界,不是容量规划的替代品。
误解三:Guaranteed 就不会被驱逐
Guaranteed 通常拥有更高的保护级别,但只要节点出现不可避免的资源压力,Guaranteed Pod 也可能被驱逐。例如:
- 节点系统进程消耗过多内存;
- 节点磁盘压力;
- Pod 本身超过 limit 后被 OOM Kill;
- 节点故障或被维护;
- 资源压力超过 kubelet 可以安全处理的范围。
误解四:CPU limit 超过后会 OOM
CPU limit 超过后通常是 throttle,不是 OOM。CPU 和内存是不同的资源类型,分别由不同的 Linux cgroup 机制处理。
误解五:删除 Pod 就能解决资源问题
删除 Pod 只能暂时释放其 cgroup 资源;如果 Deployment、StatefulSet 或其他控制器仍要求该副本存在,它会立即创建替代 Pod。若根因是 request 不合理、内存泄漏、节点容量不足或滚动更新策略,单纯删除可能造成反复重建和更大流量波动。
十三、与 LimitRange 和 ResourceQuota 的关系
资源字段还可能被命名空间级策略改变或拒绝。
13.1 LimitRange:默认值和单对象约束
LimitRange 可以为容器设置默认 request/limit,也可以限制单个容器或 Pod 的资源范围。例如:
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: team-a
spec:
limits:
- type: Container
defaultRequest:
cpu: "100m"
memory: "128Mi"
default:
cpu: "500m"
memory: "512Mi"
它可能导致一个原本没有资源字段的容器在 admission 后拥有默认值,从而改变:
- Pod 的 QoS Class;
- 调度器使用的 request;
- 是否满足 ResourceQuota;
- 节点的可调度容量。
因此,排查 QoS 或 Pending 问题时,应查看实际保存对象,而不是只看应用仓库中的原始模板。
13.2 ResourceQuota:命名空间预算
ResourceQuota 控制命名空间总体资源预算,例如:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-a
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
它的语义是:
命名空间内所有对象的 request/limit 总量不能超过配额
ResourceQuota 主要在 admission 阶段拒绝新对象或更新,不是节点运行时的实时限速器。Pod 已经运行后,quota 不会因为瞬时 CPU 使用过高而主动 throttle,也不会直接执行 eviction。
还必须注意:
requests.cpu和limits.cpu是两个不同预算;- Pod 数量配额与 CPU、内存配额互相独立;
- quota 是否要求所有 Pod 显式设置资源,取决于具体 quota 与 LimitRange 配置;
- 多租户隔离不能只依靠 QoS,还需要配额、节点池、优先级、污点容忍和网络策略等机制。
十四、生产容量规划中的正确连接方式
资源配置应至少形成以下闭环:
业务并发与延迟目标
│
▼
压测得到的实际 CPU / 内存分布
│
├── request:用于调度和容量预算
└── limit:用于限制异常峰值和隔离故障
│
▼
节点 allocatable、超卖比例、碎片和故障余量
│
▼
扩容、驱逐风险和故障恢复能力
14.1 Request 应从工作负载数据推导
设某服务在目标并发下的 CPU 使用量观测为 ,内存峰值为 。可以把 request 设计为:
其中:
- 是某个观测分位数,例如 P95 或 P99;
- 是为 GC、流量变化、采集误差和发布过程保留的余量。
这不是 Kubernetes 规定的固定公式,而是容量规划方法。分位数越高,调度预算越大、碎片越多,但被驱逐和性能抖动的概率可能降低。
14.2 内存通常比 CPU 更适合保守设置
CPU 可以在竞争时通过 throttle 退化,内存超过边界则可能直接杀死进程。因此:
- 内存 request 过低会导致调度器过度打包;
- 内存 limit 过低会造成 OOM 重启;
- 内存 limit 过高会增加节点级 OOM 风险;
- 内存泄漏不能通过无限增大 limit 解决,只能延后故障。
14.3 过大的 Request 会产生碎片
假设节点 allocatable 为 8 CPU,有三个节点,每个节点剩余:
节点 A:600m
节点 B:600m
节点 C:600m
集群总体还有 1.8 CPU 空闲,但一个新 Pod request 为 1 CPU 时仍可能无法调度,因为没有单个节点拥有至少 1 CPU 的可用 request 空间。这就是资源碎片。
所以容量规划不仅要看集群总量,还要看:
- Pod request 分布;
- 节点规格;
- 亲和性和反亲和性;
- 拓扑分布约束;
- DaemonSet 占用;
- 滚动更新期间的额外副本;
- 单节点故障后的剩余容量。
14.4 必须预留故障余量
如果所有节点都按 allocatable 的 100% 调度,任意一个节点故障都可能使剩余节点无法容纳被重新调度的副本。更实际的规划会预留:
- 节点故障余量;
- 发布期间的 surge 副本;
- 突发流量;
- 系统组件增长;
- eviction 前的缓冲;
- 镜像拉取和日志增长造成的临时磁盘空间。
这部分属于容量工程和运行策略,不是 QoS Class 自动完成的功能。
十五、处理问题时应先判断故障层次
可以按以下顺序诊断:
Pod 是否 Pending?
└─ 是:看 request、allocatable、亲和性、taint 和 FailedScheduling
Pod 是否 Running 但慢?
└─ 看 CPU limit、throttle、GC、锁、I/O 和下游延迟
容器是否频繁重启?
└─ 看 Last State、OOMKilled、退出码、previous 日志和探针事件
Pod 是否被 Evicted?
└─ 看节点 Pressure、驱逐事件、内存/磁盘/ inode 使用
多个 Pod 与节点组件同时异常?
└─ 优先排查节点级资源耗尽、内核 OOM、运行时和 kubelet
最重要的诊断原则是:
不要用一个现象解释所有资源故障。Pending 是调度问题,CPU throttle 是运行时 CPU 控制问题,OOM 是内存分配失败路径,Eviction 是 kubelet 的节点治理路径;它们可能相互诱发,但不是同一个机制。
当这些机制被分开理解后,requests、limits 和 QoS 才能被正确用于容量规划、隔离故障和解释生产环境中的资源行为。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Probe:Startup、Readiness、Liveness、gRPC 和误配置风险
- 下一篇:ConfigMap 与 Secret:注入、更新、不可变、轮换和安全边界
- 延伸:ResourceQuota 与 LimitRange:租户预算、默认值、对象数量和治理
- 延伸:Kubernetes 容量规划:Request、利用率、碎片、故障余量和压测
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论