Kubernetes CPU Requests and Limits 深度解析——第二章

一、概述

1. 背景介绍

A. 系列文章定位

本文是 Kubernetes 资源管理深度解析系列文章的第二章,专注于 CPU 资源的 requests 和 limits 在 Linux 操作系统层面的实际工作机制。

B. 前置知识

第一章已介绍 Pod 如何基于资源请求大小和节点容量进行调度匹配,以及节点的"满载程度"完全基于 requests,与实际资源使用或 limits 无关。

2. 核心问题

Kubernetes 中的 CPU requests 和 limits 如何转换为 Linux OS 层面的具体配置?这意味着什么?如何基于此预测、保证或排除 CPU 资源相关的行为?

二、从抽象到具体的转换机制

1. 资源抽象的实现层次

A. 架构概览

Kubernetes 不是操作系统,它只是编排器。实际强制执行资源设置的职责由 Linux 承担。但 Linux 不理解 requests 和 limits 的抽象概念,需要进行转换。

graph TD
    A[Pod Spec CPU Requests/Limits] --> B[Kubelet]
    B --> C[容器运行时]
    C --> D[Linux Cgroups]
    D --> E[进程实际执行]

Kubernetes CPU 资源转换流程

B. 实现组件

大多数 Kubernetes 资源抽象由 kubelet 和容器运行时使用 Linux 控制组(cgroups)及其设置实现。

2. Cgroup 版本说明

当前有两种 cgroup API 版本:

  • cgroup v1:旧版本
  • cgroup v2:新版本(本文以此为默认)

两者在功能上等效,v2 使用更统一的设置名称。

三、CPU 资源的复杂性

1. Linux CFS 调度器基础

A. 核心概念

控制、分配和保留进程 CPU 时间的底层内核设施并非像"请给 nginx 250 毫核,谢谢"那样简单。

相关控制全部关联到 Linux 的完全公平调度器(CFS)。

B. CFS 特性

CFS 是一个比例进程调度器。假设不干预,CFS 会给每个可运行进程相等量的 CPU 时间。

2. Kubernetes 使用的 CPU Cgroup 控制

A. Requests —— cpu.weight

在比例天平上放一个"手指":

  • 如果一个可运行进程的权重是另一个的两倍
  • CFS 会给双倍权重的进程分配两倍的 CPU 时间

B. Limits —— cpu.max

不会改变进程可运行时的比例优先级,但可能导致进程周期性进入"暂停"状态,期间完全不获得 CPU 时间。

四、CPU Requests——比例天平上的权重

1. 转换机制

A. 目标

Kubernetes 的目标是让每个容器的进程优先级对应于用户在容器 CPU resource request 中指定的比例。

B. 简化示例

在单核节点(1000m 容量)上:

  • 200m request:优先级 1/5 的 CPU 周期
  • 250m request:优先级 1/4 的 CPU 周期
  • 500m request:优先级 1/2 的 CPU 周期

C. 容量比例权重转换

Kubernetes 将毫核值转换为容量比例权重值。

graph LR
    A[CPU Request 250m] --> B{节点容量}
    B -->|1000m 单核| C[权重 1/4]
    B -->|2000m 双核| D[权重 1/8]
    C --> E[cpu.weight 设置]
    D --> E

CPU Request 到 cpu.weight 的转换

2. 比例权重的计算原理

A. 基础概念

cpu.weight 值本质上是相互比例的,没有神奇值能保证静态量的 CPU 时间。任何单个进程的比例优先级取决于其相对于其他运行进程的权重。

B. 通用分母法

要获得整数权重,可以计算通用分母并用于获得比例整数 cpu.weight 值。

C. Kubernetes 的实现保证

由于 Pod 调度和节点"满载"的实现方式,Kubernetes 确保为容器计算的小数值永远不会加总超过 1,因此 cgroup 的 CPU 优先级永远不会低于其 request-to-capacity 比例。

五、Burstable Pods 的特殊行为

1. 突发容量的来源

节点上经常存在瞬间空闲 CPU 容量,未被特定容器保证:

  • 节点尚未"满载",部分容量未保证给任何特定容器
  • 容器请求了 CPU 资源但当前未使用

2. QoS 类的 Cgroup 层次结构

A. 层级结构

graph TD
    A[根 Cgroup] --> B[Guaranteed QoS]
    A --> C[Burstable QoS - burstable.slice]
    A --> D[BestEffort QoS - besteffort.slice]
    C --> E[Pod 1]
    C --> F[Pod 2]
    D --> G[Pod 3]
    B --> H[Pod 4]

Kubernetes QoS Cgroup 层次结构

B. 关键原则

  • Cgroups 配置在层次结构中,像洋葱一样有层级
  • 每个层级根据 cpu.weight 在同级对等体之间按比例分配 CPU 时间
  • 一个层级分配给 cgroup 的 CPU 时间可以在下一层级进一步细分

3. 突发容量的分配特性

A. 分配优先级

在 Kubernetes 设置的组上下文中:

  • Guaranteed QoS pods 相互竞争,以及与 Burstable 超级父级和 BestEffort 微父级竞争
  • 如果即使一个 Burstable QoS 容器想要 CPU 时间,它将比 Guaranteed 或 BestEffort QoS pods 获得多得多的可用"额外"周期
  • 即使节点上每个 BestEffort 容器都想要 CPU 时间,它们与 Guaranteed 和 Burstable QoS 容器竞争的 cpu.weight 总和永远不会超过 BestEffort 微父级分配的权重 1

B. 行为影响

graph LR
    A[空闲 CPU 容量] --> B{分配竞争}
    B --> C[Burstable QoS - 高优先级]
    B --> D[Guaranteed QoS - 中优先级]
    B --> E[BestEffort QoS - 低优先级]
    C --> F[获得大部分突发容量]
    D --> G[获得少量突发容量]
    E --> H[几乎无突发容量]

QoS 类突发容量分配优先级

C. 潜在风险

对于没有任何最低性能要求或敏感性的最佳工作负载,这种突发优先级行为可能是高度理想的。但是,当工作负载有任何最低要求时,这种不平等的 CPU 突发容量分配可能会出乎意料或产生问题。

4. 避免 CPU 饥饿的策略

A. 正确设置 Requests

确保最低 CPU 分配的最可靠方法是让工作负载请求它。为每个需要它们的容器设置 CPU requests,并将其设置为正确值。

B. 使用 Limits(不推荐)

另一种可能更复杂的缓解策略是尝试使用 CPU limits 来压制最可能的坏角色或贪婪 CPU 消费者的 CPU 使用。

C. 行业共识

limits 方法起初可能很诱人,但行业共识已围绕通用工作负载模板完全不使用 CPU limits,而是依赖 request 方法。

六、CPU Limits——进程暂停机制

1. CFS 带宽控制

A. 核心概念

为容器定义 Kubernetes CPU limits 时,容器运行时将其转换为容器 cgroup 上的 cpu.max 值,容器进程将受制于 CFS 的带宽控制机制。

B. 关键概念

  • 带宽控制基于时间周期
  • 如果分配了 CPU 配额,则在每个周期授予的运行时间(微秒)方面设置
  • 一旦 CFS 计费系统确定进程已消耗周期的所有配额,进程就会受到限制
  • 受到限制时,进程实际上被暂停
  • 每个周期开始时,配额刷新,受限进程再次可运行

2. cpu.max 配置

cpu.max 通过设置 MAX PERIOD 字符串配置,其中 MAX 是组在每个 PERIOD(微秒)中可以运行的微秒数。

3. Limits 的性能影响

A. 场景示例

假设一个应用需要 120ms CPU 时间处理请求:

  • 配额周期:100ms(Kubernetes 默认)
  • CPU limit:400m

计算:

  • 400m = 4/10 = 2/5 = 0.4 的每个周期
  • 或每个 100ms 周期中的 40ms

B. 延迟影响

sequenceDiagram
    participant Req as 请求到达
    participant CPU as CPU 执行
    participant Quota as 配额检查

    Req->>CPU: 开始处理 (需要 120ms)
    CPU->>Quota: 使用 40ms 配额
    Quota->>CPU: 配额耗尽,暂停
    Note over CPU: 暂停 60ms
    Quota->>CPU: 新周期,配额刷新
    CPU->>Quota: 使用 40ms 配额
    Quota->>CPU: 配额耗尽,暂停
    Note over CPU: 暂停 60ms
    Quota->>CPU: 新周期,配额刷新
    CPU->>Req: 完成 (总耗时 220ms)

CPU Limits 导致的请求延迟

虽然这可以说是 limit 旨在做的事情,但当单独查看应用时,存在潜在的错失机会。如果在此请求处理期间没有其他进程竞争 CPU 时间,那么限制引入了 115ms 的可避免延迟。在此期间,CPU 没有被用于任何其他事情。

C. 设计反思

限制总是给应用引入约束,可能影响延迟。限制是面向约束的,那么为什么通常要引入这种隔离约束?

答案:通常不会。

4. Limits vs Requests 的根本区别

A. 常见误解

人们通常直觉地认为 limits 与公平性相关,确保每个工作负载获得其分配的时间。

B. 实际机制

如我们所学,这实际上不是真的:

  • Limits 本身不是运行时的保证者
  • 运行时分配保证来自 CPU requests,而不是 limits
  • Limits 唯一做的是防止个别应用使用节点上额外的 CPU 时间(如果碰巧有可用的话)
graph TD
    A[资源保证机制] --> B[CPU Requests]
    A --> C[CPU Limits]

    B --> D[设置 cpu.weight]
    B --> E[保证最低 CPU 时间]
    B --> F[比例分配]

    C --> G[设置 cpu.max]
    C --> H[限制最大 CPU 时间]
    C --> I[可能导致限流]

    D --> J[✅ 保证机制]
    E --> J
    F --> J

    G --> K[❌ 约束机制]
    H --> K
    I --> K

Requests vs Limits 的根本区别

七、最佳实践建议

1. 优先使用 Requests

A. 原因

  • Requests 提供真正的资源保证
  • 基于比例分配,充分利用空闲容量
  • 不引入额外的延迟

B. 设置原则

为每个需要保证的容器设置 CPU requests,并设置为正确值。

2. 慎用 Limits

A. 潜在问题

  • 引入不必要的延迟
  • 限制应用利用空闲资源的能力
  • 在单节点场景下可能完全浪费 CPU

B. 适用场景

仅在需要防止特定工作负载占用过多资源时谨慎使用。

3. 监控与调优

A. 监控指标

  • CPU 使用率
  • 节点容量利用率
  • 请求延迟(特别是 P99)

B. 调优建议

  • 基于实际使用量设置 requests
  • 定期评估和调整资源配置
  • 考虑使用垂直 Pod 自动扩缩容

八、总结

1. 核心要点

A. Requests 机制

  • 通过 cpu.weight 实现比例分配
  • 保证最低 CPU 时间
  • 允许突发使用空闲容量

B. Limits 机制

  • 通过 cpu.max 实现带宽控制
  • 限制最大 CPU 使用
  • 可能引入额外延迟

C. QoS 层次结构

  • Burstable QoS 在突发容量分配中有优势
  • BestEffort QoS 几乎无法获得突发容量
  • 合理设置 requests 对避免 CPU 饥饿至关重要

2. 实践指导

  • 优先使用 CPU requests 保证资源
  • 慎用 CPU limits,避免不必要的性能损失
  • 理解 QoS 层级对突发容量的影响
  • 基于实际需求合理设置资源配置

九、后续章节预览

下一章将深入探讨内存资源:

  • 内存 requests 和 limits 如何转换为 Linux 进程设置
  • 比例建模是否同样适用于内存
  • 节点上发生内存竞争时会发生什么
  • Requests 和 limits 如何影响结果

参考资料

  1. How K8s CPU Requests and Limits Actually Work — Chapter 2
  2. Kubernetes Overview, News and Trends | The New Stack
最后修改:2026 年 01 月 18 日
如果觉得我的文章对你有用,请随意赞赏