文章

CPU 隔离与中断亲和性

CPU 隔离与中断亲和性

概述

在 Kubernetes 节点上,系统进程(kubelet、containerd、sshd、监控 agent)与业务 Pod 共享 CPU。对于延迟敏感型工作负载(etcd、GPU 训练、DPDK、实时流处理),CPU 争抢会导致 p99 延迟井喷。CPU 隔离通过专属 CPU + tickless 模式 + 中断分散,把噪音降到最低。

一句话:CPU 隔离不是”给 Pod 更多 CPU”,而是”让 Pod 的 CPU 不被任何东西打扰”。

问题根源:是什么在抢 CPU

即使 Pod 独占了 CPU,以下内核活动仍会打断用户进程:

干扰源频率影响
定时器中断(tick)每秒 100-1000 次(CONFIG_HZ每 1-10ms 暂停用户进程一次
RCU 回调取决于 RCU 宽限期softirq 占用 CPU
中断处理网卡可达每秒百万次硬中断 + softirq
khugepaged / kcompactd持续内存压缩消耗 CPU
kubelet 健康检查每 10sHTTP probe 使用 CPU
Prometheus node_exporter每 15s采集指标消耗 CPU

CPU 隔离的目标是消除或最小化以上所有干扰。

isolcpus —— 完全隔离 CPU

原理

isolcpus 告诉内核调度器:不要主动把任何进程放到这些 CPU 上。但做了 3 个不完美的地方:

  1. 只影响 CFS 调度器,不影响内核线程和中断
  2. 不影响实时调度类(SCHED_FIFO/SCHED_RR)
  3. 隔离的 CPU 仍会收到定时器中断和软中断
# grub 启动参数
GRUB_CMDLINE_LINUX="isolcpus=4-15"
# CPU 0-3 给系统用,CPU 4-15 完全隔离给业务

cgroup v2 cpuset → 更好的替代

cgroup v2 的 cpuset 可以动态分配(无需重启),且能被 K8s CPU Manager 管理:

# 创建隔离 cpuset
mkdir /sys/fs/cgroup/isolated
echo "4-15" > /sys/fs/cgroup/isolated/cpuset.cpus
echo "0" > /sys/fs/cgroup/isolated/cpuset.mems

# 把已运行的进程绑定到隔离 CPU
echo <pid> > /sys/fs/cgroup/isolated/cgroup.procs

K8s v1.26+ 的 CPU Manager static policy 内部使用 cgroup cpuset 实现。

nohz_full + rcu_nocbs —— Tickless + RCU offload

nohz_full

内核的 CONFIG_NO_HZ_FULL 让 CPU 在只有单个可运行任务时停止定时器中断(tickless)。这对延迟敏感应用至关重要。

# grub 启动参数
GRUB_CMDLINE_LINUX="nohz_full=4-15"

条件:CPU 上必须只有 一个 可运行任务。如果有两个,tick 恢复。

rcu_nocbs

将隔离 CPU 的 RCU 回调处理迁移到其他 CPU 上,彻底消除 RCU softirq 干扰:

GRUB_CMDLINE_LINUX="rcu_nocbs=4-15"

完整 grub 行示例

GRUB_CMDLINE_LINUX="... isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15"

验证隔离效果

# 检查中断分布
cat /proc/interrupts | grep -E "CPU4 |CPU5 |CPU6 "
# 隔离 CPU 的中断数应远少于非隔离 CPU

# 检查 timer 中断
cat /proc/interrupts | grep -E "LOC:"
# 隔离 CPU 的 LOC (Local Timer Interrupts) 应很低

# 检查 RCU 回调迁移(1 = 已迁移)
cat /sys/devices/system/cpu/cpu4/rcu_expedited /sys/devices/system/cpu/cpu4/rcu_normal
# 都应为 1

Kubelet CPU Manager

CPU Manager 是 K8s 提供的 CPU 亲和性机制。当 Pod 满足 Guaranteed QoS + requests == limits + CPU 为整数核时,kubelet 为其分配独占 CPU

# /var/lib/kubelet/config.yaml
cpuManagerPolicy: static
cpuManagerReconcilePeriod: 5s
reservedSystemCPUs: "0-3"               # CPU 0-3 保留给系统进程
cpuManagerPolicyOptions:
  full-pcpus-only: true                 # 只分配完整的物理核(不跨 HT)
  distribute-cpus-across-numa: true     # 跨 NUMA 均匀分配
  align-by-socket: true                 # 按 Socket 对齐

Pod 资源配置:

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: latency-critical
    resources:
      requests:
        cpu: 4                # 必须是整数核
        memory: 8Gi
      limits:
        cpu: 4                # 必须等于 requests
        memory: 8Gi
  # QoS: Guaranteed → 触发 CPU Manager exclusive allocation

验证 Pod 是否获得独占 CPU:

# 在节点上
cat /var/lib/kubelet/cpu_manager_state
# {"policyName":"static","defaultCpuSet":"0-3","entries":{"<pod-uid>":{"<container>":"4-7"}}}

# 在容器内
cat /sys/fs/cgroup/cpuset.cpus
# 4-7 ← 独占

static vs none

策略独占 CPU适用场景
none无,所有 Pod 共享 CPU 池通用多租户
staticGuaranteed + 整数核 = 独占延迟敏感应用

中断亲和性(IRQ Affinity)

即使 CPU 被 isolcpus 隔离,网卡中断仍可能落到隔离 CPU 上。需要通过 IRQ 亲和性(smp_affinity)将中断引导到系统 CPU。

手动配置

# 查看某网卡的所有中断号
grep mlx5_0 /proc/interrupts | awk -F: '{print $1}'

# 把 mlx5_0 的中断分散到 CPU 0-3(系统 CPU)
for irq in $(grep mlx5_0 /proc/interrupts | awk -F: '{print $1}'); do
  echo "0-3" > /proc/irq/$irq/smp_affinity_list
  # 或位掩码方式:echo 0f > /proc/irq/$irq/smp_affinity(0f = CPU0-3)
done

# 验证
cat /proc/interrupts | grep mlx5_0
# 确认每一列只有 CPU0-3 有数字,CPU4-15 为 0

高速网卡的多队列中断

现代网卡(Mellanox ConnectX、Intel E810)支持多队列 RSS(Receive Side Scaling),每个队列独立中断:

# 查看队列数
ethtool -l eth0
# Channel parameters for eth0:
# Pre-set maximums:
# RX:             63
# Combined:       63
# Current hardware settings:
# RX:             63

# 查看每个队列的中断
ls /proc/irq/ | while read irq; do
  grep -l "mlx5_0" /proc/irq/$irq/* 2>/dev/null && echo "IRQ $irq"
done

# 为每个队列分配独立 CPU(系统 CPU 池内轮询)
irqs=($(grep mlx5_0-rx /proc/interrupts | awk -F: '{print $1}'))
for i in $(seq 0 $((${#irqs[@]} - 1))); do
  cpu=$((i % 4))            # 轮询分配 CPU 0-3
  echo $cpu > /proc/irq/${irqs[$i]}/smp_affinity_list
done

irqbalance 的正确用法

irqbalance 自动管理中断分配,但默认行为不适合 CPU 隔离场景(它可能将中断分配到隔离 CPU)。需要配置:

# /etc/sysconfig/irqbalance(RHEL)或 /etc/default/irqbalance(Debian)
IRQBALANCE_ARGS="--hintpolicy=exact"
IRQBALANCE_BANNED_CPUS="4-15"   # 禁止将中断分配到隔离 CPU
ONE_SHOT=1                      # 一次性分配后退出(不建议),或用 standard 模式
systemctl restart irqbalance

中断亲和性 vs RPS/RFS

中断亲和性只控制硬中断落在哪个 CPU。RPS(Receive Packet Steering)和 RFS(Receive Flow Steering)控制**软中断(softirq)**在哪个 CPU 处理:

# RPS:将网络包的软中断处理分散到多个 CPU
echo "0f" > /sys/class/net/eth0/queues/rx-0/rps_cpus   # CPU0-3

# RFS:根据应用所在 CPU 调度软中断
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

对于 CPU 隔离场景:RPS mask 应设为系统 CPU(0-3),而非隔离 CPU。

生产级 CPU 隔离方案

完整的节点初始化

#!/bin/bash
# cpu-isolation-init.sh

# 1. grub 参数(需重启生效)
# 编辑 /etc/default/grub:
# GRUB_CMDLINE_LINUX="isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15"
# update-grub && reboot

# 2. 内核线程迁移到系统 CPU
# 重启后,把已存在的内核线程迁移到 CPU 0-3
for pid in $(pgrep -f "rcuog|rcu_preempt|kworker|ksoftirqd|migration"); do
  taskset -pc 0-3 $pid 2>/dev/null
done

# 3. 配置 irqbalance
mkdir -p /etc/irqbalance
echo "IRQBALANCE_BANNED_CPUS=4-15" > /etc/irqbalance/env
systemctl restart irqbalance

# 4. 网卡中断只分配到系统 CPU
for iface in eth0 mlx5_0; do
  for irq in $(grep "$iface" /proc/interrupts | awk -F: '{print $1}'); do
    echo 0-3 > /proc/irq/$irq/smp_affinity_list 2>/dev/null
  done
done

# 5. kubelet CPU Manager
cat > /var/lib/kubelet/config.yaml << EOF
cpuManagerPolicy: static
cpuManagerReconcilePeriod: 5s
reservedSystemCPUs: "0-3"
cpuManagerPolicyOptions:
  full-pcpus-only: true
  distribute-cpus-across-numa: true
EOF

systemctl restart kubelet

验证清单

# ✓ 中断不落在隔离 CPU 上
cat /proc/interrupts | awk '{for(i=5;i<=16;i++) sum[i]+=$i} END {for(i=5;i<=16;i++) print "CPU"i-4": "sum[i]}'
# CPU 4-15 的中断数应远小于 CPU 0-3

# ✓ timer 中断被 tickless
cat /proc/interrupts | grep LOC | awk '{for(i=5;i<=NF;i++) if($i>0) print "CPU"i-4": "$i}' | sort -t: -k2 -nr

# ✓ 独占 CPU 的 Pod 正确分布
cat /var/lib/kubelet/cpu_manager_state

# ✓ 没有进程跑在隔离 CPU 上(除了期望的 Pod)
ps -eo pid,psr,comm | awk '$2>=4 && $2<=15 {print}' | grep -v "train\|etcd\|redis"
# 应该只显示你的关键进程

常见问题

问题原因解决
隔离 CPU 仍有中断irqbalance 将中断分配到了隔离 CPU配置 IRQBALANCE_BANNED_CPUS
isolcpus 后内核线程仍在隔离 CPUisolcpus 不影响内核线程手动 taskset -pc 0-3 <pid>
CPU Manager 未分配独占 CPUPod 不是 Guaranteed QoS 或 requests≠limits 或非整数核确保 requests==limits 且为整数核
nohz_full 不生效(仍有 tick)CPU 上有多个可运行任务检查 cat /proc/stat 的 runnable 数
独占 CPU 的 Pod 延迟仍然抖动khugepagedkcompactd 等内核线程仍在隔离 CPUtaskset 迁移到系统 CPU
CPU Manager 分配了 HT 兄弟核未设 full-pcpus-only启用该选项并要求 2 的幂次核数

关联知识

参考资源

学习时间

阶段时间备注
深度梳理2026-06-30isolcpus、nohz_full、rcu_nocbs、CPU Manager、IRQ affinity、生产初始化脚本

状态: 🌱 学习中 下次复习日期: 2026-07-07