1. 可插拔的调度框架
k8s以可插拔的插件方式实现了调度能力,插件可以实现多个扩展点接口来执行更复杂或有状态的任务。 
默认的调度插件 给出了他们实现的扩展点、默认的打分策略。
在AI工程中要重点关注默认调度器带来的副作用:
① 默认的调度器是以Pod为调度单元进行依次调度,不会考虑Pod之间的相互关系。
② NodeResourcesFit插件默认启用的最少资源分配策略: 为避免单点故障,调度时打散了Pod。
2. AIops
在AI工程化中,我们要面对2个现实:
① 很多数据计算类的离线作业(包括ML训练)具有组合调度 的特点,即要求所有的子任务都能够成功创建后,整个作业才能正常运行。如果只有部分子任务启动的话,启动的子任务将持续等待剩余的子任务被调度(甚至死锁), 这浪费了节点资源,这正是Gang Scheduling的场景。
② 在ML训练和推理时,节点上的GPU是最昂贵的算力资源,默认的LeastAllocated调度策略打散了Pod,很容易在节点上产生GPU碎片 , 影响了资源的利用率,这时就要求binpack调度。

gang-scheduling 调度
gang-scheduling 是coschedule协同调度中的严格协同调度,(批处理作业完全不允许有"作业碎片"存在), 也就是"All or Nothing"。
binpack调度
顾名思义就是:装箱,在k8s调度中意味:避免资源碎片化,尽可能把节点空余出来, 最终最大化的利用节点资源。
原生NodeResourcesFit调度插件
官网所述,默认调度器的NodeResourcesFit有三个打分策略
- LeastAllocated(默认),这种策略的目的是将工作量平均分配到各个节点,防止任何一个节点超负荷工作
- MostAllocated
- RequestedToCapacityRatio:根据节点资源与 pod 需求的匹配程度来决定哪个节点

同一个k8s集群,为了让在线任务和离线任务的不同调度需求能优雅落地,项目引入了koordinator 调度器。
Koordinator 是一个基于 QoS 的 Kubernetes 混合工作负载调度系统。它旨在提高对延迟敏感的工作负载和批处理作业的运行时效率和可靠性,简化与资源相关的配置调整的复杂性,并增加 Pod 部署密度以提高资源利用率。
3.koordinator一把梭
3.1 准备环境/背景
① 本次使用minikube:minikube start --cni=auto --nodes=3 --addons=metrics-server
binpack依赖metrics-server插件判断节点资源利用情况,每个节点默认2核心cpu/2200MB内存。
对control-plane添加污点
kubectl taint nodes minikube node-role.kubernetes.io/control-plane=true:NoSchedule, 只关注两个worker节点的调度。
② 本次环境无gpu硬件, 本次在k8s上安装虚拟资源:example.com/dongle
下面对2台worker分别构造了8卡虚拟资源
json
kubectl proxy
# 每个节点申请过8卡机器
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1dongle", "value": "8"}]' \
http://127.00.1:8001/api/v1/nodes/minikube-m02/status
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1dongle", "value": "8"}]' \
http://127.0.0.1:8001/api/v1/nodes/minikube-m03/status
③ 启动2副本pod,查看默认调度结果
yaml
# deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vgpu-job # 本次deploy的资源名称
spec:
replicas: 2 # 启动2副本
selector:
matchLabels:
app: vgpu-job
template:
metadata:
labels:
app: vgpu-job
spec:
containers:
- image: busybox
imagePullPolicy: IfNotPresent
name: curlimage
command:
- sleep
- 365d
resources:
limits:
cpu: 40m
memory: 40Mi
example.com/dongle: 2
requests:
cpu: 40m
memory: 40Mi
example.com/dongle: 2
每个pod申请2卡example.com/dongle资源,默认调度器的输出:启动的2pod均匀分布到2个worker节点
按照上面的理论,再申请8卡资源会受限。
本地为不影响原生调度器的能力,将koordinator作为第二套调度器。
3.2 gang scheduling
koordinator支持gang scheduling 可使用crd或者注解的方式,这里我们使用crd:
该任务要求3pod,形成组调度:
yaml
# pg.deploy.yaml
apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: PodGroup
metadata:
name: gang-example
namespace: default
spec:
scheduleTimeoutSeconds: 10
minMember: 3
修改业务manifest文件,指定koord-scheduler调度器和gang scheduling规格对象标签。

3.3 binpack
koordinator有NodeResourcesFitPlus插件 来做binpack调度。
本例模拟节点上GPU的分配,希望将应用都binpack到一个节点上,而不是均分到多个节点,那么对于资源example.com/dongle的权重要设高。
修改koordinator的NodeResourcesFitPlus插件配置:
本次拉取helm chart到本地,对
template/koord-scheduler-config.yaml新增了插件配置,切记不要用红框内容完全替换文件,而是拿下面的配置patch到文件。helm upgrade koordinator ./
kubectl rollout restart deploy/koord-scheduler -n koordinator-system

插件NodeResourcesFitPlus两种策略的评分计算方法和效果如下:

最终的节点评分可以按如下方式计算:
finalScoreNode = [(weight1 * resource1) + (weight2 * resource2) + ... + (weightN* resourceN)] /(weight1+weight2+ ... +weightN)
那么节点的打分:
nodeScore = (20* MostAllocatedAlg + 2* LeastAllocatedAlg + 1* LeastAllocatedAlg )/(20+2+1),
粗略想象在第一个Pod被调度到节点A之后, 调度第二个Pod时, 节点A的MostAllocatedAlg = 2/6, 而节点B的MostAllocatedAlg = 2/8, 20的权重值在分子端占据更大因素,故第二个Pod也会更倾向于调度到节点A, 这样算法就做到了binpack。
到这里条件就都就绪了: 2台节点(均有8卡资源),每个Pod占用2卡资源,要求3 Pod形成组调度,且要求是binpack调度。

验证有效,目前3Pod占用了minikube-m03节点上6卡资源,继续扩容到5 Pod,理论上会有一个Pod调度到另外一个worker节点。

总结
本文回顾了在云原生背景下AIops 与原生k8s调度器的格格不入。
演示了开源的koordinator调度器是如何支持AIOps场景下的gang-scheduling 和binpack调度。
gang-scheduling 聚焦于k8s集群中上层批处理作业要求的Pod协作能力。
binpack 聚焦于k8s集群中作为基础设施节点的资源利用率,binpack会避免打散,尽量留出空闲节点。
本文文字和制图均为原创,特别是binpack的验证耗费了博主1个星期的倒腾时间, 期待一键三连,交个朋友, 35+报团不迷路。🎨🎨