炼丹师的优雅内功四:volcano 批量处理容器计算业务

511 阅读10分钟

前面的文章

炼丹师的优雅内功三:kubeflow 如何承接机器学习任务

炼丹师的优雅内功二:PyTorch on kubernetes

炼丹师的优雅内功一:TensorFlow on kubernetes

炼丹师的优雅内功前言:从机器学习,分布式训练到k8s云计算

什么是volcano

Volcano是CNCF 下首个也是唯一的基于Kubernetes的容器批量计算平台,主要用于高性能计算场景。它提供了Kubernetes目前缺 少的一套机制,这些机制通常是机器学习大数据应用、科学计算、特效渲染等多种高性能工作负载所需的。作为一个通用批处理平台,Volcano与几乎所有的主流计算框 架无缝对接,如Spark 、TensorFlow 、PyTorch 、 Flink 、Argo 、MindSpore 、 PaddlePaddle 等。它还提供了包括基于各种主流架构的CPU、GPU在内的异构设备混合调度能力。Volcano的设计 理念建立在15年来多种系统和平台大规模运行各种高性能工作负载的使用经验之上,并结合来自开源社区的最佳思想和实践。

在和主流计算框架对接方面,其实volcano 其实是和kubeflow 的功能有些重合的部分的,但是为什么需要单独再学习一下volcano 呢,上面的简介里面有写了,volcano 是一个批量计算平台,同时还具备多种调度算法。支持多种CPU GPU

架构

image.png volcano 的系统架构 Volcano由scheduler、controllermanager、admission和vcctl组成:

  • Scheduler Volcano scheduler通过一系列的action和plugin调度Job,并为它找到一个最适合的节点。与Kubernetes default-scheduler相比,Volcano与众不同的 地方是它支持针对Job的多种调度算法。
  • Controllermanager Volcano controllermanager管理CRD资源的生命周期。它主要由Queue ControllerManager、 PodGroupControllerManager、 VCJob ControllerManager构成。
  • Admission Volcano admission负责对CRD API资源进行校验。
  • Vcctl Volcano vcctl是Volcano的命令行客户端工具。

核心概念

PodGroup

podGroup 是一组强关联的pod集合,主要用于批处理工作负载的场景,比如Tensorflow 中的一组ps 和worker

apiVersion: scheduling.volcano.sh/v1beta1 
kind: PodGroup 
metadata: 
  creationTimestamp: "2020-08-11T12:28:55Z" 
  generation: 5 
  name: test 
  namespace: default 
  ownerReferences: 
  - apiVersion: batch.volcano.sh/v1alpha1 
    blockOwnerDeletion: true 
    controller: true 
    kind: Job 
    name: test 
    uid: 028ecfe8-0ff9-477d-836c-ac5676491a38 
    resourceVersion: "109074" 
    selfLink: /apis/scheduling.volcano.sh/v1beta1/namespaces/default/podgroups/job-1 
    uid: eb2508f5-3349-439c-b94d-4ac23afd71ff 
  spec: 
    minMember: 1 
    minResources: 
      cpu: "3" 
      memory: "2048Mi" 
    priorityClassName: high-prority 
    queue: default 
  status: 
    conditions: 
    - lastTransitionTime: "2020-08-11T12:28:57Z" 
      message: '1/0 tasks in gang unschedulable: pod group is not ready, 1 minAvailable.'
      reason: NotEnoughResources 
      status: "True" 
      transitionID: 77d5be3f-6169-4f86-8e65-0bdc621ce983 
      type: Unschedulable 
    - lastTransitionTime: "2020-08-11T12:29:02Z" 
      reason: tasks in gang are ready to be scheduled 
      status: "True" 
      transitionID: 54514401-5c90-4b11-840d-90c1cda93096 
      type: Scheduled 
      phase: Running 
      running: 1
关键字段
  • minMember minMember表示该podgroup下最少需要运行的pod或任务数量。如果集群资源不满足miniMember数量任务的运行需求,调度器将不会调度任何一个该podgroup 内的任务。
  • queue queue表示该podgroup所属的queue。queue必须提前已创建且状态为open。
  • priorityClassName priorityClassName表示该podgroup的优先级,用于调度器为该queue中所有podgroup进行调度时进行排序。system-node-criticalsystem-cluster-critical 是2个预留的值,表示最高优先级。不特别指定时,默认使用default优先级或zero优先级。
  • minResources minResources表示运行该podgroup所需要的最少资源。当集群可分配资源不满足minResources时,调度器将不会调度任何一个该podgroup内的任务。
  • phase phase表示该podgroup当前的状态。
  • conditions conditions表示该podgroup的具体状态日志,包含了podgroup生命周期中的关键事件。
  • running running表示该podgroup中当前处于running状态的pod或任务的数量。
  • succeed succeed表示该podgroup中当前处于succeed状态的pod或任务的数量。
  • failed failed表示该podgroup中当前处于failed状态的pod或任务的数量。
注意

当创建vcjob 时,若没有指定该vcjob 所属的podGroup,默认会为该vcjob 创建同名的podGroup

Queue

queue是容纳一组podgroup的队列,也是该组podgroup获取集群资源的划分依据

apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
  creationTimestamp: "2020-08-10T11:54:36Z"
  generation: 1
  name: default
  resourceVersion: "559"
  selfLink: /apis/scheduling.volcano.sh/v1beta1/queues/default
  uid: 14082e4c-bef6-4248-a414-1e06d8352bf0
spec:
  reclaimable: true
  weight: 1
  capability:
    cpu: "4"
    memory: "4096Mi"
status:
  state: Open

VolcanoJob

Volcano Job,简称vcjob,是Volcano自定义的Job资源类型。区别于Kubernetes Job,vcjob提供了更多高级功能,如可指定调度器、支持最小运行pod数、 支持task、支持生命周期管理、支持指定队列、支持优先级调度等。Volcano Job更加适用于机器学习、大数据、科学计算等高性能计算场景。

每个vcjob 对应一个podGroup ,描述整个vcjob 的状态,最低资源要求,资源队列等信息

当vcjob 被创建之后controller-mannager 监听到信息之后

  • 1 根据vcjob 的spec 创建pod
    • 设置schedulerName(默认使用vcjob的schedulerName)
    • 设置annotations信息主要包含groupName将pod 关联到podGroup
    • pod 名称 job名称-task名称-index
    • 设置labels
  • 2 创建podGroup(先创建pod 后创建PodGroup)
    • 设置annotaions信息: 从pod 复制annotations信息
    • 设置OwnerReferences: 关联到pod
    • 设置queue

volcano scheduler

volcano scheduler 是负责pod 调度的组件(在功能上可以对比下k8s原生的调度器kube-scheduler)它由一系列action 和plugin 组成 。action定义了调度各环节中需要执行的动作;plugin根据不同场景提供了action 中算法的具体实现细节。Volcano scheduler具有高度的可扩展性,您可以根据需要实现自己的action和plugin。

Volcano scheduler的工作流程如下:

    1. 客户端提交的Job被scheduler观察到并缓存起来。
    1. 周期性的开启会话,一个调度周期开始。
    1. 将没有被调度的Job发送到会话的待调度队列中。
    1. 遍历所有的待调度Job,按照定义的次序依次执行enqueue、allocate、preempt、reclaim、backfill等动作,为每个Job找到一个最合适的节点。将该Job 绑定到这个节点。action中执行的具体算法逻辑取决于注册的plugin中各函数的实现。
    1. 关闭本次会话。

actions

  • enqueue Enqueue action负责通过一系列的过滤算法筛选出符合要求的待调度任务并将它们送入待调度队列。经过这个action,任务的状态将由pending变为inqueue。
  • allocate Allocate action负责通过一系列的预选和优选算法筛选出最适合的节点。
  • preempt Preempt action负责根据优先级规则为同一队列中高优先级任务执行抢占调度。
  • reclaim Reclaim action负责当一个新的任务进入待调度队列,但集群资源已不能满足该任务所在队列的要求时,根据队列权重回收队列应得资源。
  • backfill backfill action负责将处于pending状态的任务尽可能的调度下去以保证节点资源的最大化利用。

plugins

  • gang gang plugin认为未处于ready状态(包括Binding、Bound、Running、Allocated、Succeed、Pipelined)的任务具有更高的优先级。它会检查假如驱逐某 些任务回收队列部分应得资源后,该任务所属的Job中任务的运行数量是否满足minAvailable的要求,以决定是否执行驱逐动作。
  • conformance conformance plugin认为命名空间kube-system下的任务具有更高的优先级。这些任务不能被抢占。
  • DRF DRF plugin认为占用资源较少的任务具有更高的优先级。它会尝试计算已分配给抢占者和被抢占者的资源总量,并在抢占者资源资源份额更少时触发抢占行为。
  • nodeorder nodeorder plugin通过一系列维度的打分算法,算出针对某个任务时所有的节点的得分情况。得分最高的节点被认为是针对该任务最合适的节点。
  • predicates predictions plugin通过一系列维度的评估算法,决定某个任务是否适合被绑定到某个节点。
  • priority priority plugin用于比较两个job或任务的优先级。它通过比较job.spec.priorityClassName来决定哪个job的优先级更高。对于两个任务,它会依次比较 task.priorityClassName、task.createTime、task.id in order来决定谁的优先级更高。

配置

# kubectl get configmap volcano-scheduler-configmap -nvolcano-system -oyaml 
apiVersion: v1
data:
volcano-scheduler.conf: |
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
  - name: priority
  - name: gang
  - name: conformance
- plugins:
  - name: drf
  - name: predicates
  - name: proportion
  - name: nodeorder
  - name: binpack
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
  {"apiVersion":"v1","data":{"volcano-scheduler.conf":"actions: \"enqueue, allocate, backfill\"\ntiers:\n- plugins:\n  - name: priority\n  - name: gang\n  - name: conformance\n- plugins:\n  - name: drf\n  - name: predicates\n  - name: proportion\n  - name: nodeorder\n  - name: binpack\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"volcano-scheduler-configmap","namespace":"volcano-system"}}
creationTimestamp: "2020-08-15T04:01:02Z"
name: volcano-scheduler-configmap
namespace: volcano-system
resourceVersion: "266"
selfLink: /api/v1/namespaces/volcano-system/configmaps/volcano-scheduler-configmap
uid: 1effe4d6-126c-42d6-a3a4-b811075c30f5

在volcano-scheduler.conf中主要包括actions和tiers两部分。在actions中,使用逗号作为分隔符配置各需要执行的action。需要注意的是,action的配置 顺序就是scheduler的执行顺序。Volcano本身不会对action顺序的合理性进行检查。tiers中配置的plugin列表即为注册到scheduler中的plugin。plugin中 实现的算法将会被action调用。

tensorflow 支持

如下示例为ps worker 模式的训练分布式训练,主要是参照vcjob的格式

apiVersion: batch.volcano.sh/v1alpha1 
kind: Job 
metadata: 
  name: tensorflow-dist-mnist 
spec: 
  minAvailable: 3 
  schedulerName: volcano 
  plugins: 
    env: [] 
    svc: [] 
  policies: 
  - event: PodEvicted 
    action: RestartJob 
  queue: default 
  tasks: 
    - replicas: 1 
      name: ps 
      template: 
      spec: 
        containers: 
          - command: 
              - sh 
              - -c - | 
              PS_HOST=`cat /etc/volcano/ps.host | sed 's/$/&:2222/g' | sed 's/^/"/;s/$/"/' | tr "\n" ","`; 
              WORKER_HOST=`cat /etc/volcano/worker.host | sed 's/$/&:2222/g' | sed 's/^/"/;s/$/"/' | tr "\n" ","`; 
              export TF_CONFIG={\"cluster\":{\"ps\":[${PS_HOST}],\"worker\":[${WORKER_HOST}]},\"task\":{\"type\":\"ps\",\"index\":${VK_TASK_INDEX}},\"environment\":\"cloud\"}; python /var/tf_dist_mnist/dist_mnist.py 
          image: volcanosh/dist-mnist-tf-example:0.0.1 
          name: tensorflow 
          ports: 
            - containerPort: 2222 
              name: tfjob-port 
              resources: {} 
              restartPolicy: Never 
      - replicas: 2 
        name: worker 
        policies: 
        - event: TaskCompleted 
          action: CompleteJob 
          template: 
          spec: 
            containers: 
            - command: 
              - sh 
              - -c 
              - | 
                PS_HOST=`cat /etc/volcano/ps.host | sed 's/$/&:2222/g' | sed 's/^/"/;s/$/"/' | tr "\n" ","`; 
                
                WORKER_HOST=`cat /etc/volcano/worker.host | sed 's/$/&:2222/g' | sed 's/^/"/;s/$/"/' | tr "\n" ","`; 
                export TF_CONFIG={\"cluster\":{\"ps\":[${PS_HOST}],\"worker\":[${WORKER_HOST}]},\"task\":{\"type\":\"worker\",\"index\":${VK_TASK_INDEX}},\"environment\":\"cloud\"}; 
                python /var/tf_dist_mnist/dist_mnist.py 
             image: volcanosh/dist-mnist-tf-example:0.0.1 
             name: tensorflow 
             ports: 
             - containerPort: 2222 
               name: tfjob-port 
               resources: {} 
           restartPolicy: Never

kubeflow 支持

kubeflow 在机器学习、深度学习等场景下基本能力缺失,包括gang-schedule的调度能力,计算人对队列管理、task-topology和gpu 亲和性调度另外,Volcano在原生kubernetes能力基础上对计算任务的批量创建及生命周期管理、fair-share、binpack调度等方面做了增强。Volcano充分解决了上文提到的Kubeflow分布式训练面临的问题。'

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
name: {train_name}  
spec:
# 添加调度器为volcano
schedulerName: volcano
tfReplicaSpecs:
  Ps:
    replicas: {num_ps}
    template:
      metadata:
        annotations:
          sidecar.istio.io/inject: "false"
      spec:
        serviceAccount: default-editor
        containers:
        - name: tensorflow
          command:
          - python
          - /opt/model.py
          - --tf-model-dir={model_dir}
          - --tf-export-dir={export_path}
          - --tf-train-steps={train_steps}
          - --tf-batch-size={batch_size}
          - --tf-learning-rate={learning_rate}
          env:
          - name: S3_ENDPOINT
            value: {s3_endpoint}
          - name: AWS_ENDPOINT_URL
            value: {minio_endpoint}
          - name: AWS_REGION
            value: {minio_region}
          - name: BUCKET_NAME
            value: {mnist_bucket}
          - name: S3_USE_HTTPS
            value: "0"
          - name: S3_VERIFY_SSL
            value: "0"
          - name: AWS_ACCESS_KEY_ID
            value: {minio_username}
          - name: AWS_SECRET_ACCESS_KEY
            value: {minio_key}
          image: {image}
          workingDir: /opt
        restartPolicy: OnFailure
  Chief:
    replicas: 1
    template:
      metadata:
        annotations:
          sidecar.istio.io/inject: "false"
      spec:
        serviceAccount: default-editor
        containers:
        - name: tensorflow
          command:
          - python
          - /opt/model.py
          - --tf-model-dir={model_dir}
          - --tf-export-dir={export_path}
          - --tf-train-steps={train_steps}
          - --tf-batch-size={batch_size}
          - --tf-learning-rate={learning_rate}
          env:
          - name: S3_ENDPOINT
            value: {s3_endpoint}
          - name: AWS_ENDPOINT_URL
            value: {minio_endpoint}
          - name: AWS_REGION
            value: {minio_region}
          - name: BUCKET_NAME
            value: {mnist_bucket}
          - name: S3_USE_HTTPS
            value: "0"
          - name: S3_VERIFY_SSL
            value: "0"
          - name: AWS_ACCESS_KEY_ID
            value: {minio_username}
          - name: AWS_SECRET_ACCESS_KEY
            value: {minio_key}
          image: {image}
          workingDir: /opt
        restartPolicy: OnFailure
  Worker:
    replicas: 1
    template:
      metadata:
        annotations:
          sidecar.istio.io/inject: "false"
      spec:
        serviceAccount: default-editor
        containers:
        - name: tensorflow
          command:
          - python
          - /opt/model.py
          - --tf-model-dir={model_dir}
          - --tf-export-dir={export_path}
          - --tf-train-steps={train_steps}
          - --tf-batch-size={batch_size}
          - --tf-learning-rate={learning_rate}
          env:
          - name: S3_ENDPOINT
            value: {s3_endpoint}
          - name: AWS_ENDPOINT_URL
            value: {minio_endpoint}
          - name: AWS_REGION
            value: {minio_region}
          - name: BUCKET_NAME
            value: {mnist_bucket}
          - name: S3_USE_HTTPS
            value: "0"
          - name: S3_VERIFY_SSL
            value: "0"
          - name: AWS_ACCESS_KEY_ID
            value: {minio_username}
          - name: AWS_SECRET_ACCESS_KEY
            value: {minio_key}
          image: {image}
          workingDir: /opt
        restartPolicy: OnFailure

后续

欢迎大家订阅本专栏,后续会持续不断更新云原生&云计算相关的内容!!