CPU拓扑感知调度

当多个应用运行在同一节点时,CPU资源争抢和频繁的上下文切换可能导致性能敏感型应用产生抖动。通过启用CPU拓扑感知调度,可将应用进程独占绑定到指定CPU核心(即CPU绑核),减少因核心切换和跨NUMA访存带来的性能不确定性。

工作原理

Kubernetes默认依赖内核的CFS调度器(Completely Fair Scheduler)来实现CPU的负载均衡。该调度器通过公平分配CPU时间片将负载“打散”到各个核心,但可能因忽略了CPU的物理拓扑导致性能敏感型应用的性能抖动。

KubernetesCPU Manager(static策略)能够为Pod绑定并独占CPU核心,但仍存在以下局限:

  • 调度器无感知:原生kube-scheduler仅在节点维度做决策,无法感知整个集群的CPU拓扑,不能为Pod在全局范围内寻找最优的物理核心布局。

  • 拓扑不敏感:static策略在节点内分配核心时,不感知NUMA架构,可能导致跨NUMA节点的内存访问,引入额外延迟。

  • 灵活性不足:该策略严格要求PodQoSGuaranteed,不适用于BurstableBestEffort类型的Pod。

为此,ACK基于新版Scheduling Framework提供了增强的CPU拓扑感知调度能力,由ACK kube-schedulerack-koordinator协同完成。

  1. 节点拓扑上报:ack-koordinator实时感知本地的CPU物理拓扑(如Socket、NUMA、缓存),并将其上报至调度中心。

  2. 全局拓扑感知调度:kube-scheduler基于全局拓扑信息,在集群范围内为Pod筛选出当前最优节点,并规划核心分配方案(如在选择最优节点时默认寻找已绑定应用数量最少的核)。该分配方案会作为调度结果写入PodAnnotation中。

  3. 本地绑核执行:Pod调度至目标节点后,ack-koordinator会根据Pod的 Annotation 并修改其对应Cgroupcpuset.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绑核。

  1. 创建nginx-app.yaml

    展开查看YAML

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      namespace: default
      labels:
        app: nginx
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          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
  2. 部署应用。

    kubectl apply -f nginx-app.yaml
  3. 登录Pod所在节点,获取PodUIDContainer 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*****
  4. 确认节点的cgroup版本。

    stat -fc %T /sys/fs/cgroup/
    • 输出为 cgroup_root:系统使用 cgroup v1。

    • 输出为 cgroup2fs:系统使用 cgroup v2。

  5. 根据cgroup版本执行对应的验证命令,查看CPU绑核情况。

    cgroup路径中,Pod UID中的-需替换为_。如原始Pod UIDa78a02b5-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.cpus
    • cgroup 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 CPUCCX/CCD)来最大化CPU局部性并提升并发度。推荐在特定的大规格(如32核及以上)AMD机型上使用。

重要

启用CPU拓扑感知调度时,请勿在Pod上直接指定nodeNamekube-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.cpus
    • cgroup 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 并修改其对应Cgroupcpuset.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.cpus
    • cgroup 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 并修改其对应Cgroupcpuset.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核心解绑)

  1. 编辑应用YAML,从spec.template.metadata.annotations中移除Annotation cpuset-scheduler: "true"cpu-policy: "static-burst"(如有)。

  2. 在业务低峰期应用修改后的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 实例计费,了解自定义指标的免费额度和收费策略。您可以通过用量查询,监控和管理您的资源使用情况。

相关文档

  • ACK支持GPU拓扑感知调度,可在节点的GPU组合中选择具有最优训练速度的组合。

  • 可将集群中已分配但未使用的资源量化并提供给低优先级任务使用,以实现动态资源超卖