当多个应用运行在同一节点时,CPU资源争抢和频繁的上下文切换可能导致性能敏感型应用产生抖动。通过启用CPU拓扑感知调度,可将应用进程独占绑定到指定CPU核心(即CPU绑核),减少因核心切换和跨NUMA访存带来的性能不确定性。
工作原理
Kubernetes默认依赖内核的CFS调度器(Completely Fair Scheduler)来实现CPU的负载均衡。该调度器通过公平分配CPU时间片将负载“打散”到各个核心,但可能因忽略了CPU的物理拓扑导致性能敏感型应用的性能抖动。
Kubernetes的CPU Manager(static策略)能够为Pod绑定并独占CPU核心,但仍存在以下局限:
调度器无感知:原生kube-scheduler仅在节点维度做决策,无法感知整个集群的CPU拓扑,不能为Pod在全局范围内寻找最优的物理核心布局。
拓扑不敏感:
static策略在节点内分配核心时,不感知NUMA架构,可能导致跨NUMA节点的内存访问,引入额外延迟。灵活性不足:该策略严格要求Pod的QoS为
Guaranteed,不适用于Burstable和BestEffort类型的Pod。
为此,ACK基于新版Scheduling Framework提供了增强的CPU拓扑感知调度能力,由ACK kube-scheduler和ack-koordinator协同完成。
节点拓扑上报:ack-koordinator实时感知本地的CPU物理拓扑(如Socket、NUMA、缓存),并将其上报至调度中心。
全局拓扑感知调度:kube-scheduler基于全局拓扑信息,在集群范围内为Pod筛选出当前最优节点,并规划核心分配方案(如在选择最优节点时默认寻找已绑定应用数量最少的核)。该分配方案会作为调度结果写入Pod的Annotation中。
本地绑核执行:Pod调度至目标节点后,ack-koordinator会根据Pod的 Annotation 并修改其对应Cgroup的
cpuset.cpus文件,以完成物理核心的绑定。
适用场景
性能敏感型应用:对CPU上下文切换延迟极度敏感,如高频交易、实时数据处理。
NUMA敏感型应用:部署在神龙裸金属(Intel、AMD)等多核、多Socket服务器上,且业务性能受内存访问延迟影响巨大,期望避免跨NUMA访存。
确定性算力需求:需要稳定、可预期的计算能力,如科学计算、大数据分析任务。
遗留应用适配:应用尚未完成对云原生场景的适配,例如在设置线程数量时未考虑容器规格(而是整机物理核数量),导致性能下降。
不建议在以下场景中启用此功能:
CPU超卖环境:绑核的资源独占特性与超卖的资源共享模型存在不兼容,可能造成资源浪费并干扰超卖调度逻辑。
通用型或I/O密集型应用:大多数Web服务、中间件等应用对CPU核心切换不敏感,无需启用此功能。
准备工作
已创建ACK托管集群Pro版,且已配置节点池的CPU Policy(
cpuManagerPolicy)为none。请参见自定义节点池kubelet配置配置cpuManagerPolicy取值。已安装ack-koordinator,且组件版本为0.2.0及以上。
步骤一:部署示例应用
本文以一个Nginx应用为例,介绍如何启用CPU拓扑感知调度,实现CPU绑核。
创建
nginx-app.yaml。部署应用。
kubectl apply -f nginx-app.yaml登录Pod所在节点,获取Pod的UID和Container ID。
获取Pod名称。
执行kubectl get pods -n <your-namespace>查看Pod名称,如nginx-deployment-6f5899*****。获取Pod UID。
# 将 <your-pod-name> 替换为Pod实际名称 kubectl get pod <your-pod-name> -n default -o jsonpath='{.metadata.uid}{"\n"}'预期输出:
uid: a78a02b5-c87f-4e74-9ddd-254c163*****获取Container ID。
# 将 <your-pod-name> 替换为Pod实际名称 kubectl describe pod <your-pod-name> -n default在输出的
Containers字段下定位Container ID,并删去前缀(如containerd://)。预期输出:
Containers: nginx: Container ID: containerd://b8b88a70096aabb0aea197dd2aba78d15bcbe9145198ef46a0474b31*****
确认节点的cgroup版本。
stat -fc %T /sys/fs/cgroup/输出为
cgroup_root:系统使用 cgroup v1。输出为
cgroup2fs:系统使用 cgroup v2。
根据cgroup版本执行对应的验证命令,查看CPU绑核情况。
在cgroup路径中,Pod UID中的
-需替换为_。如原始Pod UID为a78a02b5-c87f-4e74-9ddd-254c163*****,则路径中使用的格式为a78a02b5_c87f_4e74_9ddd_254c163*****。cgroup v1:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/cpuset/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpuscgroup v2:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpus.effective
预期输出为一个范围,代表容器可以使用节点上的所有核心,未进行绑定。
0-31
步骤二:启用CPU拓扑感知调度功能
可通过为Pod添加Annotation来启用CPU拓扑感知调度。
普通绑核策略:通用策略,遵循1:1的绑核原则,为Pod绑定
resources.limits.cpu所指定的数量的核心。优先选择同一NUMA节点内的CPU核,以保证良好的内存访问性能。自动绑核策略:针对特定硬件优化的策略,优先通过绑定一个完整的物理核心簇(如AMD CPU的CCX/CCD)来最大化CPU局部性并提升并发度。推荐在特定的大规格(如32核及以上)AMD机型上使用。
启用CPU拓扑感知调度时,请勿在Pod上直接指定nodeName,kube-scheduler不参与此类Pod的调度过程。可使用nodeSelector等字段配置亲和性策略来指定节点调度。
普通绑核策略
配置方法:
在YAML中添加Annotation
cpuset-scheduler: "true"Pod中:在
metadata.annotations字段中添加工作负载(例如Deployment)中:在
spec.template.metadata.annotations字段中添加
在
Containers字段中配置resources.limits.cpu的取值(需为整数),限定CPU绑核范围。
配置示例:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: annotations: # 设置为true,启用CPU拓扑感知调度 cpuset-scheduler: "true" labels: app: nginx spec: containers: - name: nginx image: alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/nginx_optimized:20240221-1.20.1-2.3.0 ports: - containerPort: 80 command: - "sleep" - "infinity" resources: requests: cpu: 4 memory: 8Gi limits: # 设置cpu值,需为整数 cpu: 4 memory: 8Gi结果验证:
可通过以下两种方式查看。
查看节点Cgroup文件
等待Pod正常运行后,再次登录其所在节点,查看CPU绑核情况。
Pod UID中的
-需替换为_。cgroup v1:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/cpuset/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpuscgroup v2:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpus.effective
预期输出为一组核心ID,与
limits.cpu: 4的设置相符,表明绑核成功。0-3检查Pod Annotation
Pod调度至目标节点后,ack-koordinator会根据Pod的 Annotation 并修改其对应Cgroup的
cpuset.cpus文件,以完成物理核心的绑定。查看Pod
cpuset信息。# 将 <your-pod-name> 替换实际Pod名称 kubectl get pod <your-pod-name> -n default -o yaml | grep "cpuset:"预期输出:
cpuset: '{"nginx":{"0":{"elems":{"0":{},"1":{},"2":{},"3":{}}}}}'输出结果说明:
"nginx":{...}:针对名为nginx的容器的配置。"0":{...}:最外层的键"0"代表NUMA节点ID。本示例表示所有绑定的核都位于同一NUMA Node 0上,避免了跨NUMA访存的性能损耗。"elems":{"0":{},"1":{},"2":{},"3":{}}:键代表被绑定的物理CPU核心ID。此示例中,容器被成功绑定到了0、1、2、3这四个核心上,与limits.cpu: 4相符。
自动绑核策略
配置方法:
添加两个Annotation:
cpuset-scheduler: "true"和cpu-policy: "static-burst"Pod中:在
metadata.annotations字段中添加工作负载(例如Deployment)中:在
spec.template.metadata.annotations字段中添加
在
Containers字段中配置resources.limits.cpu的取值(需为整数),限定CPU绑核范围。
配置示例:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: annotations: # 设置为true,启用CPU拓扑感知调度 cpuset-scheduler: "true" # 设置为static-burst,启用自动绑核与NUMA亲和策略 cpu-policy: "static-burst" labels: app: nginx spec: containers: - name: nginx image: alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/nginx_optimized:20240221-1.20.1-2.3.0 ports: - containerPort: 80 command: - "sleep" - "infinity" resources: requests: cpu: 4 memory: 8Gi limits: # 设置cpu值,需为整数 cpu: 4 memory: 8Gi结果验证:
自动绑核策略会实时分析节点的CPU拓扑和资源使用情况,绑核数量可能会大于Pod的显式请求。绑核情况可通过以下两种方式查看。
查看节点Cgroup文件
等待Pod正常运行后,再次登录其所在节点,查看CPU绑核情况。
Pod UID中的
-需替换为_。cgroup v1:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/cpuset/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpuscgroup v2:
# 将 <POD_UID> 和 <CONTAINER_ID> 替换为实际值 cat /sys/fs/cgroup/kubepods.slice/kubepods-pod<POD_UID>.slice/cri-containerd-<CONTAINER_ID>.scope/cpuset.cpus.effective
预期输出为一组确切的核心ID,表明绑核成功。
0-7检查Pod Annotation
Pod调度至目标节点后,ack-koordinator会根据Pod的 Annotation 并修改其对应Cgroup的
cpuset.cpus文件,以完成物理核心的绑定。查看Pod
cpuset信息。# 将 <your-pod-name> 替换实际Pod名称 kubectl get pod <your-pod-name> -n default -o yaml | grep "cpuset:"预期输出:
cpuset: '{"nginx":{"0":{"elems":{"0":{},"1":{},"2":{},"3":{},"4":{},"5":{},"6":{},"7":{}}}}}'输出结果说明:
"nginx":{...}:针对名为nginx的容器的配置。"0":{...}:最外层的键"0"代表NUMA节点ID。本示例表示所有绑定的核都位于同一NUMA Node 0上,避免了跨NUMA访存的性能损耗。"elems":{"0":{},"1":{},"2":{},"3":{},"4":{},"5":{},"6":{},"7":{}}:键代表被绑定的物理CPU核心ID。此示例中,容器已绑定至0~7核心上。
相关操作
停用CPU拓扑感知调度(CPU核心解绑)
编辑应用YAML,从
spec.template.metadata.annotations中移除Annotationcpuset-scheduler: "true"和cpu-policy: "static-burst"(如有)。在业务低峰期应用修改后的YAML,等待Pod重启后变更生效。
解绑后,Pod 进程不再被绑定到特定物理核心,可能会在节点所有可用 CPU 核心之间切换。潜在影响包括:
因跨核心的上下文切换,CPU 使用率可能微幅上升。
对于计算密集型应用,由于无法独占核心,可能会重新出现因 CPU 资源争抢导致的性能抖动。
当多个高负载 Pod 的进程被调度至同一核心时,可能导致该核心负载瞬时过高,进而触发容器的 CPU 限流(Throttling)。
生产环境使用建议
可观测性:在启用绑核前后,接入阿里云Prometheus监控,密切关注应用的关键性能指标(如RT、QPS)以及节点的CPU使用率、CPU Throttling等指标,关注绑核带来的性能变化。
分批变更:对于多副本应用,建议采用金丝雀发布或分批更新的方式,逐步启用或停用绑核策略,以控制变更风险。
费用说明
ack-koordinator组件本身的安装和使用免费,但在以下场景中可能产生额外费用。
ack-koordinator是非托管组件,安装后将占用Worker节点资源。您可以在安装组件时配置各模块的资源申请量。
ack-koordinator默认会将资源画像、精细化调度等功能的监控指标以Prometheus的格式对外透出。若您配置组件时开启了ACK-Koordinator开启Prometheus监控指标选项并使用了阿里云Prometheus服务,这些指标将被视为自定义指标并产生相应费用。具体费用取决于您的集群规模和应用数量等因素。建议您在启用此功能前,仔细阅读阿里云PrometheusPrometheus 实例计费,了解自定义指标的免费额度和收费策略。您可以通过用量查询,监控和管理您的资源使用情况。