🎉在调度的花园里面挖呀挖

阿里云教程2个月前发布
13 0 0

上文使用 koordinator 演示 gang-scheduling 和 binpack 调度,已经生效。

4个2卡 Pod 龟缩在一个节点,另外一个2卡 Pod 被挤到另外一个节点(每节点上虚拟 gpu:8卡)。

此时我们再尝试申请8卡作业,pod 会 Pending 状态 。但一旦节点有资源,pod 就会自动进入 Running 状态 。

这就是 resource.requests/limits 软调度的效果。

1. resource.requests/limits 软调度

上面的调度主要由 requests 配置来约束。

requests :是“承诺资源”,kube-scheduler 将 re #后端quests cpu:1 的 pod 调度到某个 node,就相当于从该 node 资源池上划走了有一部分资源,这1核会被预定,不再承诺给其他 pod,即使你这个 pod 只用了500m 核。

limits : 是资源使用的上限,是由 kubelet 来强制执行。

C 2. k8s原生配额ResourceQuota: 硬隔离

当多个团队共享 k8s 集群节点资源时,会有某一租户霸占大量资源的可能性。

资源配额就是用来解决这个问题:

资源配额 作用在命名空间上(命名空间天生就是多租户概念的载体),限制了该租户(命名空间)能创建的资源对象(+基础设施资源)的上限 ,这个限制是通过 api server 在资源对象层面做到的。

ResourceQuota 相当于框定某一类资源的可用上限,有“资源类型”、”配额作用域“ 等过滤资源的选项,具体请参见 ResouceQouta 官方 。

下面给出一个包含[基础设施资源、扩展资源、资源对象]的 ResourceQuota:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi   # 需求总量

limits.cpu: "2" limits.memory: 2Gi # 限额总量

requests.example.com/dongle: 2 pods: "4" replicationcontrollers: "20" secrets: "10" services: "10"

由于扩展资源不可超量分配,故没有必要为扩展资源同时指定requests和limits配置,只需指定requests.xxxx 即可。

由于配额的准入是在 apiserver 资源对象层面,所以当配额不足,不会产生 pod 处于 pending 的现象,kubectl 命令会给出报错:pods “quota-mem-cpu-demo-2” is forbidden: exceeded quota ,这点与 resource.requests/limits 软调度不同。

提示:

ResourceQuota 与集群资源总量是完全独立的。它们通过绝对的单位来配置。

所以,为集群添加节点时,k8s资源配额不会自动赋予每个命名空间消耗更多资源的能力。

3. kueue 任务队列

上文 k8s 原生 resourceQuota 是命名空间级别的硬资源限制,“它只负责限制,不负责调度”。

在企业级多租户云环境中,为了①高效②灵活 利用集群资源,需要“协调和调度”的能力。

kueue 这种任务队列就是这个作用,它不谋求替代 k8s 原生组件作用,工作在 k8s 原生调度器之上。

kueue 是 k8s 上管理资源池配额和管控 job 消费资源池配额的任务队列系统,kueue 决定了 job 什么时候应该等待,什么时候被准入,什么时候 job 可以被抢占。

什么时候使用 kueue?

① 需要弹性计算资源(可随时扩缩容)

② 计算资源是异构资源(架构、可用性、价格等因素)

这里我用自己的想法协助大家理解这①②点:

kueue 中构建的 clusterQueue 引用的资源池配额是逻辑配额,资源可以跨队列流动 (借用、抢占),虽然和 k8s 原生配额一样都不与硬件资源直接挂钩,但很明显相比 k8s 原生配额更具弹性。

同构资源指的是所有计算节点/设备规格一样,异构资源是指计算节点/设备规格/特性不同。

为什么会存在异构资源?

由业务需求驱动产生的不同设备形态(AI/ML工作负载、边缘计算、科学计算等要求的设备种类、设备规格、设备侧重都不同), 衍生出设备价格也不同。

— 下图是 kueue 的作用原理。

任务满足资源池配额,被准入后:

  • kueue修改job的 suspend: false, 放行job
  • kueue 向job注入nodeselector注解,将本次资源风味相关的信息给到job,用于后续调度!!!

3.1 资源高度抽象

上面的 resource.requests/limits 和 k8s 原生 resourceQuota 都没能跳脱 worker 节点资源的概念。

kueue 将 worker 节点上的资源抽象成由特定资源风味(ResourceFlavor)表征的资源池,框定了某一类含有特定资源类型/规格的节点。

apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
  name: "default-flavor"
spec:
  nodeLabels:
    gpu: h20 #  框定了含h20的节点

上面名为 default-flavor 的资源风味 框定了带有 gpu:h20 标签的节点

如果是同构资源,你也可以定义empty resource flavor。

— 然后基于框定的资源池给某个任务队列分发资源配额。

3.2 资源池的配额 nominalQuota

ClusterQueue 默认是集群级别的对象,定义了集群中某类资源风味的配额。

下面为 cluster-queue 的全局队列(依托于“default-flavor”资源池)定义了使用配额, 其中为稀缺资源 example.com/dongle 约束10卡。

kubectl label nodes minikube-m02/minikube-m03 gpu=h20 假设节点有这样的标签

命名空间 team-a 的 localQueue 引用了该 clusterQueue。

apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
  name: default-flavor ##  对同构资源,定义empty资源风味

spec: nodeLabels: gpu: h20 # 框定了含 h20标签的节点

---

kind: ClusterQueue metadata: name: "cluster-queue" spec: namespaceSelector: {} # match all.

resourceGroups: - coveredResources: ["cpu", "memory", "pods"] flavors: - name: "default-flavor" resources: - name: "cpu" nominalQuota: 10 - name: "memory" nominalQuota: 10Gi - name: "example.com/dongle" nominalQuota: 10 ---

kind: LocalQueue metadata: namespace: team-a name: team-a-queue spec: clusterQueue: cluster-queue

clusterQueue 与 localQueue 的引用关系实现了共享全局资源池的理念。

形成的对象映射关系如下:

浅绿色是 kueue 产生的对象,浅蓝色是用户实际提交的批处理任务。

— 下面用一个 k8s 原生 job 来演示 kueue 的工作表现。

apiVersion: batch/v1
kind: Job
metadata:
  name: pi3
  labels:
    kueue.x-k8s.io/queue-name: team-a-queue
spec:
  parallelism: 3 # 并行执行次数,默认为1

completions: 3 # 完成次数,默认为 parallelism 的值

# suspend: true

template: spec: containers: - name: pi image: perl imagePullPolicy: IfNotPresent command: [ "perl", "-Mbignum=bpi", "-wle", "print bpi(5000)" ] resources: requests: cpu: "1" memory: "200Mi" example.com/dongle: "2" limits: cpu: "1" memory: "200Mi" example.com/dongle: "2" restartPolicy: Never

# Job 代表一次性任务,运行完成到停止,它将π计算到5000个位置并将其打印出来。完成大约需要60秒。 之后pod状态是 Completed
  • 这里最重大的是配置是job中的”suspend:true”, job应该以挂起状态被创建 ,由kueue来决定何时启动, 不需要这个suspend:true配置,加上了kueue.x-k8s.io/queue-name: team-a-queue就能受kueue管控。
  • 在原生job标签关联localqueue

提交第一个任务,3个 Pod 占用了6卡;再立刻启动同样配置的第二个任务,受 localqueue 中 nominalQuota: 10 的约束,任务2会 pending,

等待任务1执行完,释放了 example.com/dongle 资源,最后进入 runing 状态跑完任务,分别查看任务1和任务2的准入事件:

最后我们看下业务 job 被 kueue 修改的结果。

— kueue 还有许多特性 ,读者自行审阅,修行在个人。

① 核心的 clusterqueue 默认的排队策略是:BestEffortFIFO:先按优先级排序,再按照创建时间;未被准入的旧任务不会影响后续能被准入的新来任务。

② 支持队列组 Cohort,可实现资源弹性借用

③ 注意队列组、clusterqueue 与资源风味的1对多的关系。

在企业级多租户、强资源生产隔离、计费要求的背景下,一般是独立clusterQueue 与 共享ClusterQueue结合的做法。

4. 总结

今天主要在调度这个花园里面挖呀挖,更准确的是聚焦在“准入”这个动作上展开思路。

k8s 原生资源配额的目的:不是为了优化调度,而是在多租户背景下,约束资源的硬使用边界。ResourceQuota 框定了命名空间中某些资源的产生上限;

kueue 资源池的配额约束了某些细粒度要求的资源池的逻辑边界,通过 resourceFalvor 抽象出资源池的概念,kueue 通过”排队“这个概念细化了准入这个动作,在 kube-scheduler 工作前管控了批处理任务的调度。

  • juejin.cn/post/732735…
  • www.infracloud.io/blogs/batch…
© 版权声明

相关文章

暂无评论

none
暂无评论...