1. CPU 管理基础:CPU 架构基础与调度原理
在容器运行时环境中,CPU 是竞争最激烈、隔离最敏感的硬件资源。要理解容器进程如何被分配、限制和绑定到特定 CPU,首先需要掌握现代 CPU 的物理拓扑、内存访问架构以及操作系统调度器的基本工作原理。本节将建立这些核心概念,为后续的绑核策略和性能调优打下坚实基础。
1.1 物理核、逻辑核与超线程
现代 CPU 通过超线程技术将一个物理核模拟为多个逻辑核,以提高指令级并行度。理解这三者的关系是 CPU 管理的第一步。
| 概念 | 定义 | 关键特征 | 容器视角 |
|---|---|---|---|
| 物理核 | 芯片上独立的计算单元,拥有完整的执行单元、L1/L2 缓存 | 真正的并行执行能力;每个物理核在同一时刻只能执行一个线程 | 容器绑核时,优先绑定到不同物理核可获得最佳性能隔离 |
| 逻辑核 | 操作系统看到的可调度单元(CPU 编号) | 由超线程或物理核直接提供;/proc/cpuinfo 中的 processor 编号 |
容器 cpuset 配置直接操作逻辑核编号 |
| 超线程 | 一个物理核内共享执行资源的两个逻辑核 | 共享 L1/L2 缓存和 ALU;一个逻辑核空闲时另一核可借用资源 | 两个超线程逻辑核竞争资源,不适合延迟敏感型容器 |
性能调优提示: 在容器绑核场景中,应避免将两个容器进程绑定到同一物理核的两个超线程逻辑核上。这会导致严重的资源争抢和性能抖动。推荐使用
lscpu -e 命令查看 CPU 拓扑,确认哪些逻辑核共享同一物理核。
1.2 NUMA 架构简介
NUMA(非统一内存访问)是现代多路服务器的主流架构。在 NUMA 系统中,每个 CPU 插槽拥有本地内存,访问本地内存的速度远快于访问远端内存。容器绑核时必须考虑 NUMA 拓扑,否则会导致内存访问延迟显著增加。
- NUMA 节点(Node): 每个节点包含一组 CPU 和本地内存。节点内访问延迟低(通常 60-100ns)。
- 跨节点访问: 访问其他节点的内存需要经过 QPI/UPI 总线,延迟增加 1.5-2 倍(通常 120-200ns)。
- 容器绑核原则: 容器进程应尽量绑定在同一 NUMA 节点内的 CPU 上,并分配该节点的本地内存。
| 特性 | UMA(统一内存访问) | NUMA(非统一内存访问) |
|---|---|---|
| 内存访问延迟 | 所有 CPU 访问内存延迟一致 | 本地内存低延迟,远端内存高延迟 |
| 总线架构 | 单总线/交叉开关 | 每个节点独立内存控制器,通过互联总线通信 |
| 扩展性 | CPU 数量增加时总线成为瓶颈 | 支持更多 CPU,扩展性好 |
| 容器影响 | 绑核只需考虑 CPU 缓存 | 绑核必须同时考虑 CPU 和内存节点亲和性 |
实战检查命令: 使用
numactl --hardware 查看 NUMA 节点拓扑;使用 lscpu | grep NUMA 查看节点与 CPU 的映射关系。在容器启动时,可通过 --cpuset-mems 参数指定内存节点。
1.3 CPU 时间片与调度原理
Linux 内核的 CFS(完全公平调度器)是容器 CPU 管理的核心。CFS 通过时间片分配和虚拟运行时间(vruntime)机制,确保所有进程公平获得 CPU 资源。容器运行时(如 runc、containerd)通过 cgroup 的 cpu 子系统与 CFS 交互。
1.3.1 CFS 调度核心概念
| 概念 | 说明 | 容器调优意义 |
|---|---|---|
| 时间片(sched_latency) | CFS 保证每个进程在一个调度周期内至少运行一次的时间窗口,默认 6ms | 容器 CPU 限制(quota)基于此计算 |
| 虚拟运行时间(vruntime) | 进程实际运行时间经过优先级加权后的值;CFS 总是选择 vruntime 最小的进程运行 | nice 值调整影响容器进程的 CPU 份额 |
| 调度周期(sched_period) | CFS 重新计算所有进程 vruntime 并选择下一个运行进程的间隔 | 影响容器 CPU 分配的细粒度 |
| 最小粒度(min_granularity) | 进程被调度后至少运行的时间,默认 0.75ms,防止频繁上下文切换 | 对延迟敏感型容器,过小会导致切换开销 |
1.3.2 容器 CPU 限制的 cgroup 接口
容器运行时通过 cgroup v1 的 cpu 子系统或 cgroup v2 的 cpu 控制器来管理 CPU。以下是核心参数:
# cgroup v1 示例(/sys/fs/cgroup/cpu/docker/<container-id>/)
cpu.cfs_period_us # 调度周期,默认 100000(100ms)
cpu.cfs_quota_us # 在周期内允许的 CPU 时间,-1 表示无限制
# 例如 quota=50000, period=100000 表示使用 0.5 个 CPU
cpu.shares # CPU 权重(相对份额),默认 1024
cpu.rt_runtime_us # 实时调度运行时间(通常不用于容器)
# cgroup v2 示例(/sys/fs/cgroup/system.slice/docker-<id>.scope/)
cpu.max # 格式:quota period,例如 "50000 100000"
cpu.weight # CPU 权重,范围 [1, 10000],默认 100
常见陷阱: 当
cpu.cfs_quota_us 设置过小(例如 10000us)且容器进程数较多时,会导致严重的调度延迟和 throttling(节流)。使用 /sys/fs/cgroup/cpu/<group>/cpu.stat 中的 nr_throttled 字段可以监控容器是否被过度节流。
1.3.3 调度流程与容器上下文切换
- 时钟中断触发: 内核定时器(通常 1000Hz 或 250Hz)触发调度器检查。
- vruntime 更新: 当前运行进程的 vruntime 增加(实际运行时间 × 优先级权重因子)。
- 红黑树选择: CFS 从红黑树中选取 vruntime 最小的进程作为下一个运行进程。
- 上下文切换: 保存当前进程状态(寄存器、页表、TLB),加载新进程状态。
- 容器 cgroup 检查: 如果进程属于容器 cgroup,内核检查 quota 是否耗尽;若耗尽则进程进入 throttled 状态,直到下一个周期。
性能调优关键指标: 使用
perf sched 或 trace-cmd 可以分析容器进程的调度延迟和上下文切换频率。对于绑核容器,上下文切换应尽量发生在同一物理核上,避免跨核迁移导致缓存失效。
1.4 本节核心要点总结
- 物理核 vs 逻辑核: 超线程逻辑核共享物理核资源,容器绑核时应避免绑定到同一物理核的两个逻辑核上。
- NUMA 亲和性: 容器进程和内存分配应尽量限制在同一 NUMA 节点内,使用
numactl和cpuset-mems控制。 - CFS 调度: 通过 vruntime 红黑树实现公平调度;容器 cgroup 的 quota/period 控制 CPU 使用上限。
- 监控关键指标:
nr_throttled(节流次数)、context switches(上下文切换)、cpu migrations(CPU 迁移)是判断容器 CPU 性能瓶颈的核心指标。