4、CFS调度器原理:Linux完全公平调度器(CFS)在容器中的工作方式,CFS配额与周期
4.1 CFS调度器概述
Linux 完全公平调度器(Completely Fair Scheduler, CFS)自内核 2.6.23 起成为默认调度器,也是 Kubernetes 容器运行时(如 containerd、CRI-O)底层依赖的 CPU 分配引擎。CFS 的核心思想是:为每个可运行线程分配一个“虚拟运行时间”(vruntime),调度器总是选择 vruntime 最小的线程运行,从而在宏观上实现所有线程对 CPU 的公平共享。
在容器场景下,CFS 通过 CPU 组调度(CFS Group Scheduling) 和 带宽控制(CFS Bandwidth Control) 两个机制,将容器的 CPU 请求(requests)和限制(limits)转化为内核可执行的调度参数。
- vruntime:线程实际运行时间的加权值,权重由线程优先级(nice 值)决定。
- 调度周期(period):CFS 重新分配 CPU 时间的时间窗口,默认 100ms。
- 配额(quota):在一个调度周期内,容器组(cgroup)允许运行的最大 CPU 时间。
- CFS 组调度:将容器视为一个调度实体,在组内公平分配 CPU 时间。
4.2 CFS 在容器中的工作方式
Kubernetes 通过 cgroup v1 或 v2 的 cpu 子系统控制容器的 CPU 使用。当 Pod 指定了 resources.requests.cpu 和 resources.limits.cpu 后,kubelet 会将其转换为以下 CFS 参数写入 cgroup:
| Kubernetes 资源字段 | CFS 内核参数 | 说明 |
|---|---|---|
requests.cpu |
cpu.shares (cgroup v1)cpu.weight (cgroup v2) |
相对权重,决定 CPU 争用时的时间分配比例。例如 2 核 = 2048 shares。 |
limits.cpu |
cpu.cfs_period_uscpu.cfs_quota_us |
硬限制:在 cfs_period_us 微秒内,容器最多运行 cfs_quota_us 微秒。 |
当容器内的线程被调度时,CFS 会执行以下步骤:
- 入队:线程进入 CFS 红黑树,vruntime 初始化为当前 cgroup 的 vruntime。
- 选择:调度器从红黑树中取出 vruntime 最小的线程(即“最饥饿”的线程)。
- 运行:线程在 CPU 上执行,同时其 vruntime 按权重比例递增。
- 检查配额:在每个调度周期结束时,CFS 带宽控制器检查该 cgroup 的累计运行时间是否超过 quota。若超过,则将该 cgroup 内的所有线程节流(throttle),直到下一个周期开始。
- 重新入队:线程被抢占或时间片用完后,重新插入红黑树,等待下一次调度。
4.3 CFS 配额与周期:核心参数详解
CFS 带宽控制通过两个关键参数实现 CPU 硬限制:
cpu.cfs_period_us:调度周期,单位微秒(μs)。默认值 100000(即 100ms)。取值范围通常为 1ms 到 1s。cpu.cfs_quota_us:在一个周期内,该 cgroup 允许运行的总 CPU 时间,单位微秒。若设置为 -1,表示不限制。
容器的 CPU 限制核数计算公式为:
CPU 限制(核数) = cpu.cfs_quota_us / cpu.cfs_period_us
例如:quota = 200000, period = 100000 → 2 核
例如:quota = 50000, period = 100000 → 0.5 核
下表展示了常见 Kubernetes CPU 限制与 CFS 参数的对应关系:
| K8s CPU limit | cfs_period_us | cfs_quota_us | 等效物理核数 | 节流风险 |
|---|---|---|---|---|
| 100m | 100000 | 10000 | 0.1 | 高(周期内仅 10ms 可用) |
| 500m | 100000 | 50000 | 0.5 | 中 |
| 1 | 100000 | 100000 | 1.0 | 低(满周期可用) |
| 2 | 100000 | 200000 | 2.0 | 低(需双核并行) |
| 4 | 100000 | 400000 | 4.0 | 低(需四核并行) |
4.4 CFS 组调度与容器隔离
在 Kubernetes 节点上,每个 Pod 对应一个 cgroup 目录树。CFS 组调度确保:
- Pod 级隔离:不同 Pod 的容器属于不同的 cgroup 组,CFS 在组间按
cpu.shares分配 CPU。 - 容器级隔离:Pod 内的多个容器共享 Pod 的 cgroup 配额,但各自有独立的线程调度实体。
- QoS 优先级:Guaranteed Pod 的 shares 值较高,Burstable 次之,BestEffort 最低。CFS 在 CPU 争用时优先满足高 shares 的组。
limits.cpu = 2 并不意味着容器独占 2 个物理核。CFS 仍然允许其他容器在空闲时使用这些核,只是当容器需要时,CFS 保证其在一个周期内最多获得 2 核的 CPU 时间。真正的核绑定需使用 cpu pinning(如 CPU Manager Policy)。
4.5 CFS 调度流程与配额检查机制
下图展示了 CFS 在容器环境下的完整调度流程,从线程就绪到配额节流:
4.6 配额与周期的性能影响
CFS 的 period 和 quota 设置直接影响容器的 CPU 利用率和响应延迟:
| 参数调整 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 减小 period(如 10ms) | 降低节流延迟,提高响应速度 | 增加调度开销,CPU 利用率下降 | 延迟敏感型应用(如 Web 服务器、API 网关) |
| 增大 period(如 500ms) | 减少调度开销,提高吞吐 | 节流延迟增大,可能导致突发延迟尖刺 | 批处理任务、离线计算 |
| quota 接近 period(如 1 核 = 100ms/100ms) | 容器可连续运行,几乎无节流 | CPU 争用时公平性下降 | Guaranteed QoS Pod |
| quota 远小于 period(如 0.1 核 = 10ms/100ms) | 严格限制 CPU 使用 | 频繁节流,上下文切换开销大 | 限制后台任务、sidecar 容器 |
4.7 查看 CFS 配额与节流状态
在生产环境中,可以通过以下命令检查容器的 CFS 参数和节流统计:
# 查看容器 cgroup 的 CFS 参数(cgroup v1)
cat /sys/fs/cgroup/cpu/kubepods/burstable/pod<pod-uid>/cpu.cfs_period_us
cat /sys/fs/cgroup/cpu/kubepods/burstable/pod<pod-uid>/cpu.cfs_quota_us
# 查看节流统计
cat /sys/fs/cgroup/cpu/kubepods/burstable/pod<pod-uid>/cpu.stat
# 输出示例:
# nr_periods 1000 # 总周期数
# nr_throttled 50 # 被节流的周期数
# throttled_time 1234567 # 被节流的总时间(微秒)
nr_throttled / nr_periods > 5%,说明容器频繁被节流,应考虑增大 CPU limit 或优化应用并发模型。若 throttled_time 持续增长,表明 CPU 限制过紧。
4.8 CFS 与 CPU Manager 的协同
当节点启用 CPU Manager(--cpu-manager-policy=static)时,Guaranteed Pod 的容器可以独占物理 CPU 核心。此时 CFS 的行为发生变化:
- 独占核心:容器内的线程绑定到固定的物理核,CFS 不再在这些核上进行组间调度。
- CFS 退化为组内调度:独占核上的 CFS 仅负责容器内多个线程之间的公平调度,不再受 quota/period 限制(quota 被设置为 -1)。
- 性能优势:消除 CFS 节流抖动,适合对 CPU 延迟极度敏感的应用(如 DPDK、实时数据库)。
cpu.shares 仍然影响非独占核心的分配。混合使用独占核和共享核时,需仔细规划 Pod 的 QoS 等级和 CPU 请求。
4.9 本章小结
CFS 调度器是 Kubernetes CPU 资源管理的底层基石。理解其工作原理——特别是 quota 与 period 的配合机制、组调度隔离、以及节流产生的条件——是进行 CPU 容量配额调优的前提。在实际调优中,应结合应用的延迟敏感度和 CPU 利用率特征,合理选择 period 大小和 quota 比例,必要时借助 CPU Manager 实现核级隔离。