在灵骏集群中使用网络拓扑感知调度

在机器学习或大数据分析类作业中,Pod与Pod间通常有较大的网络通信需求。在默认情况下原生Kubernetes调度器会将Pod均匀打散在集群的每台机器上,但是这样会增大Pod间的通信距离,导致作业完成时间变长。在灵骏集群中可以通过网络拓扑感知调度,将Pod声明调度到相同的一层转发域或二层转发域下,以此减少机器间的网络通信时延,进而缩短作业完成时间。

方案概述

网络拓扑感知调度通过贪心策略将任务放置在跨越更少拓扑的节点上。

假设当前灵骏集群中存在Pod和ASW两层网络拓扑,ASW为灵骏机器的直接接口,Pod为范围更大的网络拓扑,所以跨灵骏节点的网络传输需要至少1跳的网络转发,跨ASW的网络传输需要至少2跳的网络转发。

  • 若您提交了一个需要两台节点的任务,网络拓扑感知调度会将任务放置在Node A-B或Node E-F上。

  • 若您提交了一个需要四台节点的任务,网络拓扑感知调度会将任务放置在Node A-D或Node E-H上。

image

在ACK灵骏集群中,如何判断节点之间是否在相同的ASW下或相同的Pod下呢?

您可以查看节点上已配置包含网络位置信息的标签alibabacloud.com/asw-idalibabacloud.com/point-of-delivery

声明配置拓扑结构

在灵骏集群中使用网络拓扑感知调度,需要定义集群级别的拓扑调度需求,最后在任务中标识网络拓扑感知调度信息。

  1. 创建cluster-network-topology.yaml声明两级的拓扑结构。

    展开查看完整代码

    apiVersion: scheduling.koordinator.sh/v1alpha1
    kind: ClusterNetworkTopology
    metadata:
      # 保持不变
      name: default
    spec:
      networkTopologySpec:
      # parentTopologyLayer用于声明上层拓扑结构
      - parentTopologyLayer: ASWTopologyLayer
        # 定义节点级别拓扑,最低一级必须为NodeTopologyLayer
        topologyLayer: NodeTopologyLayer
      # 以下部分定义跨机网络拓扑,通常不需要进行修改
      - labelKey:
        - alibabacloud.com/point-of-delivery
        topologyLayer: PoDTopologyLayer
      - labelKey:
        - alibabacloud.com/asw-id
        parentTopologyLayer: PoDTopologyLayer
        topologyLayer: ASWTopologyLayer
  2. 创建sample-network-topology.yaml声明任务中的网络拓扑感知调度需求。

    展开查看完整代码

    apiVersion: scheduling.koordinator.sh/v1alpha1
    kind: JobNetworkTopology
    metadata:
      labels:
        network-topology-permit-wait-time: "999999"
      # 任务名称
      name: sample-network-topology
      # 任务所在命名空间
      namespace: sample-network-topology
    spec:
      topologyStrategy:
      # 允许跨ASW调度
      - layer: ASWTopologyLayer
        # 当前支持PreferGather以及MustGather两种策略。PreferGather表示Pod调度可以跨该拓扑
        # 进行调度,MustGather表示Pod只能调度在相同的该拓扑下
        strategy: PreferGather
      - layer: NodeTopologyLayer
        strategy: PreferGather
      # 不允许跨Pod调度
      - layer: PoDTopologyLayer
        strategy: MustGather
      # 任务的Pod数量
      workerNum: 2
  3. 创建pi.yaml标识网络拓扑感知调度信息。

    提交任务时需要指定关联的JobNetworkTopology信息。

    展开查看完整代码

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: pi
    spec:
      # 任务的Pod数量,保持与JobNetworkTopology中的数量一致
      parallelism: 2
      template:
        metadata:
          labels:
            # 任务的Pod数量,保持与JobNetworkTopology中的数量一致
            pod-group.scheduling.sigs.k8s.io/min-available: "2"
            pod-group.scheduling.sigs.k8s.io/name: sample-gang
            # 引用刚刚提交的任务拓扑信息
            network-topology-job-name: sample-network-topology
            network-topology-job-namespace: sample-network-topology
        spec:
          schedulerName: default-scheduler
          containers:
          - name: pi
            image: perl:5.34.0
            command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
            resources:
              limits:
                # 示例中使用单卡作为示例,实际使用中可按需填写
                nvidia.com/gpu: 1
          restartPolicy: Never
      backoffLimit: 4
  4. 执行命令在集群中部署上述YAML文件。

    kubectl apply -f cluster-network-topology.yaml
    kubectl apply -f sample-network-topology.yaml
    kubectl apply -f pi.yaml

调度效果展示

  1. 通过kubectl连接集群获取当前网络拓扑结构信息。

    在灵骏集群中,集群中组件lingjun-networktopology-collector从节点上采集并以label的方式标记在节点上。

    而对于其他节点或其他类型集群,需要您手动标记label,标记时使用的labelkey需要与上文中ClusterNetworkTopology中的labelKey对应。

    # network topology is like:
    #              test-pod-1                     test-pod-2
    #        /          |           \                   |
    #    test-1      test-2      test-3               test-4
    #     /   \         |           |                   |
    #   0.12  0.14     0.15        0.16                0.17
    
    ➜  network kubectl get no -l alibabacloud.com/asw-id,alibabacloud.com/point-of-delivery -ojson | jq '.items[] | {"Name":.metadata.name, "ASW":.metadata.labels."alibabacloud.com/asw-id", "POD":.metadata.labels."alibabacloud.com/point-of-delivery"}'
    {
      "Name": "cn-hongkong.10.1.0.12",
      "ASW": "test-1",
      "POD": "test-pod-1"
    }
    {
      "Name": "cn-hongkong.10.1.0.14",
      "ASW": "test-1",
      "POD": "test-pod-1"
    }
    {
      "Name": "cn-hongkong.10.1.0.15",
      "ASW": "test-2",
      "POD": "test-pod-1"
    }
    {
      "Name": "cn-hongkong.10.1.0.16",
      "ASW": "test-3",
      "POD": "test-pod-1"
    }
    {
      "Name": "cn-hongkong.10.1.0.17",
      "ASW": "test-4",
      "POD": "test-pod-2"
    }
  2. 提交上文中配置的Job任务,可以看到任务运行在test-1下的两个节点上。

    ➜ kubectl get pod -owide
    NAME       READY   STATUS              RESTARTS   AGE   IP               NODE                    NOMINATED NODE   READINESS GATES
    pi-8p89l   1/1     Running             0          4s    172.30.240.197   cn-hongkong.10.1.0.14   <none>           <none>
    pi-p8swv   0/1     ContainerCreating   0          4s    <none>           cn-hongkong.10.1.0.12   <none>           <none>
    1. 若将任务中的Pod数量设置为4。

      (需要同时更改上文Job的parallelism以及label中的pod-group.scheduling.sigs.k8s.io/min-available和JobNetworkTopology的workerNum),可以看到只有test-pod-2上的节点没有被调度。

      ➜ kubectl get pod -owide
      NAME       READY   STATUS              RESTARTS   AGE   IP               NODE                    NOMINATED NODE   READINESS GATES
      pi-2kwq9   1/1     Running             0          4s    172.30.241.123   cn-hongkong.10.1.0.12   <none>           <none>
      pi-87hm5   0/1     ContainerCreating   0          4s    <none>           cn-hongkong.10.1.0.16   <none>           <none>
      pi-bsvx8   1/1     Running             0          4s    172.30.240.198   cn-hongkong.10.1.0.14   <none>           <none>
      pi-dvwhl   0/1     ContainerCreating   0          4s    <none>           cn-hongkong.10.1.0.15   <none>           <none>
    2. 若将任务中的Pod数量设置为5。

      (需要同时更改上文Job的parallelism以及label中的pod-group.scheduling.sigs.k8s.io/min-available和JobNetworkTopology的workerNum),可以看到任务调度失败,其中第一个调度的Pod中带有调度失败信息,其中说明了调度失败是由于跨Pod调度被禁止(all fail topology paths by MustGather reason: [path:RootNode->test-pod-1, freeSlotNum:4], [path:RootNode->DefaultTopologyName, freeSlotNum:0], [path:RootNode->test-pod-2, freeSlotNum:1])。

      ➜ kubectl get pod
      NAME       READY   STATUS    RESTARTS   AGE
      pi-75qf5   0/1     Pending   0          2s
      pi-8k4nd   0/1     Pending   0          2s
      pi-b2pmc   0/1     Pending   0          2s
      pi-n7c2b   0/1     Pending   0          2s
      pi-wf4zn   0/1     Pending   0          2s
      
      
      ➜ kubectl get pod -ojson | jq '.items[].status'
      {
        "conditions": [
          {
            "lastProbeTime": null,
            "lastTransitionTime": "2024-05-29T07:46:27Z",
            "message": "0/6 nodes are available: 1 Insufficient nvidia.com/gpu, 1 [NetworkTopology begin] cluster total nodes:6, 5 node provide 5 freeSlot, 1 node unavailable cause Insufficient nvidia.com/gpu, job desireNum:5, all fail topology paths by MustGather reason: [path:RootNode->test-pod-1, freeSlotNum:4], [path:RootNode->DefaultTopologyName, freeSlotNum:0], [path:RootNode->test-pod-2, freeSlotNum:1] [NetworkTopology end], 4 NetworkTopology bestPlan empty. network topology job sample-network-topology/sample-network-topology gets rejected due to pod is unschedulable, preemption: 0/6 nodes are available: 1 No victims found on node cn-hongkong.10.1.0.10 for preemptor pod pi-75qf5, 5 Preemption is not helpful for scheduling..",
            "reason": "Unschedulable",
            "status": "False",
            "type": "PodScheduled"
          }
        ],
        "phase": "Pending",
        "qosClass": "BestEffort"
      }
      {
        "phase": "Pending",
        "qosClass": "BestEffort"
      }
      {
        "phase": "Pending",
        "qosClass": "BestEffort"
      }
      {
        "phase": "Pending",
        "qosClass": "BestEffort"
      }
      {
        "phase": "Pending",
        "qosClass": "BestEffort"
      }