QuXiao's Blog

Life && Tech && Thoughts

GPU 场景下的 k8s 调度器调研

Written on

背景

当前团队内部的机器学习平台,主要是为团队内部的算法同事提供模型训练的环境。算法同事除了之前准备数据集、训练代码以外,首先要做的事情就是向平台申请资源,其中 GPU 是一个相对稀缺的资源,能否比较高效的使用 GPU,直接影响了算法工程师的效率、以及团队运营成本,不过目前的资源调用策略还存在一些问题。

默认调度策略

Kubernetes 默认调度策略优先考虑负载均衡, 简而言之即在调度一个 pod 时, 选择可用 node 中剩余资源较多的 node, 将该 pod 分配至该 node。 例如某集群有 3 台 GPU node:

  1. 时间 t0 时集群有 3 个 GPU 节点, 剩余 GPU 数量如 t0 列所示,
  2. t1 时, pod1 需要使用两张 GPU, 按照负载均衡策略, 会被优先调度至 node3, 那么调度后各 node 剩余 GPU 数量为 t1 列
  3. t2 时, pod2 需要使用 8 张 GPU, 则无法调度该 pod, 只能等待集群中某个 node 有 8 张 GPU 时才能调度成功.
            t0      t1
node1       3       3
node2       6       6
node3       8       6   <- 导致 8 卡的 pod 无法调度到 node3 节点

负载均衡只是默认调度策略中的一种, Kubernetes 调度器实际运行时会考虑多种因素, 综合评价各 node 调度优先顺序, 但负载均衡策略对调度结果影响很大。

负载均衡策略的问题

实践中, 使用上述调度策略主要存在两个问题:

  1. 往往多数 node 剩余 1-2 张 GPU 长期无法有效使用. 浪费了宝贵的 GPU 资源
  2. 由于集群 GPU 需求量一直较高, 很少有某一 node 所有 GPU 都空闲的时候, 而需要使用多张 GPU 的训练任务可能长期无法调度, 影响算法工程师工作

自研的 GPU 调度策略

针对使用 GPU 资源的 pod, AVA 使用相反的调度策略: 选择满足 pod 需求且剩余 GPU 最少的 node

(PS:除了这个条件,其实更高的要求是要考虑到 GPU 的拓扑矩阵的关系,这里先不考虑)

仍以前述集群举例:

  1. t0 时, 集群剩余资源与前述场景相同, 为 t0 列
  2. t1 时, pod1 申请 2 张 GPU, 调度至 node1, 此时剩余 GPU 为 t1 列
  3. t2 时, pod2 申请 8 张 GPU, 成功调度至 node3, 此时剩余 GPU 为 t2
            t0      t1  t2
node1       3       1   1
node2       6       6   6
node3       8       8   0   <- 8 卡的调度请求得到了满足

调度策略解决了默认调策略的两个问题, 明显提高 GPU 资源利用率和多 GPU 任务调度的成功率。

k8s scheduler 二次开发

源码

源码仓库: fork from https://github.com/kubernetes/kubernetes 开发分支:v1.7-dev

编译流程

下载

git clone https://github.com/xxx/kubernetes

编译

# 单独编译 kubernetes 下的组件
KUBE_BUILD_PLATFORMS=linux/amd64 make WHAT=plugin/cmd/kube-scheduler

# 编译 scheduler && build 成镜像
➜  kubernetes git:(ava-scheduler-v1) ✗ ./build/scheduler.sh
...
+++ [0402 14:36:12] Building the toolchain targets:
    k8s.io/kubernetes/hack/cmd/teststale
    k8s.io/kubernetes/vendor/github.com/jteeuwen/go-bindata/go-bindata
+++ [0402 14:36:12] Generating bindata:
    test/e2e/generated/gobindata_util.go
~/git_rep/github.com/quxiao/kubernetes ~/git_rep/github.com/quxiao/kubernetes/test/e2e/generated
~/git_rep/github.com/quxiao/kubernetes/test/e2e/generated
+++ [0402 14:36:13] Building go targets for linux/amd64:
    plugin/cmd/kube-scheduler
Sending build context to Docker daemon  1.527GB
Step 1/2 : FROM busybox
---> f6e427c148a7
Step 2/2 : ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler
---> Using cache
---> 003fb4835e3b
Successfully built 003fb4835e3b
...
Successfully tagged ava-kube-scheduler:latest

推送至 docker registry

# 编译 scheduler && build 成镜像 && push 到 registry
docker login <some-docker-registry>

➜  kubernetes git:(ava-scheduler-v1) ✗ ./build/scheduler.sh --push
+++ [0402 14:42:56] Building the toolchain targets:
    k8s.io/kubernetes/hack/cmd/teststale
    k8s.io/kubernetes/vendor/github.com/jteeuwen/go-bindata/go-bindata
+++ [0402 14:42:56] Generating bindata:
    test/e2e/generated/gobindata_util.go
~/git_rep/github.com/quxiao/kubernetes ~/git_rep/github.com/quxiao/kubernetes/test/e2e/generated
~/git_rep/github.com/quxiao/kubernetes/test/e2e/generated
+++ [0402 14:42:58] Building go targets for linux/amd64:
    plugin/cmd/kube-scheduler
Sending build context to Docker daemon  1.527GB
Step 1/2 : FROM busybox
---> f6e427c148a7
Step 2/2 : ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler
---> Using cache
---> 003fb4835e3b
Successfully built 003fb4835e3b
Successfully tagged ava-kube-scheduler:latest
The push refers to repository [some-docker-registry/ava-kube-scheduler]
...
latest: digest: sha256:xxxx size: 739

在 k8s 集群中添加 sceduler

ava-scheduler.yml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
labels:
    component: scheduler
    tier: control-plane
name: ava-scheduler
namespace: kube-system
spec:
replicas: 1
template:
    metadata:
    labels:
        component: scheduler
        tier: control-plane
        version: second
    spec:
    imagePullSecrets:
    - name: xxx
    containers:
    - command:
        - /usr/local/bin/kube-scheduler
        - --address=0.0.0.0
        - --leader-elect=false
        - --scheduler-name=ava-scheduler
        - --algorithm-provider=AVAProvider
        image: <some-docker-registry>/ava-kube-scheduler
        imagePullPolicy: Always
        livenessProbe:
        httpGet:
            path: /healthz
            port: 10251
        initialDelaySeconds: 15
        name: kube-second-scheduler
        readinessProbe:
        httpGet:
            path: /healthz
            port: 10251
        resources:
        requests:
            cpu: '0.1'
        securityContext:
        privileged: false
        volumeMounts: []
    hostNetwork: false
    hostPID: false
    volumes: []

新建 scheduler

kubectl create -f ava-scheduler.yml

使用 ava-scheduler 调度 pod

pod 样例

pod-with-ava-scheduler.yml

apiVersion: v1
kind: Pod
metadata:
name: pod-with-ava-scheduler
labels:
    name: multischeduler-example
spec:
schedulerName: ava-scheduler
containers:
- name: pod-with-ava-schduler
    image: ubuntu
    command: ["sleep", "infinity"]
    resources:
    requests:
        cpu: 0.2
        memory: 128Mi
    limits:
        cpu: 0.8
        memory: 256Mi

验证

kubectl describe pod pod-with-ava-scheduler
comments powered by Disqus