跳到主要内容

· 18 分钟阅读
Rougang Han
Jianyu Wang

背景

Koordinator 是一个开源项目,基于阿里巴巴在容器调度领域的多年经验积累而生,自发布以来经历了个版本的迭代,持续不断地为 Kubernetes 生态系统带来创新和增强。旨在提供混部工作负载编排、混部资源调度、混部资源隔离和混部性能调优的综合解决方案,帮助用户优化容器性能,并提升集群资源使用效率,管理和优化延迟敏感型工作负载和批处理作业的运行效率和可靠性。

在此,我们向大家宣布 Koordinator v1.5.0 版本的发布,这是自2022年4月正式开源以来,Koordinator 迭代发布的第13个大版本。在2年多的时间里,Koordinator 很荣幸吸引了包括阿里巴巴、蚂蚁科技、Intel、小红书、小米、爱奇艺、360、有赞等众多企业的优秀工程师参与,贡献了众多的想法、代码和场景。 在 v1.5.0 版本中,Koordinator 带来了众多的功能优化,新增了 Pod 级别 NUMA 对齐策略、网络 QoS、Core Scheduling 等功能支持。

此外,Koordinator 项目近期通过了 CNCF TOC 投票,顺利被 CNCF 基金会接受为 Sandbox 项目,CNCF 全称 Cloud Native Computing Foundation(云原生计算基金会),旨在为云原生软件构建可持续发展的生态系统,服务于厂商中立的快速增长的开源项目,如 Kubernetes、Prometheus 等。

koordinator-aboard-cncf-sandbox-img 投票地址:https://github.com/cncf/sandbox/issues/51

版本功能特性解读

Pod级别NUMA对齐策略

在过去的 v1.4.0 版本中,Koordinator 支持了用户在节点上加标签为不同节点配置不同的 NUMA 对齐策略。然而这意味着用户需要提前将集群中的节点拆分为不同 NUMA 对齐策略的节点池,引入了额外管理节点的负担。 Koordinator 在 v1.5.0 中引入了 Pod 级别的 NUMA 对齐策略来解决该问题。举例,我们可以为 pod-1 设置 SingleNUMANode

apiVersion: v1
kind: Pod
metadata:
name: pod-1
annotations:
scheduling.koordinator.sh/numa-topology-spec: |-
{
"numaTopologyPolicy": "SingleNUMANode",
}
spec:
containers:
- name: container-1
resources:
requests:
cpu: '1'
limits:
cpu: '1'

引入 Pod 级别的 NUMA 策略后,必然会出现不同 NUMA 策略的节点部署在同一个 NUMA Node 的情况。 举例,node-1 有两个 NUMA Node,pod-1 采用 SingleNUMANode 策略使用了 numa-0pod-2 采用 Restricted 策略使用了 numa-0numa-1。 由于 Pod 设置 Limit 只能限制 Pod 在整机维度最多能使用多少资源,无法限制在某个 NUMA 节点下最多能使用多少 NUMA 资源,所以 pod-2numa-0 使用的资源可能超过调度器分配给它的资源量。这时候 pod-2pod-1numa-0 上存在资源竞争。 为了解决上述问题,Koordinator 支持用户为 SingleNUMANode 的 Pod 配置独占策略。举例,我们可以配置 Pod 为 SingleNUMANode 且不与跨 NUMA 共存在一个机器上:

apiVersion: v1
kind: Pod
metadata:
name: pod-1
annotations:
scheduling.koordinator.sh/numa-topology-spec: |-
{
"numaTopologyPolicy": "SingleNUMANode",
"singleNUMANodeExclusive": "Required", # Required or Preferred
}
spec:
containers:
- name: container-1
resources:
requests:
cpu: '1'
limits:
cpu: '1'

另外,Pod 级别的 NUMA 策略的引入并不意味着废弃 Node 级别的 NUMA 策略,而是相互兼容的。 因此,如果 Pod 和节点上的策略不同,Pod 将不会被调度到该节点上;如果节点上的策略为 "", 则表示该节点能够放置任何 Pod;如果 Pod 上的策略为 "",则表示 Pod 可以调度到任何节点上。

SingleNUMANode nodeRestricted nodeBestEffort node
SingleNUMANode pod[✓][x][x]
Restricted pod[x][✓][x]
BestEffort pod[x][x][✓]
""[✓][✓][✓]

关于 Pod 级别 NUMA 对齐策略的更多信息,请见 Proposal: Pod-level NUMA Policy

Terway网络QoS

在 v1.5.0 版本中,Koordinator 联动 Terway 社区提供了网络 QoS 能力。 Terway QoS 的诞生是为了解决混部场景下的网络带宽争抢问题,它支持按单个 Pod 或 QoS 类型进行带宽限制,与其他方案相比具备以下优势:

  1. 支持按业务类型限制带宽,适用于多种业务混部的场景。
  2. 支持动态调整 Pod 带宽限制。
  3. 提供整机带宽限制,支持多网卡,支持容器网络和 HostNetwork Pod 的带宽限制。

Terway QoS 包括3种网络带宽的优先级,对应的 Koordinator 默认 QoS 映射如下:

Koordinator QoSKubernetes QoSTerway Net QoS
SYSTEM--L0
LSEGuaranteedL1
LSRGuaranteedL1
LSGuaranteed/BurstableL1
BEBestEffortL2

在混部场景中,我们希望在线业务具有最大的带宽保障,以避免争抢;在空闲时,离线业务也可以充分利用所有带宽资源。

因此,用户可以为业务流量定义为3个优先级,从高到低依次为:L0、L1、L2。我们定义争用场景为:当 L0+L1+L2 的总流量超过整机带宽时。 L0 的最大带宽根据 L1 和 L2 的实时流量动态调整。它可以高至整机带宽,低至“整机带宽 - L1 最小带宽 - L2 最小带宽”。在任何情况下,L1 和 L2 的带宽都不会超过各自的上限。在争用场景中,L1 和 L2 的带宽不会低于各自的下限。在争用场景中,带宽将按 L2、L1 和 L0 的顺序进行限制。由于 Terway QoS 只有三个优先级,因此只能设置 LS 和 BE 的全机带宽限制,其余L0部分根据整机的带宽上限计算。

下面是一个配置示例:

# unit: bps
resource-qos-config: |
{
"clusterStrategy": {
"policies": {"netQOSPolicy":"terway-qos"},
"lsClass": {
"networkQOS": {
"enable": true,
"ingressRequest": "50M",
"ingressLimit": "100M",
"egressRequest": "50M",
"egressLimit": "100M"
}
},
"beClass": {
"networkQOS": {
"enable": true,
"ingressRequest": "10M",
"ingressLimit": "200M",
"egressRequest": "10M",
"egressLimit": "200M"
}
}
}
}
system-config: |-
{
"clusterStrategy": {
"totalNetworkBandwidth": "600M"
}
}

此外,网络 QoS 能力也支持 Pod 维度的带宽限制,采用以下 annotations 协议:

KeyValue
koordinator.sh/networkQOS'{"IngressLimit": "10M", "EgressLimit": "20M"}'

关于网络 QoS 能力的更多信息,请见 Network Bandwidth Limitation Using Terway QoSTerway 社区。

Core Scheduling

在 v1.5.0 版本中,Koordinator 提供了容器维度的 Core Scheduling 能力,用于多租户场景下降低侧信道(Side Channel Attack)攻击风险,也可以作为 CPU QoS 能力增强混部隔离。

Linux Core Scheduling 支持在用户态定义可以共享物理核的任务分组。属于同一分组的任务将赋予相同的 cookie 作为标识,同一物理核(SMT 维度)在同一时刻只会运行一种 cookie 的任务。通过将这种机制应用到安全方面或性能方面,我们可以做到以下事情:

  1. 对不同租户的任务进行物理核维度的隔离。
  2. 避免离线任务争抢在线服务的物理资源。

Koordinator 使能内核的 Core Scheduling 机制,实现容器维度的分组隔离策略,最终形成了以下两种能力:

  1. Pod 运行时物理核隔离:对 Pods 进行分组,不同分组的 Pods 不能同时共享物理核,保障多租户隔离。
  2. 下一代 CPU QoS 策略:作为 Group Identity 机制以外,兼顾安全的 CPU QoS 能力。

Pod运行时物理核隔离

Koordinator 提供 Pod Label 协议,标识 Pod 的 Core Scheduling 分组。

KeyValue
koordinator.sh/coreSchedulingGroup"xxx-group"

不同分组的 Pods 运行时在物理核层面互斥,可以规避了一些物理核、L1 cache、L2 cache 维度的侧信道攻击,适用于多租户场景。

container-core-scheduling-img

区别于绑核调度,Pod 运行的物理核范围并不固定,同一物理核在不同时刻可能运行着不同分组的 Pods,物理核资源可以被分时复用。

下一代CPU QoS策略

Koordinator 基于 Anolis OS 内核提供的 Core Scheduling 和 CGroup Idle 机制,构建了新的 CPU QoS 策略。

  • BE 容器启用 CGroup Idle 特性,最小化调度权重和优先级。
  • LSR/LS 容器启用 Core Scheduling 特性,支持驱逐物理核上同分组的 BE 任务。

用户可以通过在 slo-controller-config 中指定 cpuPolicy="coreSched" 来启用该策略。

# Example of the slo-controller-config ConfigMap.
apiVersion: v1
kind: ConfigMap
metadata:
name: slo-controller-config
namespace: koordinator-system
data:
resource-qos-config: |
{
"clusterStrategy": {
"policies": {
"cpuPolicy": "coreSched"
},
"lsClass": {
"cpuQOS": {
"enable": true,
"coreExpeller": true,
"schedIdle": 0
}
},
"beClass": {
"cpuQOS": {
"enable": true,
"coreExpeller": false,
"schedIdle": 1
}
}
}
}

关于 Core Scheduling 能力的更多信息,请见 CPU QoS

其他功能

除了上述新功能特性以外,Koordinator v1.5.0 版本还包含了以下一系列的功能增强和稳定性优化:

  1. 功能增强:Reservation Restricted 模式下支持通过 Annotation 控制哪些资源严格遵循 Restricted 语义;将 NUMA 对齐策略 Restricted 的语义跟上游对齐;Coscheduling 实现完全公平的调度队列排队规则,确保同一个 GangGroup 的 Pod 一起出队,不同 Gang 以及裸 Pod 之间按照上次调度时间排队;NRI 模式支持重连机制;koordlet 优化监控指标分类,增加性能指标;BlkioReconcile 配置能力增强。
  2. BugFixes:修复 koordlet CPU 压制功能的内存泄露问题;修复 runtimeproxy 的 panic 问题;修正 CPICollector、BECPUEvict、CPUBurst 模块计算逻辑。
  3. 环境适配:所有组件升级到 K8S 1.28;koordlet 支持非 CUDA 镜像的部署;koordlet 适配 kubelet 1.28 配置,优化 cpu manager 兼容逻辑;适配 cri-o 运行时。
  4. 重构优化:koordlet 优化 Resctrl 更新逻辑;优化单机驱逐接口逻辑;优化节点 GPU 资源和卡型号同步逻辑;优化 Batch 账本计算逻辑。
  5. CI/CD:修复多个 flaky tests。

通过 v1.5.0 Release 页面,可以看到更多包含在 v1.5.0 版本的新增功能。

欢迎社区新成员

Koordinator 是一个开放的社区,在 v1.5.0 版本中,共有 10 位新的开发者参与到了 Koordinator 的建设,他们是 @georgexiang、@googs1025、@l1b0k、@ls-2018、@PeterChg、@sjtufl、@testwill、@yangfeiyu20102011、@zhifanggao、@zwForrest。

Koordinator 社区目前有许多来自不同行业的企业级贡献者,其中不少同学成为了项目的 Maintainer 和 Member,近期新加入的 Maintainer 成员有 @songzh215、@j4ckstraw、@lucming、@kangclzjc。 在此感谢各位新同学的积极参与和老同学的持续投入,也欢迎更多优秀的同学参与到 Koordinator 社区~

未来计划

在接下来的版本中,Koordinator 目前规划了以下工作:

  • 调度性能优化:调度性能是调度器能否应对大规模集群的关键指标。接下来的版本中,Koordinator 将给出基本的压测环境搭建手册以及常见压测场景,并着手提升 Koord-Scheduler 的调度性能。
  • 设备联合分配。在 AI 大模型分布式训练场景中,不同机器 GPU 之间通常需要通过高性能网卡相互通信,且 GPU 和高性能网卡就近分配的时候性能更好。Koordinator 正在推进支持多种异构资源的联合分配,目前已经在协议上和调度器分配逻辑上支持联合分配;单机侧关于网卡资源的上报逻辑正在探索中。
  • Job 粒度的抢占实现:在大规模共享集群中,有些配额可能非常繁忙,有些配额可能处于空闲状态。在 ElasticQuota 插件中,我们已经支持从空闲的 ElasticQuota 借用资源。但是,当被借用的 ElasticQuota 关联的 Pod 需要取回资源时,并没有考虑到 Job 粒度。对于属于同一个 Job 的 Pod,我们需要以 Job 粒度进行抢占,以确保获得足够的资源来满足 Job 的需求,提高资源交付效率。之前社区已经通过了 Proposal,接下来 Koordinator 将推进该提案的实现。
  • 负载感知调度针对 inflight pods 的优化:负载感知调度当前基于节点真实利用率进行多个维度的调度过滤和打分,可以用于优化节点利用率的分布,降低 Pod 调度到高负载节点的风险;不过,由于节点利用率视图存在同步延迟,各个阶段的 inflight pods 可能影响利用率信息的准确性,接下来,负载感知调度将完善这部分优化,更充分地规避调度到过载节点,优化节点间的负载均衡度。
  • 细粒度的末级缓存及内存带宽隔离策略:容器间争抢共享的末级缓存和内存带宽资源,可能导致应用访存性能的抖动;当前 Koordinator 提供了 ResctrlQoS 能力,在满足隔离分组的数量限制的前提下,用来对 QoS 维度隔离末级缓存和内存带宽资源,降低离线负载对在线应用的干扰。下一步,Koordinator 将基于 v1.3 版本支持的 NRI (Node Resource Interface) 框架,增强末级缓存及内存带宽的隔离策略,提供 Pod 维度的隔离能力,增强功能的灵活性和时效性。

致谢

自开源以来,Koordinator 已经共计发布了19个小版本,吸引了80多名贡献者的参与,社区的不断发展壮大,离不开广大工程师的积极参与,在此真诚感谢各位社区同学们的贡献和持续投入。同时,也特别感谢 CNCF 社区同仁对项目发展的提供的大力支持。

欢迎更多开发者和用户参与 Koordinator 社区建设,是您们的积极参与和宝贵意见让 Koordinator 不断进步。我们期待您继续提供反馈,并欢迎新的贡献者加入我们。 无论您在云原生领域是初学乍练还是驾轻就熟,我们都非常期待听到您的声音!

· 28 分钟阅读
Jianyu Wang

背景

Koordinator 作为一个积极发展的开源项目,自 2022 年 4 月发布 v0.1.0 版本以来,经历了多次迭代,持续为 Kubernetes 生态系统带来创新和增强。项目的核心是提供混部工作负载编排、混部资源调度、混部资源隔离和混部性能调优的综合解决方案,帮助用户优化容器性能,并提升集群资源使用效率。

在过去的版本迭代中,Koordinator 社区不断壮大,已经得到了包括阿里巴巴、蚂蚁科技、Intel、小米、小红书、爱奇艺、360、有赞、趣玩、美亚柏科、PITS 等知名企业工程师的积极参与和贡献。每一个版本都是在社区共同努力下推进的,反映了项目在实际生产环境中解决问题的能力。

今天我们很高兴的向大家宣布,Koordinator v1.4.0 版本正式发布。在本次发布中,Koordinator 引入了 Kubernetes 与 YARN 负载混部、NUMA 拓扑对齐策略、CPU 归一化和冷内存上报等新特性,同时重点增强了弹性配额管理、宿主机非容器化应用的 QoS 管理、重调度防护策略等领域的功能。这些新增和改进点旨在更好地支持企业级 Kubernetes 集群环境,特别是对于复杂和多样化的应用场景。

v1.4.0 版本的发布,将为用户带来更多的计算负载类型支持和更灵活的资源管理机制,我们期待这些改进能够帮助用户应对更多企业资源管理挑战。在 v1.4.0 版本中,共有 11 位新加入的开发者参与到了 Koordinator 社区的建设,他们是 @shaloulcy,@baowj-678,@zqzten,@tan90github,@pheianox,@zxh326,@qinfustu,@ikaven1024,@peiqiaoWang,@bogo-y,@xujihui1985,感谢期间各位社区同学的积极参与和贡献,也感谢所有同学在社区的持续投入。

版本功能特性解读

1. 支持 K8s 与 YARN 混部

Koordinator 已经支持了 K8s 生态内的在离线混部,然而在 K8s 生态外,仍有相当数量的大数据任务运行在传统的 Hadoop YARN 之上。YARN 作为发展多年的大数据生态下的资源管理系统,承载了包括 MapReduce、Spark、Flink 以及 Presto 等在内的多种计算引擎。

Koordinator 社区会同来自阿里云、小红书、蚂蚁金服的开发者们共同启动了 Hadoop YARN 与 K8s 混部项目 Koordinator YARN Copilot,支持将 Hadoop NodeManager 运行在 kubernetes 集群中,充分发挥不同类型负载错峰复用的技术价值。Koordinator YARN Copilot 具备以下特点:

  • 面向开源生态:基于 Hadoop YARN 开源版本,不涉及对 YARN 的侵入式改造;
  • 统一资源优先级和 QoS 策略:YARN NM 使用 Koordinator 的 Batch 优先级资源,遵循 Koordinator QoS 管理策略;
  • 节点级别的资源共享:Koordinator 提供的混部资源,既可被 K8s Pod 使用,也可被 YARN task使用,不同类型的离线应用可运行在同一节点。

img

关于 Koordinator YARN Copilot 的详细设计,以及在小红书生产环境的使用情况,请参考往期文章以及社区官方文档

2. 引入 NUMA 拓扑对齐策略

运行在 Kubernetes 集群中的工作负载日益多样化。尤其是在机器学习等领域,对于高性能计算资源的需求持续上升。在这些领域中,不仅需要大量 CPU 资源,还经常需要 GPU 和 RDMA 等其他高速计算资源配合使用;并且,为了获得最佳的性能,这些资源往往需要在同一个 NUMA 节点,甚至同一个 PCIE 中。

Kubernetes 的 Kubelet 提供了 Topology Manager 来管理资源分配的 NUMA 拓扑,试图在 Kubelet 的 Admission 阶段从节点层面对齐多种资源的拓扑。然而,节点组件没有调度器的全局视角以及为 Pod 选择节点的时机,可能导致 Pod 被调度到无法满足拓扑对齐策略的节点上,从而导致 Pod 由于 Topology Affinity错误无法启动。

为了解决这一问题,Koordinator 将 NUMA 拓扑选择和对齐的时机放在中心调度器中,从集群级别优化资源之间的 NUMA 拓扑。在本次发布的版本中,Koordinator 将 CPU 资源(包含 Batch 资源)的 NUMA 感知调度和 GPU 设备的 NUMA 感知调度作为 alpha 功能支持,整套 NUMA 感知调度快速演进中。

koordinator 支持用户通过节点的 Label 配置节点上多种资源的 NUMA 拓扑对齐策略,可配置策略如下:

  • None 是默认策略,不执行任何拓扑对齐。
  • BestEffort 表示节点不严格按照 NUMA 拓扑对齐来分配资源。只要节点的剩余总量满足 Pods 的需求,调度器总是可以将这样的节点分配给 Pods。
  • Restricted 表示节点严格按照 NUMA 拓扑对齐来分配资源,即调度器在分配多个资源时必须只选择相同的一个或多个 NUMA 节点,否则不应使用该节点;可以使用多个 NUMA 节点。例如,如果一个Pod请求 33C,并且每个 NUMA 节点有 32C,那么它可以被分配使用两个 NUMA 节点。如果这个Pod还需要请求 GPU/RDMA,那么它需要位于与 CPU 相同的 NUMA 节点上。
  • SingleNUMANodeRestricted 类似,也是严格按照 NUMA 拓扑对齐,但与 Restricted 不同的是,Restricted 允许使用多个NUMA节点,而 SingleNUMANode 只允许使用一个NUMA 节点。

举例,我们可以为 node-0设置策略 SingleNUMANode

apiVersion: v1
kind: Node
metadata:
labels:
node.koordinator.sh/numa-topology-policy: "SingleNUMANode"
name: node-0
spec:
...

在生产环境中,用户可能已经开启了 Kubelet 的拓扑对齐策略,这个策略会由 koordlet 更新到 NodeResourceTopologyCRD 对象中的 TopologyPolicies字段。当 Kubelet 的策略和用户在 Node 上设置的策略相冲突时,以 Kubelet 策略为准。Koordinator 调度器基本采用与 Kubelet Topology Manager 相同的 NUMA 对齐策略语义,Kubelet 策略 SingleNUMANodePodLevel SingleNUMANodeContainerLevel被映射为 SingleNUMANode

在为节点配置好 NUMA 对齐策略的前提下,调度器可以为每个 Pod 选出许多个符合条件的 NUMA Node 分配结果。Koordinator 当前支持 NodeNUMAResource 插件配置 CPU 和内存资源的 NUMA Node 分配结果打分策略,包括 LeastAllocatedMostAllocated, 默认为 LeastAllocated 策略,资源支持配置权重。调度器最终将选择得分最高的 NUMA Node 分配结果。如下例,我们配置 NUMA Node 分配结果打分策略为 MostAllocated

apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
- pluginConfig:
- name: NodeNUMAResource
args:
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: NodeNUMAResourceArgs
scoringStrategy: # Here configure Node level scoring strategy
type: MostAllocated
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
- name: "kubernetes.io/batch-cpu"
weight: 1
- name: "kubernetes.io/batch-memory"
weight: 1
numaScoringStrategy: # Here configure NUMA-Node level scoring strategy
type: MostAllocated
resources:
- name: cpu
weight: 1
- name: memory
weight: 1
- name: "kubernetes.io/batch-cpu"
weight: 1
- name: "kubernetes.io/batch-memory"
weight: 1

3. ElasticQuota 再进化

为了充分地利用集群资源、降低管控系统成本,用户常常将多个租户的负载部署在一个集群中。在集群资源有限的情况下,不同租户之间必然会发生资源争抢。有的租户的负载可能一直被满足,而有的租户的负载一直无法得到执行。这就产生对公平性的诉求。配额机制是非常自然地保障租户间公平性的方式,给每个租户一个配额,租户可以使用配额内的资源,超过配额的任务将不被调度和执行。然而,简单的配额管理无法满足租户对云的弹性期待。用户希望除了配额之内的资源请求可以被满足外,配额之外的资源请求也可以按需地被满足。

在之前的版本中,Koordinator 复用了上游 ElasticQuota 的协议,允许租户设置 Min 表达其一定要满足的资源诉求,允许设置 Max 限制其最大可以使用的资源和表达在集群资源不足的情况下对集群剩余资源的使用权重。另外,koordinator 观察到,一些租户可能通过 Min 申请了配额,但是实际的任务申请可能并没有充分利用该配额。由此,为了更近一步地提高资源利用率,Koordinator 允许租户间借用/归还资源。

除了提供弹性的配额机制满足租户按需诉求外,Koordinator 在 ElasticQuota 上增加注解将其组织成树的结构,方便用户表达树形的组织架构。

img

上图是使用了 Koordinator 弹性配额的集群中常见的 Quota 结构树。Root Quota 是连接配额与集群中实际资源之间的桥梁。在之前的设计中,Root Quota 只在调度器逻辑中存在,在本次发布中,我们将 Root Quota 也通过 CRD 的形式暴露给用户,用户可以通过 koordinator-root-quota 这个 ElasticQuota CRD 查看 Root Quota 信息。

3.1 引入 Multi QuotaTree

大型集群中的节点的形态是多样的,例如云厂商提供的 ECS VM 会有不同的架构,常见的是 amd64 和 arm64,相同架构又会有不同种类的机型,而且一般会把节点按可用区划分。不同类型的节点放到同一个 Quota Tree 中管理时,其特有的属性将丢失,当用户希望精细化管理机器的特有属性时,当前的 ElasticQuota 显得不够精确。为了满足用户灵活的资源管理或资源隔离诉求,Koordinator 支持用户将集群中的资源划分为多份,每一份由一个 Quota Tree 来管理,如下图所示:

img

同时,为了帮助用户简化管理复杂性,Koordinator 在 v1.4.0 中 引入了 ElasticQuotaProfile 机制,用户可以通过 nodeSelector 快速的将节点关联到不同的 QuotaTree 中,如下实例所示:

apiVersion: quota.koordinator.sh/v1alpha1
kind: ElasticQuotaProfile
metadata:
labels:
kubernetes.io/arch: amd64
name: amd64-profile
namespace: kube-system
spec:
nodeSelector:
matchLabels:
kubernetes.io/arch: amd64 // 挑选 amd64 节点
quotaName: amd64-root-quota // 匹配的 root quota 名称
---
apiVersion: quota.koordinator.sh/v1alpha1
kind: ElasticQuotaProfile
metadata:
labels:
kubernetes.io/arch: arm64
name: arm64-profile
namespace: kube-system
spec:
nodeSelector:
matchLabels:
kubernetes.io/arch: arm64 // 挑选 arm64 节点
quotaName: arm64-root-quota // 匹配的 root quota 名称

关联好 QuotaTree 之后,用户在每一个 QuotaTree 中与之前的 ElasticQuota 用法一致。当用户提交 Pod 到对应的 Quota 时,当前仍然需要用户完成 Pod NodeAffinity 的管理,以确保 Pod 运行在正确的节点上。未来,我们会增加一个特性帮助用户自动管理 Quota 到 Node 的映射关系。

3.2 支持 non-preemptible

Koordinator ElasticQuota 支持把 ElasticQuota 中 Min 未使用的部分共享给其他 ElasticQuota 使用从而提高资源利用效率,但当资源紧张时,会通过抢占机制把借用配额的 Pod 抢占驱逐走拿回资源。

在实际生产环境中,有一些在线服务如果从其他 ElasticQuota 中借用了这部分额度,后续又发生了抢占,是可能影响服务质量的。这类工作负载实质上是不能被抢占的。

为了实现这个机制,Koordinator v1.4.0 引入了新的 API,用户只需要在 Pod 上声明 quota.scheduling.koordinator.sh/preemptible: false 表示这个 Pod 不可以被抢占。

调度器调度时发现 Pod 声明了不可抢占,那么此类 Pod 的可用配额的上限不能超过 min,所以这里也需要注意的是,启用该能力时,一个 ElasticQuota 的 min 需要设置的合理,并且集群内有相应的资源保障。

这个特性不会破坏原有的行为。

apiVersion: v1
kind: Pod
metadata:
name: pod-example
namespace: default
labels:
quota.scheduling.koordinator.sh/name: "quota-example"
quota.scheduling.koordinator.sh/preemptible: false
spec:
...

3.3 其它改进

  1. Koordinator Scheduler 过去支持跨 Namespace 使用同一个 ElasticQuota 对象,但有一些场景下,希望只被一个或者多个有限的 Namespace 可以共享同一个对象,为了支持这个场景,用户可以在 ElasticQuota 上增加 annotation quota.scheduling.koordinator.sh/namespaces,对应的值为一个 JSON 字符串数组。
  2. 性能优化:过去的实现中,当 ElasticQuota 发生变化时,ElasticQuota 插件会重建整棵 Quota 树,在 v1.4.0 版本中做了优化。
  3. 支持忽略 Overhead:当 Pod 使用一些安全容器时,一般是在 Pod 中声明 Overhead 表示安全容器自身的资源开销,但这部分资源成本最终是否归于终端用户承担取决于资源售卖策略。当期望不用用户承担这部分成本时,那么就要求 ElaticQuota 忽略 overhead。在 v1.4.0 版本中,可以开启 featureGate ElasticQuotaIgnorePodOverhead 启用该功能。

4. CPU 归一化

随着 Kubernetes 集群中节点硬件的多样化,不同架构和代数的 CPU 之间性能差异显著。因此,即使 Pod 的 CPU 请求相同,实际获得的计算能力也可能大不相同,这可能导致资源浪费或应用性能下降。CPU 归一化的目标是通过标准化节点上可分配 CPU 的性能,来保证每个 CPU 单元在 Kubernetes 中提供的计算能力在异构节点间保持一致。

为了解决该问题,Koordinator 在 v1.4.0 版本中实现了一套支持 CPU 归一化机制,根据节点的资源放大策略,调整节点上可分配的 CPU 资源数量,使得集群中每个可分配的 CPU 通过缩放实现算力的基本一致。整体的架构如下图所示:

img

CPU 归一化分为两个步骤:

  1. CPU 性能评估,计算不同 CPU 的性能基准,可以参考工业级性能评测标准 SPEC CPU,这部分 Koordinator 项目未提供;
  2. 配置 CPU 归一化系数到 Koordinator,调度系统基于归一化系数来调度资源,这部分 Koordinator 提供;

将 CPU 归一化比例信息配置到 koord-manager 的 slo-controller-config 中,配置示例如下:

apiVersion: v1
kind: ConfigMap
metadata:
name: slo-controller-config
namespace: koordinator-system
data:
cpu-normalization-config: |
{
"enable": true,
"ratioModel": {
"Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz": {
"baseRatio": 1.29,
"hyperThreadEnabledRatio": 0.82,
"turboEnabledRatio": 1.52,
"hyperThreadTurboEnabledRatio": 1.0
},
"Intel Xeon Platinum 8369B CPU @ 2.90GHz": {
"baseRatio": 1.69,
"hyperThreadEnabledRatio": 1.06,
"turboEnabledRatio": 1.91,
"hyperThreadTurboEnabledRatio": 1.20
}
}
}
# ...

对于配置了 CPU 归一化的节点,Koordinator 通过 Webhook 拦截 Kubelet 对 Node.Status.Allocatable 的更新以实现 CPU 资源的缩放,最终在节点上呈现出归一后的 CPU 资源可分配量。

5. 改进的重调度防护策略

Pod 迁移是一个复杂的过程,涉及审计、资源分配、应用启动等步骤,并且与应用升级、扩展场景以及集群管理员的资源操作和维护操作混合在一起。因此,如果同时有大量 Pods 正在进行迁移,可能会对系统的稳定性产生影响。此外,如果同一工作负载的许多Pods同时被迁移,也会影响应用的稳定性。此外,如果同时迁移多个作业中的 Pods,可能会造成惊群效应。因此,我们希望顺序处理每个作业中的 Pods。

Koordinator 在之前提供的 PodMigrationJob 功能中已经提供了一些防护策略来解决上述问题。在 v1.4.0 版本中,Koordinator 将之前的防护策略增强为仲裁机制。当有大量的 PodMigrationJob 可以被执行时,由仲裁器通过排序和筛选,来决定哪些 PodMigrationJob 可以得到执行。

排序过程如下:

  • 根据迁移开始时间与当前时间的间隔进行排序,间隔越小,排名越高。
  • 根据 PodMigrationJob 的 Pod 优先级进行排序,优先级越低,排名越高。
  • 按照工作负载分散 Jobs,使得同一作业中的 PodMigrationJobs 靠近。
  • 如果作业中已有 Pods 正在迁移,则该 PodMigrationJob 的排名更高。

筛选过程如下:

  • 根据工作负载、节点、命名空间等对 PodMigrationJob 进行分组和筛选。
  • 检查每个工作负载中正在运行状态的 PodMigrationJob 数量,达到一定阈值的将被排除。
  • 检查每个工作负载中不可用副本的数量是否超出了最大不可用副本数,超出的将被排除。
  • 检查目标 Pod 所在节点上正在迁移的 Pod 数量是否超过单个节点的最大迁移量,超出的将被排除。

6. 冷内存上报

为提升系统性能,内核一般尽可能不让应用程序请求的页面缓存空闲,而是尽可能将其分配给应用程序。虽然内核分配了这些内存,但是应用可能不再访问,这些内存被称为冷内存。

Koordinator 在 1.4 版本中引入冷内存上报功能,主要为未来冷内存回收功能打下基础。冷内存回收主要用于应对两个场景:

  1. 对于标准的 Kubernetes 集群,当节点内存水位过高时,突发的内存请求容器导致系统直接内存回收,操作系统的直接内存回收触发时会影响已经运行容器的性能,如果回收不及时极端场景可能触发整机 oom。保持节点内存资源的相对空闲,对提升运行时稳定性至关重要
  2. 在混部场景中,高优先级应用程序请求但未使用的资源可以被低优先级应用程序回收利用。对内存而言,操作系统未回收的内存,是不能被 Koordinator 调度系统看到的。为了提高混部资源效率,回收容器未使用的内存页面可以提高整机的资源利用效率

Koordlet 在 Collector Plugins 中添加了一个冷页面回收器,用于读取由 kidled(Anolis 内核)、kstaled(Google)或 DAMON(Amazon)导出的 cgroup 文件 memory.idle_stat。该文件包含页面缓存中的冷页面信息,并存在于 memory 的每个层次结构中。目前 koordlet 已经对接了 kidled 冷页面收集器并提供了其他冷页面收集器接口。

在收集冷页面信息后,冷页面回收器将把收集到的指标(例如节点、Pod 和容器的热页面使用量和冷页面大小)存到 metriccache 中,最后该数据会被上报到 NodeMetric CRD 中。

用户可以通过 NodeMetric 启用冷内存回收和配置冷内存收集策略,当前提供了 usageWithHotPageCache、usageWithoutPageCache 和 usageWithPageCache 三种策略,更多的细节详见社区设计文档

7. 非容器化应用的 QoS 管理

在企业容器化过程中,除了已经运行在 K8s 上的应用,可能还会存在一些非容器化的应用运行在主机上。为了更好兼容企业在容器化过程这一过渡态,Koordinator 开发了节点资源预留机制,可以未尚未容器化的应用预留资源并赋予特定的 QoS 特性。与 Kubelet 提供的资源预留配置不同,Koordinator 主要目标是解决这些非容器化应用与容器化应用运行时的 QoS 问题,整体的方案如下图所示:

img

目前,应用程序需要按照规范将进程启动到对应的 cgroup 中,Koordinator 未实现自动的 cgroup 搬迁工具。针对宿主机非容器化应用,支持 QoS 如下:

  • LS (Latency Sensitive)

    • CPU QoS(Group Identity):应用按照规范将进程运行在 cgroup 的 cpu 子系统中,koordlet 根据 CPU QoS 的配置 resource-qos-config 为其设置 Group Identity 参数;
    • CPUSet Allocation:应用按照规范将进程运行在 cgroup 的 cpu 子系统中,koordlet 将为其设置 cpu share pool 中的所有 CPU 核心。
  • BE (Best-effort)

    • CPU QoS(Group Identity):应用按照规范将进程运行在 cgroup 的 cpu 子系统中,koordlet 根据 CPU QoS 的配置为其设置 Group Identity 参数。

关于宿主机应用 QoS 管理的详细设计,可以参考社区文档,后续我们将陆续增加其他QoS策略对宿主机应用的支持。

8. 其它特性

除了上述新特性和功能增强外,Koordinator 在 v1.4.0 版本还做了一些如下的 bugfix 和优化:

  1. RequiredCPUBindPolicy:精细化 CPU 编排支持 Required 的 CPU 绑定策略配置,表示严格按照指定的 CPU 绑定策略分配 CPU,否则调度失败。
  2. CICD:Koordinator 社区在 v1.4.0 提供了一套 e2e 测试的 Pipeline;提供了 ARM64 镜像。
  3. Batch 资源计算策略优化:支持了 maxUsageRequest 的计算策略,用于更保守地超卖高优资源;优化了节点上短时间大量 Pod 启停时,Batch allocatable 被低估的问题;完善了对 hostApplication、thirdparty allocatable、dangling pod used 等特殊情况的考虑。
  4. 其它:利用 libpfm4&perf group 优化 CPI 采集、SystemResourceCollector 支持自定义的过期时间配置、BE Pod 支持根据 evictByAllocatable 策略计算CPU 满足度、Koordlet CPUSetAllocator 修复了对于 LS 和 None Qos 的 Pod 的过滤逻辑、RDT 资源控制支持获得 sandbox 容器的 task IDs 等

通过 v1.4.0 Release 页面,可以看到更多包含在 v1.4.0 版本的新增功能。

未来计划

在接下来的版本中,Koordinator 目前规划了以下功能:

  • Core Scheduling。在运行时侧,Koordinator 开始探索下一代 CPU QoS 能力,通过利用 Linux Core Scheduling 等内核机制,增强的物理核维度的资源隔离,降低混部的安全性风险,相关工作详见 Issue #1728
  • 设备联合分配。在 AI 大模型分布式训练场景中,不同机器 GPU 之间通常需要通过高性能网卡相互通信,且 GPU 和高性能网卡就近分配的时候性能更好。Koordinator 正在推进支持多种异构资源的联合分配,目前已经在协议上和调度器分配逻辑上支持联合分配;单机侧关于网卡资源的上报逻辑正在探索中。

更多信息,敬请关注 Milestone v1.5.0

结语

最后,我们十分感谢 Koordinator 社区的所有贡献者和用户,是您们的积极参与和宝贵意见让 Koordinator 不断进步。我们期待您继续提供反馈,并欢迎新的贡献者加入我们的行列。

· 12 分钟阅读
Rougang Han

背景

Koordinator 是一个开源项目,旨在基于阿里巴巴在容器调度领域的多年经验,提供一个完整的混部解决方案,包含混部工作负载编排、资源调度、资源隔离及性能调优等多方面能力,来帮助用户优化容器性能,充分发掘空闲物理资源,提升资源效率,增强延迟敏感型工作负载和批处理作业的运行效率和可靠性。

在此,我们很高兴地向各位宣布 Koordinator v1.3.0 版本的发布。自 2022 年 4 月发布 v0.1.0 版本以来,Koordinator 迄今迭代发布了共 11 个版本,吸引了了包括阿里巴巴、Intel、小米、小红书、爱奇艺、360、有赞等企业在内的大量优秀工程师参与贡献。在 v1.3.0 版本中,Koordinator 带来了 NRI (Node Resource Interface) 支持、Mid 资源超卖等新特性,并在资源预留、负载感知调度、DeviceShare 调度、负载感知重调度、调度器框架、单机指标采集和资源超卖框架等特性上进行了稳定性修复、性能优化与功能增强。

在 v1.3.0 版本中,共有 12 位新加入的开发者参与到了 Koordinator 社区的建设,他们是 @bowen-intel,@BUPT-wxq,@Gala-R,@haoyann,@kangclzjc,@Solomonwisdom,@stulzq,@TheBeatles1994,@Tiana2018,@VinceCui,@wenchezhao,@zhouzijiang,感谢期间各位社区同学的积极参与和贡献,也感谢所有同学在社区的持续投入。

版本功能特性解读

资源预留增强

资源预留(Reservation)能力自 v0.5.0 版本提出后,经历了一年的打磨和迭代,在 v1.3.0 版本中针对抢占、设备预留、Coscheduling 等场景增强了预留机制,新增 allocatePolicy 字段用于定义不同的预留资源分配策略。最新的资源预留 API 如下:

apiVersion: scheduling.koordinator.sh/v1alpha1
kind: Reservation
metadata:
name: reservation-demo
spec:
# template字段填写reservation对象的资源需求和affinity信息,就像调度pod一样.
template:
namespace: default
spec:
containers:
- args:
- '-c'
- '1'
command:
- stress
image: polinux/stress
imagePullPolicy: Always
name: stress
resources:
requests:
cpu: 500m
memory: 1Gi
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- cn-hangzhou-i
schedulerName: koord-scheduler # 指定koord-scheduler来负责reservation对象的调度.
# 指定可分配预留资源的owners.
owners:
- labelSelector:
matchLabels:
app: app-demo
ttl: 1h
# 指定预留资源是否仅支持一次性的分配.
allocateOnce: true
# 指定预留资源的分配策略,当前支持以下策略:
# - Default: 缺省配置,不限制对预留资源的分配,pod优先分配自节点上的预留资源;如果预留资源不足,则继续分配节点空闲资源。
# - Aligned: pod优先分配自节点上的预留资源;如果预留资源不足,则继续分配节点空闲资源,但要求这部分资源满足Pod需求。该策略可用于规避pod同时分配多个reservation的资源。
# - Restricted: 对于预留资源包含的各个资源维度,pod必须分配自预留资源;其余资源维度可以分配节点空闲资源。包含了Aligned策略的语义。
# 同一节点尚不支持Default策略和Aligned策略或Restricted策略共存。
allocatePolicy: "Aligned"
# 控制预留资源是否可以使用
unschedulable: false

此外,资源预留在 v1.3.0 中还包含了如下兼容性和性能优化:

  1. 增强 Reservation 的抢占,允许 Reservation 内的 Pod 间抢占,拒绝 Reservation 外的 Pod 抢占 Reservation 内的 Pod。
  2. 增强设备预留场景,如果节点上设备资源被部分预留并被 pod 使用,支持剩余资源的分配。
  3. 支持 Reservation 使用 Coscheduling。
  4. 新增 Reservation Affinity协议,允许用户一定从Reservation内分配资源。
  5. 优化 Reservation 兼容性,修复因 Reservation 导致原生打分插件失效的问题。
  6. 优化因引入 Reservation 导致的调度性能回归问题。
  7. 修复 Reservation 预留端口误删除的问题。

关于资源预留的设计,详见Designs - Resource Reservation

其他调度增强

在 v1.3.0 中,koordinator 在调度和重调度方面还包含如下增强:

  1. DeviceShare 调度

    • 更改 GPU 资源使用方式,使用 GPU Share API 时,必须声明koordinator.sh/gpu-memorykoordinator.sh/gpu-memory-ratio,允许不声明koordinator.sh/gpu-core
    • 支持打分,可用于实现 GPU Share 场景和整卡分配场景的 bin-packing 或 spread,并支持卡粒度 binpacking 或 spread。
    • 修复用户误删除 Device CRD 导致调度器内部状态异常重复分配设备的问题。
  2. 负载感知调度:修复对仅填写 Request 的 Pod 的调度逻辑。

  3. 调度器框架:优化 PreBind 阶段的 Patch 操作,将多个插件的 Patch 操作合并为一次提交,提升操作效率,降低 APIServer 压力。

  4. 重调度

    • LowNodeLoad 支持按节点池设置不同的负载水位和参数等。自动兼容原有配置。
    • 跳过 schedulerName 不是 koord-scheduler 的Pod,支持配置不同的 schedulerName。

NRI 资源管理模式

Koordinator 的 runtime hooks 支持两种模式,standalone 和 CRI proxy,然而这两种模式各自有着一些限制。当前,尽管在 standalone 模式做了很多优化,但当想获得更加及时的 Pod 或容器的事件或者环境变量的注入时还是需要依赖 proxy 模式。然而, proxy 模式要求单独部署 koord-runtime-proxy 组件来代理 CRI (Container Runtime Interface) 请求, 同时需要更改 Kubelet 的启动参数并重启 Kubelet。

NRI(Node Resource Interface),即节点资源接口,是 CRI 兼容的容器运行时插件扩展的通用框架,独立于具体的容器运行时(e.g. containerd, cri-o), 提供不同生命周期事件的接口,允许用户在不修改容器运行时源代码的情况下添加自定义逻辑。特别的是,2.0 版本 NRI 只需要运行一个插件实例用于处理所有 NRI 事件和请求,容器运行时通过 Unix-Domain Socket 与插件通信,使用基于 Protobuf 的协议数据,和 1.0 版本 NRI 相比拥有更高的性能,能够实现有状态的 NRI 插件。

通过 NRI 的引入,既能及时的订阅 Pod 或者容器的生命周期事件,又避免了对 Kubelet 的侵入修改。在 Koordinator v1.3.0 中,我们引入 NRI 这种社区推荐的方式来管理 runtime hooks 来解决之前版本遇到的问题,大大提升了 Koordinator 部署的灵活性和处理的时效性,提供了一种优雅的云原生系统的资源管理标准化模式。

nri

注:NRI 模式不支持 docker 的容器运行时,使用 docker 的用户请继续使用 standalone 模式或 proxy 模式。

关于 Koordinator 启用 NRI 的部署方式,请见Installation - Enable NRI Mode Resource Management

节点画像和 Mid 资源超卖

Koordinator 中将节点资源分为4种资源优先级模型 Prod、Mid、Batch 和 Free,低优先级资源可以复用高优先级已分配但未使用的物理资源,以提升物理资源利用率;同时,资源优先级越高,提供的资源也越稳定,例如 Batch 资源采用高优先级资源短期(short-term)已分配但未使用的超卖资源,而 Mid 资源采用高优先级资源长周期(long-term)已分配但未使用的超卖资源。不同资源优先级模型如下图所示:

resource-priority-model

Koordinator v1.3.0 新增了节点画像能力,基于 Prod 的历史资源用量进行峰值预测,以支持 Mid-tier 的资源超卖调度。Mid 资源的超卖计算公式如下:

MidAllocatable := min(ProdReclaimable, NodeAllocatable * thresholdRatio)
ProdReclaimable := max(0, ProdAllocated - ProdPeak * (1 + safeMargin))
  • ProdPeak:通过节点画像,预估的节点上已调度 Prod Pod 在中长周期内(e.g. 12h)的用量峰值。
  • ProdReclaimable:基于节点画像结果,预估在中长周期内可复用的 Prod 资源。
  • MidAllocatable:节点上可分配的 Mid 资源。

此外,Mid 资源的单机隔离保障将在下个版本得到完善,相关动态敬请关注Issue #1442。 在 v1.3.0 版本中,用户可以查看和提交 Mid-tier 的超卖资源,也可以通过以下 Prometheus metrics 来观测节点画像的趋势变化。

# 查看节点的CPU资源画像,reclaimable指标表示预测的可回收资源量,predictor对应不同的预测模型
koordlet_node_predicted_resource_reclaimable{node="test-node", predictor="minPredictor", resource="cpu", unit="core"}
# 查看节点的内存资源画像,reclaimable指标表示预测的可回收资源量,predictor对应不同的预测模型
koordlet_node_predicted_resource_reclaimable{node="test-node", predictor="minPredictor", resource="memory", unit="byte"}
$ kubectl get node test-node -o yaml
apiVersion: v1
kind: Node
metadata:
name: test-node
status:
# ...
allocatable:
cpu: '32'
memory: 129636240Ki
pods: '110'
kubernetes.io/mid-cpu: '16000' # allocatable cpu milli-cores for Mid-tier pods
kubernetes.io/mid-memory: 64818120Ki # allocatable memory bytes for Mid-tier pods
capacity:
cpu: '32'
memory: 129636240Ki
pods: '110'
kubernetes.io/mid-cpu: '16000'
kubernetes.io/mid-memory: 64818120Ki

关于 Koordinator 节点画像的设计,详见Design - Node Prediction

其他功能

通过 v1.3.0 Release 页面,可以看到更多包含在 v1.3.0 版本的新增功能。

未来计划

在接下来的版本中,Koordinator 目前规划了以下功能:

  • 硬件拓扑感知调度,综合考虑节点 CPU、内存、GPU 等多个资源维度的拓扑关系,在集群范围内进行调度优化。
  • 提供节点可分配资源的放大机制。
  • NRI 资源管理模式的完善和增强。

更多信息,敬请关注 Milestone v1.4.0

结语

最后,Koordinator 是一个开放的社区,欢迎广大云原生爱好者们随时通过各种方式参与共建,无论您在云原生领域是初学乍到还是驾轻就熟,我们都非常期待听到您的声音!

· 13 分钟阅读
Zuowei Zhang

背景

Koordinator 是一个开源项目,基于阿里巴巴在容器调度领域多年累积的经验孵化诞生,可以提升容器性能,降低集群资源成本。通过混部、资源画像、调度优化等技术能力, 能够提高延迟敏感的工作负载和批处理作业的运行效率和可靠性,优化集群资源使用效率。

从 2022 年 4 月发布以来,Koordinator 迄今一共迭代发布了 10 个版本,吸引了了包括阿里巴巴、小米、小红书、爱奇艺、360、有赞 等在内的大量优秀工程师参与贡献。 随着2023年春天的来临,Koordinator也迎来了它的一周年诞辰,在此我们很高兴的向大家宣布,Koordinator v1.2版本正式发布。新版本中Koordinator支持了节点资源预留功能, 并兼容了K8s社区的重调度策略,同时在单机侧增加了对AMD环境L3 Cache和内存带宽隔离的支持。

在新版本中,共有12位新加入的开发者参与到了Koordiantor社区的建设,他们是@Re-Grh,@chengweiv5,@kingeasternsun,@shelwinnn,@yuexian1234,@Syulin7,@tzzcfrank @Dengerwei,@complone,@AlbeeSo,@xigang,@leason00,感谢以上开发者的贡献和参与。

版本功能特性解读

节点资源预留

混部场景中包含的应用形态多种多样,除了已经完成云原生化的容器,还包含很多尚未完成容器化的应用,这部分应用会以进程的形式在宿主机上与K8s容器共同运行。 为了减少K8s应用和其他类型应用在节点侧的资源竞争,Koordinator 支持将一部分资源预留,使其既不参与调度器的资源调度,也不参与节点侧的资源分配,达到资源分隔使用的效果。 在v1.2版本中,Koordiantor已经支持CPU和内存资源维度的预留,并允许直接指定预留的CPU编号,具体如下。

节点资源预留声明

在Node上可以配置需要预留的资源量或具体的CPU编号,举例如下:

apiVersion: v1
kind: Node
metadata:
name: fake-node
annotations: # specific 5 cores will be calculated, e.g. 0, 1, 2, 3, 4, and then those core will be reserved.
node.koordinator.sh/reservation: '{"resources":{"cpu":"5"}}'
---
apiVersion: v1
kind: Node
metadata:
name: fake-node
annotations: # the cores 0, 1, 2, 3 will be reserved.
node.koordinator.sh/reservation: '{"reservedCPUs":"0-3"}'

单机组件Koordlet在上报节点资源拓扑信息时,会将具体预留的CPU编号更新到NodeResourceTopology对象的Annotation中。

调度及重调度场景适配

调度器在分配资源的过程中,涉及了多种情况的资源校验,包括Quota管理,节点容量校验,CPU拓扑校验等等,这些场景都需要增加对节点预留资源的考虑,例如,调度器在计算节点CPU容量时,需要将节点预留的资源进行扣除。

cpus(alloc) = cpus(total) - cpus(allocated) - cpus(kubeletReserved) - cpus(nodeAnnoReserved)

此外,对于Batch混部超卖资源的计算同样需要将这部分资源扣除,而考虑到节点中还包括一部分系统进程的资源消耗,Koord-Manager在计算时会取节点预留和系统用量的最大值,具体为:

reserveRatio = (100-thresholdPercent) / 100.0
node.reserved = node.alloc * reserveRatio
system.used = max(node.used - pod.used, node.anno.reserved)
Node(BE).Alloc = Node.Alloc - Node.Reserved - System.Used - Pod(LS).Used

对于重调度,各插件策略需要在节点容量、利用率计算等场景感知节点预留资源量,此外,若已经有容器占用了节点的预留资源,重调度需要考虑将其进行驱逐,确保节点容量得到正确管理, 避免资源竞争。这部分重调度相关的功能,我们将在后续版本进行支持,也欢迎广大爱好者们一起参与共建。

单机资源管理

对于LS类型的Pod,单机Koordlet组件会根据CPU分配情况动态计算共享CPU池,对于节点预留的CPU核心会将其排除在外,确保LS类型pod和其他非容器化的进程资源隔离。 同时,对于单机相关的QoS策略,例如CPUSuppress压制策略在计算节点利用率时,会将预留资源量考虑在内。

suppress(BE) := node.Total * SLOPercent - pod(LS).Used - max(system.Used, node.anno.reserved)

关于节点资源预留功能的详细说明,可以参考 设计文档 中的介绍。

兼容社区重调度策略

得益于 Koordinator Descheduler 的框架日益成熟,在 Koordinator v1.2 版本中,通过引入一种接口适配机制,可以无缝的对 Kubernetes Desceheduler 已有插件进行兼容,在使用时您只需部署 Koordinator Descheduler 即可使用到上游的全部功能。

在实现上,Koordinator Descheduler 通过 import 上游代码不做任何侵入式的改动,保证完全兼容上游所有的插件、参数配置以及其运行策略。同时,Koordinator 允许用户为上游插件指定增强的 evictor,从而复用 Koordinator 提供的资源预留、工作负载可用性保障以及全局流控等安全性策略。

兼容的插件列表包括:

  • HighNodeUtilization
  • LowNodeUtilization
  • PodLifeTime
  • RemoveFailedPods
  • RemoveDuplicates
  • RemovePodsHavingTooManyRestarts
  • RemovePodsViolatingInterPodAntiAffinity
  • RemovePodsViolatingNodeAffinity
  • RemovePodsViolatingNodeTaints
  • RemovePodsViolatingTopologySpreadConstraint
  • DefaultEvictor

在使用时,可以参考如下的方式配置,以 RemovePodsHavingTooManyRestarts 为例:

apiVersion: descheduler/v1alpha2
kind: DeschedulerConfiguration
clientConnection:
kubeconfig: "/Users/joseph/asi/koord-2/admin.kubeconfig"
leaderElection:
leaderElect: false
resourceName: test-descheduler
resourceNamespace: kube-system
deschedulingInterval: 10s
dryRun: true
profiles:
- name: koord-descheduler
plugins:
evict:
enabled:
- name: MigrationController
deschedule:
enabled:
- name: RemovePodsHavingTooManyRestarts
pluginConfig:
- name: RemovePodsHavingTooManyRestarts
args:
apiVersion: descheduler/v1alpha2
kind: RemovePodsHavingTooManyRestartsArgs
podRestartThreshold: 10

资源预留调度能力增强

Koordinator 在比较早期的版本中引入了 Reservation 机制,通过预留资源并复用给指定特征的 Pod 使用,用于帮助解决资源交付确定性问题。 例如重调度场景中期望被驱逐的 Pod 一定有资源可以使用,而不是被驱逐后无资源可用导致引起稳定性问题;又或者需要扩容时, 一些 PaaS 平台希望能够先确定是否满足应用调度编排的资源,再决定是否扩容,或者提前做一些预备工作等。

Koordinator Reservation 通过 CRD 定义,每个 Reservation 对象会在 koord-scheduler 内伪造成一个 Pod 进行调度, 这样的 Pod 我们称为 Reserve PodReserve Pod 就可以复用已有的调度插件和打分插件找到合适的节点,并最终在调度器内部状态中占据对应的资源。 Reservation 在创建时都会指定预留的资源将来要给哪些 Pod 使用,可以指定具体某个 Pod,也可以指定某些 workload 对象,或者具备某些标签的 Pod 使用。 当这些 Pod 通过 koord-scheduler 调度时,调度器会找到可以被该 Pod 使用的 Reservation 对象,并且优先使用 Reservation 的资源。 并且 Reservation Status 中会记录被哪个 Pod 使用,以及 Pod Annotations 中也会记录使用了哪个 Reservation。 Reservation 被使用后,会自动的清理内部状态,确保其他 Pod 不会因为 Reservation 导致无法调度。

在 Koordinator v1.2 中,我们做了大幅度的优化。首先我们放开了只能使用 Reservation 持有的资源的限制,允许跨出 Reservation 的资源边界, 既可以使用 Reservation 预留的资源,也可以使用节点上剩余的资源。而且我们通过非侵入式的方式扩展了 Kubernetes Scheduler Framework, 支持预留精细化资源,即可以预留 CPU 核和 GPU 设备等。我们也修改了 Reservation 可以被重复使用的默认行为,改为 AllocateOnce, 即 Reservation 一旦被某个 Pod 使用,该 Reservation 会被废弃。这样的改动是考虑到,AllocateOnce 更能覆盖大部分场景,这样作为默认行为,大家在使用时会更简单。

支持AMD环境下的L3 Cache和内存带宽隔离

在v0.3.0版本中,Koordiantor已经支持了Intel环境的L3 Cache和内存带宽隔离,在最新的1.2.0版本中我们新增了对AMD环境的支持。 Linux内核L3 Cache和内存带宽隔离能力提供了统一的resctrl接口,同时支持Intel和AMD环境,主要区别在于,Intel提供的内存带宽隔离接口为百分比格式, 而AMD提供的内存带宽隔离接口为绝对值格式,具体如下。

# Intel Format
# resctrl schema
L3:0=3ff;1=3ff
MB:0=100;1=100

# AMD Format
# resctrl schema
L3:0=ffff;1=ffff;2=ffff;3=ffff;4=ffff;5=ffff;6=ffff;7=ffff;8=ffff;9=ffff;10=ffff;11=ffff;12=ffff;13=ffff;14=ffff;15=ffff
MB:0=2048;1=2048;2=2048;3=2048;4=2048;5=2048;6=2048;7=2048;8=2048;9=2048;10=2048;11=2048;12=2048;13=2048;14=2048;15=2048

接口格式包含两部分,L3表示对应的socket或CCD可用的“路数”(way),以16进制的数据格式表示,每个比特位表示一路 MB表示对应的socket或CCD可以使用的内存带宽范围,Intel可选范围为0~100的百分比格式,AMD对应的为绝对值格式,单位为Gb/s,2048表示不限制。 Koordiantor统一提供了百分比格式的接口,并自动感知节点环境是否为AMD,决定resctrl接口中填写的格式。

apiVersion: v1
kind: ConfigMap
metadata:
name: slo-controller-config
namespace: koordinator-system
data:
resource-qos-config: |-
{
"clusterStrategy": {
"lsClass": {
"resctrlQOS": {
"enable": true,
"catRangeStartPercent": 0,
"catRangeEndPercent": 100,
"MBAPercent": 100
}
},
"beClass": {
"resctrlQOS": {
"enable": true,
"catRangeStartPercent": 0,
"catRangeEndPercent": 30,
"MBAPercent": 100
}
}
}
}

其他功能

通过 v1.2 release 页面,可以看到更多版本所包含的新增功能。

未来计划

在接下来的版本中,Koordiantor重点规划了以下功能,具体包括:

  • 硬件拓扑感知调度,综合考虑节点CPU、内存、GPU等多个资源维度的拓扑关系,在集群范围内进行调度优化。
  • 对重调度器的可观测性和可追溯性进行增强。
  • GPU资源调度能力的增强。

Koordinator 是一个开放的社区,非常欢迎广大云原生爱好者们通过各种方式一起参与共建,无论您在云原生领域是初学乍练还是驾轻就熟,我们都非常期待听到您的声音!

· 10 分钟阅读
Erwei Deng

什么是 CPU 混部

CPU 混部是指将不同类型的业务部署到同一台机器上运行,让它们共享机器上的 CPU 资源以提升 CPU 利用率,从而降低机器的采购和运营成本。但是,对于有些类型的任务来说,它们对延时非常的敏感,比如电商、搜索或 web 服务等,这类任务的实时性很高,但是通常对资源的消耗却不是很多,我们称之为在线任务;还有一类任务,它们更多的关注计算或者批处理,对延时没有要求,但是消耗的资源相对较多,我们称之为离线任务。

当这两类任务同时部署到同一台机器上时,由于离线任务对资源的占用较多,资源竞争导致在线任务的延时受到了很大的影响,而且,在超线程架构的机器上,即使离线任务和在线任务跑在不同的超线程 CPU 上,流水线和 cache 的竞争也会导致在线任务的运行受到影响。于是,CPU 混部技术诞生了,来解决离线任务对在线任务延时的影响,同时还能进一步提升 CPU 资源的利用率。

图1 单机混部 CPU 利用率示意图

内核 CPU 混部技术

CPU 混部技术,主要是通过单机操作系统调度器来实现的,通过任务类型来决定所分配到的 CPU 资源。Koordinator 社区主要使用的单机操作系统发行版有 Alibaba Cloud Linux 2/3(简称 Alinux2/3) 和 CentOS7.9。对于 Alinux2/3,它使用的是龙蜥社区的 Group Identity CPU 混部技术,在操作系统内核中提供了 CPU 混部能力。Group Identity 在原有的 CFS 调度器中新增了另一个运行队列来区分在线和离线任务,而且,为了避免对端 CPU(超线程架构)上离线任务的干扰,Group Identity 会对其进行驱逐。龙蜥的 Group Identity 技术已经经过阿里双十一等大型活动以及大规模商业化的验证,其 CPU 混部能力也得到广大用户和开发者的认可。

但是对于 CentOS 发行版来说,到目前为止还没有提供任何 CPU 混部相关的技术和能力。对于 CentOS CPU 混部能力的缺失,可能有以下几种解决方案:

  • 制作 CentOS 的衍生版系统,并包含 CPU 混部技术;
  • 迁移到 Alibaba Cloud Linux 2/3 操作系统发行版;

对于第一种方案,需要从 CentOS 镜像站中下载其内核源码,将 CPU 混部技术移植到内核,编译后安装,然后重启系统便可以使用该技术,但这会涉及到业务迁移和停机,势必会给业务方带来昂贵的代价。 对于第二种方案,虽然迁移工作会有一定的工作量,但是,Alinux2/3 或 Anolis OS 包含了完整的混部资源隔离方案(CPU 混部仅仅是其中一点),技术红利所带来的收益远比迁移代价要大得多。而且 CentOS 即将停服,为了解决 CentOS 停服问题,龙蜥社区推出了 Anolis OS 发行版操作系统,该发行版系统完全兼容 CentOS,用户可以进行无缝迁移。

龙蜥 CPU 混部插件

针对 Koordinator 云原生 CentOS 单机操作系统 CPU 混部能力的缺失,龙蜥社区开发人员给出了另一种方案,利用 plugsched 调度器热升级技术提供一种 CPU 混部技术的调度器插件包,该插件包含了阿里云早期(2017年)的 CPU 混部技术 bvt + noise clean,该技术采用的是 throttle 机制,当调度器选择下一个任务时,它会检测对端 CPU 上的任务类型以及当前 CPU 正在执行的任务类型,如果在、离线任务同时存在,则会将离线任务 throttle 掉,然后继续选择下一个任务进行调度,保证在线任务优先执行且不被对端 CPU 上的离线干扰。该 CPU 混部调度器插件可直接安装到 CentOS7.9,不需要停机和业务迁移等工作。

Plugsched SDK 神器

Plugsched 调度器热升级,是龙蜥社区推出的 plugsched SDK 调度器热升级开发工具,它可从 Linux 内核中将调度器解耦,形成一个独立的模块,然后将 CPU 混部技术移植到调度器模块,形成一个调度器插件,然后将其直接安装到运行的系统中就可以使用 CPU 混部技术。Plugsched,可以对内核调度器特性动态的进行增、删、改,来满足业务的需求,且无需进行业务迁移和停机升级,还可以回滚。内核开发人员可通过 plugsched SDK 生产出各种类型的调度器插件来满足不同的业务场景。

Plugsched 调度器热升级论文《Efficient Scheduler Live Update for Linux Kernel with Modularization》已被 ASPLOS 顶会收录,里面详细介绍了 plugsched 技术原理和应用价值,以及全面的测试和评估。目前,plugsched 生产的插件已在蚂蚁集团、阿里云和国内某大型互联网企业规模部署。

Plugsched 开源链接:https://gitee.com/anolis/plugsched

CPU 混部插件测试

开发人员对该调度器插件进行了 CPU 混部的测试,服务端配置:

  • 测试机器:阿里云神龙裸金属服务器,104 CPU,384 GB 内存
  • 系统配置:CentOS 7.9 发行版,内核版本 3.10,安装 CPU 混部调度器插件
  • 测试内容:在线任务是 Nginx 服务,容器配置为 80C 10GB,Nginx workers 数量为 80;离线任务是 ffmpeg 视频转码,容器配置为 50C 20GB,线程数量为 50。
  • 测试case:
    • 基线:单独启动 Nginx 容器
    • 对照组:同时启动 Nginx 容器和 ffmpeg 容器,但不设置优先级(不启用混部功能)
    • 实验组:同时启动 Nginx 容器和 ffmpeg 容器,给 Nginx 设置在线高优先级,ffmpeg 为离线低优先级(启用混部功能)

在另一台压测机上使用 wrk 工具向 Nginx 服务发起请求,结果如下:(单位:ms)

基线对照组实验组
RT-P500.2230.245(+9.86%)0.224(+0.44%)
RT-P750.3220.387(+20.18%)0.338(+4.96%)
RT-P900.4440.575(+29.50)0.504(+13.51%)
RT-P990.7061.7(+140.79)0.88(+24.64%)
CPU%25.15%71.7%49.15%

从上面的结果来看,没有 CPU 混部插件,离线任务对在线任务的影响很大,P99 延时增长了一倍多,而安装 CPU 混部插件后,P99 长尾延时的影响显著降低,CPU 利用率也接近50%。

该插件虽然能显著降低离线对在线任务的干扰,但还是逊色于龙蜥社区的 Group Identity 技术。龙蜥的 Group Identity 技术能让在线受到的干扰小于 5%,而且整机利用率的提升也比该插件要更多一些,达到 60% 以上(可查阅:koordinator 混部最佳实践手册)。这些差异的原因在于,1)内核自身的差异,CentOS 7.9 使用的是比较早的 3.10 内核,而龙蜥使用的是 4.19/5.10 内核,3.10 内核调度器性能本身就不及 4.19/5.10;2)Group Identity 的实现原理相比 noise clean 更适合 CPU 混部场景。

结语

最后,欢迎广大技术人员、开源爱好者和读者用户加入 Koordinator、openanolis 社区,享受社区带来的技术,不论是 Group Identity 还是 Plugsched 神器,一定会给大家带来意想不到的收益和价值,欢迎大家共建社区,与社区共同交流、成长和发展。

· 17 分钟阅读
Siyu Wang

背景

Koordinator 旨在为用户提供完整的混部工作负载编排、混部资源调度、混部资源隔离及性能调优解决方案,帮助用户提高延迟敏感服务的运行性能,挖掘空闲节点资源并分配给真正有需要的计算任务,从而提高全局的资源利用效率。

从 2022 年 4 月发布以来,Koordinator 迄今一共迭代发布了 9 个版本。项目经历的大半年发展过程中,社区吸纳了包括阿里巴巴、小米、小红书、爱奇艺、360、有赞 等在内的大量优秀工程师,贡献了众多的想法、代码和场景,一起推动 Koordinator 项目的成熟。

今天,很高兴的宣布 Koordinator v1.1 正式发布,它包含了负载感知调度/重调度、cgroup v2 支持、干扰检测指标采集,以及其他一系列优化点。接下来我们就针对这些新增特性做深入解读与说明。

版本特性深入解读

负载感知调度

支持按工作负载类型统计和均衡负载水位

Koordinator v1.0 及之前的版本,提供了负载感知调度提供基本的利用率阈值过滤保护高负载水位的节点继续恶化影响工作负载的运行时质量,以及通过预估机制解决解决冷节点过载的情况。已有的负载感知调度能解决很多常见场景的问题。但负载感知调度作为一种优化手段,还有比较多的场景是需要完善的。

目前的负载感知调度主要解决了集群内整机维度的负载均衡效果,但有可能出现一些特殊的情况:节点部署了不少离线Pod运行,拉高了整机的利用率,但在线应用工作负载的整体利用率偏低。这个时候如果有新的在线Pod,且整个集群内的资源比较紧张时,会有如下的问题:

  1. 有可能因为整机利用率超过整机安全阈值导致无法调度到这个节点上的;
  2. 还可能出现一个节点的利用率虽然相对比较低,但上面跑的全是在线应用率,从在线应用角度看,利用率已经偏高了,但按照当前的调度策略,还会继续调度这个Pod上来,导致该节点堆积了大量的在线应用,整体的运行效果并不好。

在 Koordinator v1.1 中,koord-scheduler 支持感知工作负载类型,区分不同的水位和策略进行调度。

在 Filter 阶段,新增 threshold 配置 prodUsageThresholds,表示在线应用的安全阈值,默认为空。如果当前调度的 Pod 是 Prod 类型,koord-scheduler 会从当前节点的 NodeMetric 中统计所有在线应用的利用率之和,如果超过了 prodUsageThresholds 就过滤掉该节点;如果是离线 Pod,或者没有配置 prodUsageThresholds,保持原有的逻辑,按整机利用率处理。

在 Score 阶段,新增开关 scoreAccordingProdUsage 表示是否按 Prod 类型的利用率打分均衡。默认不启用。当开启后,且当前 Pod 是 Prod 类型的话,koord-scheduler 在预估算法中只处理 Prod 类型的 Pod,并对 NodeMetrics 中记录的其他的未使用预估机制处理的在线应用的 Pod 的当前利用率值进行求和,求和后的值参与最终的打分。如果没有开启 scoreAccordingProdUsage,或者是离线Pod,保持原有逻辑,按整机利用率处理。

支持按百分位数利用率均衡

Koordinator v1.0及以前的版本都是按照 koordlet 上报的平均利用率数据进行过滤和打分。但平均值隐藏了比较多的信息,因此在 Koordinator v1.1 中 koordlet 新增了根据百分位数统计的利用率聚合数据。调度器侧也跟着做了相应的适配。

更改调度器的 LoadAware 插件的配置,aggregated 表示按照百分位数聚合数据进行打分和过滤。aggregated.usageThresholds 表示过滤时的水位阈值;aggregated.usageAggregationType 表示过滤阶段要使用的百分位数类型,支持 avgp99, p95, p90p50aggregated.usageAggregatedDuration 表示过滤阶段期望使用的聚合周期,如果不配置,调度器将使用 NodeMetrics 中上报的最大周期的数据;aggregated.scoreAggregationType 表示在打分阶段期望使用的百分位数类型;aggregated.scoreAggregatedDuration 表示打分阶段期望使用的聚合周期,如果不配置,调度器将使用 NodeMetrics 中上报的最大周期的数据。

在 Filter 阶段,如果配置了 aggregated.usageThresholds 以及对应的聚合类型,调度器将按该百分位数统计值进行过滤;

在 Score 阶段,如果配置了 aggregated.scoreAggregationType,调度器将会按该百分位数统计值打分;目前暂时不支持 Prod Pod 使用百分位数过滤。

负载感知重调度

Koordinator 在过去的几个版本中,持续的演进重调度器,先后了开源完整的框架,加强了安全性,避免因过度驱逐 Pod 影响在线应用的稳定性。这也影响了重调度功能的进展,过去 Koordinator 暂时没有太多力量建设重调度能力。这一情况将会得到改变。

Koordinator v1.1 中我们新增了负载感知重调度功能。新的插件称为 LowNodeLoad,该插件配合着调度器的负载感知调度能力,可以形成一个闭环,调度器的负载感知调度在调度时刻决策选择最优节点,但随着时间和集群环境以及工作负载面对的流量/请求的变化时,负载感知重调度可以介入进来,帮助优化负载水位超过安全阈值的节点。 LowNodeLoad 与 K8s descheduler 的插件 LowNodeUtilization 不同的是,LowNodeLoad是根据节点真实利用率的情况决策重调度,而 LowNodeUtilization 是根据资源分配率决策重调度。

LowNodeLoad 插件有两个最重要的参数,分别是 highThresholdslowThresholds

  • highThresholds 表示负载水位的警戒阈值,超过该阈值的节点上的Pod将参与重调度;
  • lowThresholds 表示负载水位的安全水位。低于该阈值的节点上的Pod不会被重调度。

以下图为例,lowThresholds 为45%,highThresholds 为 70%,那么低于 45% 的节点是安全的,因为水位已经很低了;高于45%,但是低于 70%的是区间是我们期望的负载水位范围;高于70%的节点就不安全了,应该把超过70%的这部分(假设当前节点A的负载水位是85%),那么 85% - 70% = 15% 的负载降低,筛选 Pod 后执行迁移。

LowNodeLoad 示例

迁移时,还要考虑到低于 45% 的这部分节点是我们重调度后要承载新Pod的节点,我们需要确保迁移的Pod的负载总量不会超过这些低负载节点的承载上限。这个承载上限即是 highThresholds - 节点当前负载,假设节点B的负载水位是20%,那么 70%-20% = 50%,这50%就是可以承载的容量了。因此迁移时每驱逐一个 Pod,这个承载容量就应该扣掉当前重调度 Pod 的当前负载或者预估负载或者画像值(这部分值与负载调度里的值对应)。这样就可以确保不会多迁移。

如果一个集群总是可能会出现某些节点的负载就是比较高,而且数量并不多,这个时候如果频繁的重调度这些节点,也会带来安全隐患,因此可以让用户按需设置 numberOfNodes

另外,LowNodeLoad 识别出超过阈值的节点后会筛选 Pod,当筛选 Pod 时,可以配置要支持或者过滤的 namespace,或者配置 pod selector 筛选,也可以配置 nodeFit 检查每个备选 Pod 对应的 Node Affinity/Node Selector/Toleration 是否有与之匹配的 Node,如果没有的话,这种节点也会被忽略。当然可以考虑不启用这个能力,通过配置 nodeFit 为 false 后即可禁用,此时完全由底层的 MigrationController 通过 Koordinator Reservation 预留资源;

当筛选出 Pod 后,会对这些 Pod 进行排序。会依靠Koordinator QoSClass、Kubernetes QoSClass、Priority、用量和创建时间等多个维度排序。

cgroup v2 支持

背景

Koordinator 中众多单机 QoS 能力和资源压制/弹性策略构建在 Linux Control Group (cgroups) 机制上,比如 CPU QoS (cpu)、Memory QoS (memory)、CPU Burst (cpu)、CPU Suppress (cpu, cpuset),koordlet 组件可以通过 cgroups (v1) 限制容器可用资源的时间片、权重、优先级、拓扑等属性。Linux 高版本内核也在持续增强和迭代了 cgroups 机制,带来了 cgroups v2 机制,统一 cgroups 目录结构,改善 v1 中不同 subsystem/cgroup controller 之间的协作,并进一步增强了部分子系统的资源管理和监控能力。Kubernetes 自 1.25 起将 cgroups v2 作为 GA (general availability) 特性,在 Kubelet 中启用该特性进行容器的资源管理,在统一的 cgroups 层次下设置容器的资源隔离参数,支持 MemoryQoS 的增强特性。

cgroup v1/v2 结构

在 Koordinator v1.1 中,单机组件 koordlet 新增对 cgroups v2 的支持,包括如下工作:

  • 重构了 Resource Executor 模块,以统一相同或近似的 cgroup 接口在 v1 和 v2 不同版本上的文件操作,便于 koordlet 特性兼容 cgroups v2 和合并读写冲突。
  • 在当前已开放的单机特性中适配 cgroups v2,采用新的 Resource Executor 模块替换 cgroup 操作,优化不同系统环境下的报错日志。

Koordinator v1.1 中大部分 koordlet 特性已经兼容 cgroups v2,包括但不限于:

  • 资源利用率采集
  • 动态资源超卖
  • Batch 资源隔离(BatchResource,废弃BECgroupReconcile)
  • CPU QoS(GroupIdentity)
  • Memory QoS(CgroupReconcile)
  • CPU 动态压制(BECPUSuppress)
  • 内存驱逐(BEMemoryEvict)
  • CPU Burst(CPUBurst)
  • L3 Cache 及内存带宽隔离(RdtResctrl)

遗留的未兼容特性如 PSICollector 将在接下来的 v1.2 版本中进行适配,可以跟进 issue#407 获取最新进展。接下来的 Koordinator 版本中也将逐渐引入更多 cgroups v2 的增强功能,敬请期待。

使用 cgroups v2

在 Koordinator v1.1 中,koordlet 对 cgroups v2 的适配对上层功能配置透明,除了被废弃特性的 feature-gate 以外,您无需变动 ConfigMap slo-controller-config 和其他 feature-gate 配置。当 koordlet 运行在启用 cgroups v2 的节点上时,相应单机特性将自动切换到 cgroups-v2 系统接口进行操作。

此外,cgroups v2 是 Linux 高版本内核(建议 >=5.8)的特性,对系统内核版本和 Kubernetes 版本有一定依赖。建议采用默认启用 cgroups v2 的 Linux 发行版以及 Kubernetes v1.24 以上版本。

更多关于如何启用 cgroups v2 的说明,请参照 Kubernetes 社区文档

干扰检测指标采集

在真实的生产环境下,单机的运行时状态是一个“混沌系统”,资源竞争产生的应用干扰无法绝对避免。Koordinator 正在建立干扰检测与优化的能力,通过提取应用运行状态的指标,进行实时的分析和检测,在发现干扰后对目标应用和干扰源采取更具针对性的策略。

当前 Koordinator 已经实现了一系列 Performance Collector,在单机侧采集与应用运行状态高相关性的底层指标,并通过 Prometheus 暴露出来,为干扰检测能力和集群应用调度提供支持。

指标采集

Performance Collector 由多个 feature-gate 进行控制,Koordinator 目前提供以下几个指标采集器:

  • CPICollector:用于控制 CPI 指标采集器。CPI:Cycles Per Instruction。指令在计算机中执行所需要的平均时钟周期数。CPI 采集器基于 Cycles 和 Instructions 这两个 Kernel PMU(Performance Monitoring Unit)事件以及 perf_event_open(2) 系统调用实现。
  • PSICollector:用于控制 PSI 指标采集器。PSI:Pressure Stall Information。表示容器在采集时间间隔内,因为等待 cpu、内存、IO 资源分配而阻塞的任务数。使用 PSI 采集器前,需要在 Anolis OS 中开启 PSI 功能,您可以参考文档获取开启方法。

Performance Collector 目前是默认关闭的。您可以通过修改 Koordlet 的 feature-gates 项来使用它,此项修改不会影响其他 feature-gate

kubectl edit ds koordlet -n koordinator-system
...
spec:
...
spec:
containers:
- args:
...
# modify here
# - -feature-gates=BECPUEvict=true,BEMemoryEvict=true,CgroupReconcile=true,Accelerators=true
- -feature-gates=BECPUEvict=true,BEMemoryEvict=true,CgroupReconcile=true,Accelerators=true,CPICollector=true,PSICollector=true

ServiceMonitor

v1.1.0 版本的 Koordinator 为 Koordlet 增加了 ServiceMonitor 的能力,将所采集指标通过 Prometheus 暴露出来,用户可基于此能力采集相应指标进行应用系统的分析与管理。

ServiceMonitor 由 Prometheus 引入,故在 helm chart 中设置默认不开启安装,可以通过以下命令安装ServiceMonitor:

helm install koordinator https://... --set koordlet.enableServiceMonitor=true

部署后可在 Prometheus UI 找到该 Targets。

# HELP koordlet_container_cpi Container cpi collected by koordlet
# TYPE koordlet_container_cpi gauge
koordlet_container_cpi{container_id="containerd://498de02ddd3ad7c901b3c80f96c57db5b3ed9a817dbfab9d16b18be7e7d2d047",container_name="koordlet",cpi_field="cycles",node="your-node-name",pod_name="koordlet-x8g2j",pod_namespace="koordinator-system",pod_uid="3440fb9c-423b-48e9-8850-06a6c50f633d"} 2.228107503e+09
koordlet_container_cpi{container_id="containerd://498de02ddd3ad7c901b3c80f96c57db5b3ed9a817dbfab9d16b18be7e7d2d047",container_name="koordlet",cpi_field="instructions",node="your-node-name",pod_name="koordlet-x8g2j",pod_namespace="koordinator-system",pod_uid="3440fb9c-423b-48e9-8850-06a6c50f633d"} 4.1456092e+09

可以期待的是,Koordinator 干扰检测的能力在更复杂的真实场景下还需要更多检测指标的补充,后续将在如内存、磁盘 IO 等其他诸多资源的指标采集建设方面持续发力。

其他更新点

通过 v1.1 release 页面,可以看到更多版本所包含的新增功能。

· 7 分钟阅读
Joseph

Koordinator 今年3月份开源以来,先后发布了7个版本,逐步的把阿里巴巴&阿里云内部的混部系统的核心能力输出到开源社区,并在中间过程中逐渐的被 Kubernetes、大数据、高性能计算、机器学习领域或者社区的关注,Koordinator 社区也逐步获得了一些贡献者的支持,并有一些企业开始逐步的在生产环境中使用 Koordinator 解决实际生产中遇到的成本问题、混部问题等。 经过 Koordinator 社区的努力,我们怀着十分激动的心情向大家宣布 Koordinator 1.0 版本正式发布。

Koordinator 项目早期着重建设核心混部能力 -- 差异化 SLO,并且为了让用户更容易的使用 Koordinator 的混部能力,Koordinator 提供了 ClusterColocationProfile 机制帮助用户可以不用修改存量代码完成不同工作负载的混部,让用户逐步的熟悉混部技术。随后 Koordinaor 逐步在节点侧 QoS 保障机制上做了增强,提供了包括但不限于 CPU Suppress、CPU Burst、 Memory QoS、L3 Cache/MBA 资源隔离机制和基于满足度驱逐机制等多种能力,解决了大部分节点侧工作负载的稳定性问题。配合使用 Koordinator Runtime Proxy 组件,可以更好的兼容 Kubernetes kubelet 原生管理机制。

并且 Koordinator 在任务调度和 QoS 感知调度以及重调度等方面也都提供了一些创新方案,建设了全面兼容 Kubernetes CPU 管理机制的精细化 CPU 调度能力,面向节点实际负载的均衡调度能力。为了更好的让用户管理好资源, Koordinator 还提供了资源预留能力(Reservation),并且 Koordinator 基于 Kubernetes 社区已有的Coscheduling、ElasticQuota Scheduling 能力做了进一步的增强,为任务调度领域注入了新的活力。Koordinator 提供了全新的重调度器框架,着重建设 Descheduler 的扩展性和安全性问题。

安装或升级 Koordinator v1.0.0

使用 Helm 安装

您可以通过 helm v3.5+ 非常方便的安装 Koordinator,Helm 是一个简单的命令行工具,您可以从 这里 获取它。

# Firstly add koordinator charts repository if you haven't do this.
$ helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

# [Optional]
$ helm repo update

# Install the latest version.
$ helm install koordinator koordinator-sh/koordinator --version 1.0.0

版本功能特性解读

Koordinator v1.0 整体新增的特性并不多,主要有以下一些变化

独立 API Repo

为了更方便集成和使用 Koordiantor 定义的 API,并避免因依赖 Koordiantor 引入额外的依赖或者依赖冲突问题,我们建立了独立的 API Repo: koordinator-sh/apis

新增 ElasticQuota Webhook

在 Koordinator v0.7 版本中,我们基于 Kubernetes sig-scheduler 提供的 ElasticQuota 做了诸多增强,提供了树形管理机制,并提供了公平性保障机制等,可以很好的帮助您解决使用 ElasticQuota 遇到的问题。在 Koordinator v1.0 版本中,我们进一步提供了 ElasticQuota Webhook,帮助您在使用 ElasticQuota 树形管理机制时,保障新的 ElasticQuota 对象遵循 Koordinator 定义的规范或约束:

  1. 除了根节点,其他所有子节点的 min 之和要小于父节点的 min。
  2. 不限制子节点 max,允许子节点的 max 大于父节点的 max。考虑以下场景,集群中有 2 个 ElasticQuota 子树:dev-parent 和 production-parent,每个子树都有几个子 ElasticQuota。 当 production-parent 忙时,我们可以通过只降低 dev-parent 的 max 限制 dev-parent 整颗子树的资源使用量,而不是降低 dev-parent 子树的每个子 ElasticQuota 的max限制用量。
  3. Pod 不能使用父节点ElasticQuota。如果放开这个限制,会导致整个弹性 Quota 的机制变的异常复杂,暂时不考虑支持这种场景。
  4. 只有父节点可以挂子节点,不允许子节点挂子节点
  5. 暂时不允许改变 ElasticQuota 的 quota.scheduling.koordinator.sh/is-parent属性

进一步完善 ElasticQuota Scheduling

在 Koordinator v0.7 版本中,koord-scheduler 的主副 Pod 都会启动 ElasticQuota Controller 并都会更新 ElasticQuota 对象。在 Koordinator v1.0 中我们修复了该问题,确保只有主 Pod 可以启动 Controller 并更新 ElasticQuota 对象。 还优化了 ElasticQuota Controller 潜在的频繁更新 ElasticQuota 对象的问题,当检查到 ElasticQuota 各维度数据发生变化时才会更新,降低频繁更新给 APIServer 带来的压力。

进一步完善 Device Share Scheduling

Koordinator v1.0 中 koordlet 会上报 GPU 的型号和驱动版本到 Device CRD 对象中,并会由 koord-manager 同步更新到 Node 对象,追加相应的标签。

apiVersion: v1
kind: Node
metadata:
labels:
kubernetes.io/gpu-driver: 460.91.03
kubernetes.io/gpu-model: Tesla-T4
...
name: cn-hangzhou.10.0.4.164
spec:
...
status:
...

Koordinator Runtime Proxy 增强兼容性

在 Koordinator 之前的版本中,koord-runtime-proxy 和 koordlet 一起安装后,如果 koordlet 异常或者 koordlet 卸载/重装等场景下,会遇到新调度到节点的 Pod 无法创建容器的问题。为了解决这个问题,koord-runtime-proxy 会感知 Pod 是否具有特殊的 label runtimeproxy.koordinator.sh/skip-hookserver=true,如果 Pod 存在该标签,koord-runtime-proxy 会直接把 CRI 请求转发给 containerd/docker 等 runtime。

其他改动

你可以通过 Github release 页面,来查看更多的改动以及它们的作者与提交记录。

· 34 分钟阅读
Joseph

Koordinator[1] 继上次 v0.6版本[2] 发布后,经过 Koordinator 社区的努力,我们迎来了具有重大意义的 v0.7 版本。在这个版本中着重解决机器学习、大数据场景需要的任务调度能力,例如 CoScheduling、ElasticQuota和精细化的 GPU 共享调度能力。并在调度问题诊断分析方面得到了增强,重调度器也极大的提升了安全性,降低了重调度的风险。

版本功能特性解读

1. 任务调度

1.1 Enhanced Coscheduling

Gang scheduling是在并发系统中将多个相关联的进程调度到不同处理器上同时运行的策略,其最主要的原则是保证所有相关联的进程能够同时启动,防止部分进程的异常,导致整个关联进程组的阻塞。例如当提交一个Job时会产生多个任务,这些任务期望要么全部调度成功,要么全部失败。这种需求称为 All-or-Nothing,对应的实现被称作 Gang Scheduling(or Coscheduling) 。
Koordinator 在启动之初,期望支持 Kubernetes 多种工作负载的混部调度,提高工作负载的运行时效率和可靠性,其中就包括了机器学习和大数据领域中广泛存在的具备 All-or-Nothing 需求的作业负载。 为了解决 All-or-Nothing 调度需求,Koordinator v0.7.0 基于社区已有的 Coscheduling 实现了 Enhanced Coscheduling。
Enhanced Coscheduling 秉承着 Koordiantor 兼容社区的原则,完全兼容社区 Coscheduling 和 依赖的 PodGroup CRD。已经使用 PodGroup 的用户可以无缝升级到 Koordinator。
除此之外,Enhanced Coscheduling 还实现了如下增强能力:

支持 StrictNonStrict 两种模式

两种模式的区别在于 Strict模式(即默认模式)下调度失败会 Reject 所有分配到资源并处于 Wait 状态的 Pod,而 NonStrict 模式不会发起 Reject。NonStrict 模式下,同属于一个 PodGroup 的 Pod A 和 PodB 调度时,如果 PodA 调度失败不会影响 PodB 调度, PodB 还会继续被调度。NonStrict 模式对于体量较大的 Job 比较友好,可以让这种大体量 Job 更快的调度完成,但同时也增加了资源死锁的风险。后续 Koordinator 会提供 NonStrict 模式下解决死锁的方案实现。
用户在使用时,可以在 PodGroup 或者 Pod 中追加 annotation gang.scheduling.koordinator.sh/mode=NonStrict开启 NonStrict 模式。

改进 PodGroup 调度失败的处理机制,实现更高效的重试调度

举个例子,PodGroup A 关联了5个Pod,其中前3个Pod通过Filter/Score,进入Wait阶段,第4个Pod调度失败,当调度第5个Pod时,发现第4个Pod已经失败,则拒绝调度。在社区 Coscheduling 实现中,调度失败的PodGroup 会加入到基于cache机制的 lastDeniedPG 对象中,当 cache 没有过期,则会拒绝调度;如果过期就允许继续调度。可以看到 cache 的过期时间很关键,过期时间设置的过长会导致Pod迟迟得不到调度机会,设置的过短会出现频繁的无效调度。
而在Enhanced Coscheduling 中,实现了一种基于 ScheduleCycle 的重试机制。以上场景为例,5个Pod的 ScheduleCycle 初始值为 0,PodGroup 对应的 ScheduleCycle 初始值为1;当每一次尝试调度 Pod 时,都会更新 Pod ScheduleCycle 为 PodGroup ScheduleCycle。如果其中一个 Pod 调度失败,会标记当前的 PodGroup ScheduleCycle 无效,之后所有小于 PodGroup ScheduleCycle 的 Pod 都会被拒绝调度。当同一个 PodGroup 下的所有 Pod 都尝试调度一轮后,Pod ScheduleCycle 都更新为当前 PodGroup ScheduleCycle,并递进 PodGroup ScheduleCycle,并标记允许调度。这种方式可以有效规避基于过期时间的缺陷,完全取决于调度队列的配置重试调度。
image.png

支持多个 PodGroup 为一组完成 Gang Scheduling

一些复杂的 Job 有多种角色,每个角色管理一批任务,每个角色的任务要求支持 All-or-Nothing ,每个角色的 MinMember 要求也不一样,并且每个角色之间也要求 All-or-Nothing。这就导致每个角色都有一个对应的 PodGroup ,并且还要求 PodGroup 即使满足了也需要等待其他角色的 PodGroup 必须满足。社区 Coscheduling 无法满足这种场景需求。而 Koordinator 实现的 Enhanced Coscheduling 支持用户在多个 PodGroup 中增加 anntation 相关关联实现,并支持跨Namespace。例如用户有2个PodGroup ,名字分别是PodGroupA和PodGroupB,可以按照如下例子关联两个 PodGroup:

apiVersion: v1alpha1
kind: PodGroup
metadata:
name: podGroupA
namespace: default
annotations:
gang.scheduling.koordinator.sh/groups: ["namespaceA/podGroupA", "namespaceB/podGroupB"]
spec:
...

支持轻量化 Gang 协议

如果用户不希望创建 PodGroup,认为创建 PodGroup 太繁琐,那么可以考虑在一组 Pod 中填充相同 annotation gang.scheduling.koordinator.sh/name=<podGroupName> 表示这一组 Pod 使用 Coscheduling 调度。如果期望设置 minMember ,可以追加 Annotation gang.scheduling.koordinator.sh/min-available=<availableNum>。举个例子:

apiVersion: v1
kind: Pod
metadata:
annotations:
gang.scheduling.koordinator.sh/name: "pod-group-a"
gang.scheduling.koordinator.sh/min-available: "5"
name: demo-pod
namespace: default
spec:
...

1.2 ElasticQuota Scheduling

一家中大型公司内有多个产品和研发团队,共用多个比较大规模的 Kubernetes 集群,这些集群内含有的大量 CPU/Memory/Disk 等资源被资源运营团队统一管理。运营团队往往在采购资源前,通过额度预算的机制让公司内每个团队根据自身的需求提交额度预算。业务团队此时一般根据业务当前和对未来的预期做好额度预算。最理想的情况是每一份额度都能够被使用,但现实告诉我们这是不现实的。往往出现的问题是:

  1. 团队 A 高估了业务的发展速度,申请了太多的额度用不完
  2. 团队 B 低估了业务的发展速度,申请的额度不够用
  3. 团队 C 安排了一场活动,手上的额度不够多了,但是活动只持续几周,申请太多额度和资源也会浪费掉。
  4. 团队 D 下面还有各个子团队和业务,每个子团队内也会出现类似A B C 三个团队的情况,而且其中有些团队的业务临时突发需要提交一些计算任务要交个客户,但是没有额度了,走额度预算审批也不够了。
  5. ......

以上大家日常经常遇到的场景,在混部场景、大数据场景,临时性突发需求又是时常出现的,这些资源的需求都给额度管理工作带来了极大的挑战。做好额度管理工作,一方面避免过度采购资源降低成本,又要在临时需要额度时不采购资源或者尽量少的采购资源;另一方面不能因为额度问题限制资源使用率,额度管理不好就会导致即使有比较好的技术帮助复用资源,也无法发挥其价值。 总之,额度管理工作是广大公司或组织需长期面对且必须面对的问题。
Kubernetes ResourceQuota 可以解决额度管理的部分问题。 原生 Kubernetes ResourceQuota API 用于指定每个 Namespace 的最大资源额度量,并通过 admission 机制完成准入检查。如果 Namespace 当前资源分配总量超过ResourceQuota 指定的配额,则拒绝创建 Pod。 Kubernetes ResourceQuota 设计有一个局限性:Quota 用量是按照 Pod Requests 聚合的。 虽然这种机制可以保证实际的资源消耗永远不会超过 ResourceQuota 的限制,但它可能会导致资源利用率低,因为一些 Pod 可能已经申请了资源但未能调度。
Kuberenetes Scheduler-Sig 后来给出了一个借鉴 Yarn Capacity Scheduling,称作 ElasticQuota 的设计方案并给出了具体的实现。允许用户设置 max 和 min:

  • max 表示用户可以消费的资源上限
  • min 表示需要保障用户实现基本功能/性能所需要的最小资源量

通过这两个参数可以帮助用户实现如下的需求:

  1. 用户设置 min < max 时,当有突发资源需求时,即使当前 ElasticQuota 的总用量超过了 min, 但只要没有达到 max,那么用户可以继续创建新的 Pod 应对新的任务请求。
  2. 当用户需要更多资源时,用户可以从其他 ElasticQuota 中“借用(borrow)” 还没有被使用并且需要通保障的 min。
  3. 当一个 ElasticQuota 需要使用 min 资源时,会通过抢占机制从其他借用方抢回来,即驱逐一些其他ElasticQuota 超过 min 用量的 Pod。

ElasticQuota 还有一些局限性:没有很好的保障公平性。假如同一个 ElasticQuota 有大量新建的Pod,有可能会消耗所有其他可以被借用的Quota,从而导致后来的 Pod 可能拿不到 Quota。此时只能通过抢占机制抢回来一些 Quota。
另外 ElasticQuota 和 Kubernetes ResourceQuota 都是面向 Namespace的,不支持多级树形结构,对于一些本身具备复杂组织关系的企业/组织不能很好的使用ElasticQuota/Kubenretes ResourceQuota 完成额度管理工作。
Koordinator 针对这些额度管理问题,给出了一种基于社区 ElasticQuota 实现的支持多级管理方式的弹性Quota管理机制(multi hierarchy quota management)。具备如下特性:

  • 兼容社区的 ElasticQuota API。用户可以无缝升级到 Koordinator
  • 支持树形结构管理 Quota。
  • 支持按照共享权重(shared weight)保障公平性。
  • 允许用户设置是否允许借用Quota 给其他消费对象。

Pod 关联 ElasticQuota 方式

用户可以非常使用的使用该能力,可以完全按照 ElasticQuota 的用法,即每个 Namespace 设置一个 ElasticQuota 对象。也可以在 Pod 中追加 Label 关联 ElasticQuota:

apiVersion: v1
kind: Pod
metadata:
labels:
quota.scheduling.koordinator.sh/name: "elastic-quota-a"
name: demo-pod
namespace: default
spec:
...

树形结构管理机制和使用方法

需要使用树形结构管理 Quota 时,需要在 ElasticQuota 中追加 Label quota.scheduling.koordinator.sh/is-parent表示当前 ElasticQuota 是否是父节点,quota.scheduling.koordinator.sh/parent表示当前 ElasticQuota 的父节点 ElasticQuota 的名字。举个例子:
image.png
我们创建一个 ElasticQuota Root 作为根节点,资源总量为CPU 100C,内存200Gi,以及子节点 quota-a

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: parentA
namespace: default
labels:
quota.scheduling.koordinator.sh/is-parent: "true"
quota.scheduling.koordinator.sh/allow-lent-resource: "true"
spec:
max:
cpu: 100
memory: 200Gi
min:
cpu: 100
memory: 200Gi
---
apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: childA1
namespace: default
labels:
quota.scheduling.koordinator.sh/is-parent: "false"
quota.scheduling.koordinator.sh/parent: "parentA"
quota.scheduling.koordinator.sh/allow-lent-resource: "true"
spec:
max:
cpu: 40
memory: 100Gi
min:
cpu: 20
memory: 40Gi

在使用树形结构管理 ElasticQuota 时,有一些需要遵循的约束:

  1. 除了根节点,其他所有子节点的 min 之和要小于父节点的 min。
  2. 不限制子节点 max,允许子节点的 max 大于父节点的 max。考虑以下场景,集群中有 2 个 ElasticQuota 子树:dev-parent 和 production-parent,每个子树都有几个子 ElasticQuota。 当 production-parent 忙时,我们可以通过只降低 dev-parent 的 max 限制 dev-parent 整颗子树的资源使用量,而不是降低 dev-parent 子树的每个子 ElasticQuota 的max限制用量。
  3. Pod 不能使用父节点ElasticQuota。如果放开这个限制,会导致整个弹性 Quota 的机制变的异常复杂,暂时不考虑支持这种场景。
  4. 只有父节点可以挂子节点,不允许子节点挂子节点
  5. 暂时不允许改变 ElasticQuota 的 quota.scheduling.koordinator.sh/is-parent属性

我们将在下个版本中通过 webhook 机制实现这些约束。

公平性保障机制

为了方便阅读和理解将要介绍的公平性保障机制,先明确几个新概念:

  • request 表示同一个 ElasticQuota 关联的所有 Pod 的资源请求量。如果一个 ElasticQuota A 的 request 小于 min,ElasticQuota B 的 request 大于 min,那么 ElasticQuota A 未使用的部分,即 min - request 剩余的量通过公平性保障机制借用给 ElasticQuota B. 当 ElasticQuota A 需要使用这些借走的量时,要求 ElasticQuota B 依据公平性保障机制归还给 ElasticQuota A。
  • runtime 表示 ElasticQuota 当前可以使用的实际资源量。如果 request 小于 min,runtime 等于 request。这也意味着,需要遵循 min 语义,应无条件满足 request。如果 request 大于 min,且 min 小于 max,公平性保障机制会分配 runtime 在min 与 max 之前,即 max >= runtime >= min。
  • shared-weight 表示一个 ElasticQuota 的竞争力,默认等于 ElasticQuota Max。

通过几个例子为大家介绍公平性保障机制的运行过程,假设当前集群的 CPU 总量为100C,并且有4个ElasticQuota,如下图所示,绿色部分为 Request 量:A 当前的request 为5,B当前的request为20,C当前的Request为30,D当前的Request为70。
image.png
并且我们注意到, A, B, C, D 的 min 之和是60,剩下 40 个空闲额度, 同时 A 还可以借给 B, C, D 5个额度,所以一共有45个额度被B,C,D共享,根据各个ElasticQuota的 shared-weight,B,C,D分别对应60,50和80,计算出各自可以共享的量:

  • B 可以获取 14个额度, 45 * 60 / (60 + 50 + 80) = 14
  • C 可以获取 12个额度, 45 * 50 / (60 + 50 + 80) = 12
  • D 可以获取 19个额度, 45 * 80 / (60 + 50 + 80) = 19

image.png
但我们也要注意的是,C和D需要更多额度,而 B只需要5个额度就能满足 Request,并且 B 的min是15,也就意味着我们只需要给 B 5个额度,剩余的9个额度继续分给C和D。

  • C 可以获取 3个额度, 9 * 50 / (50 + 80) = 3
  • D 可以获取 6个额度, 9 * 80 / (50 + 80) = 6


最终我们得出如下的分配结果结果:

  • A runtime = 5
  • B runtime = 20
  • C runtime = 35
  • D runtime = 40


总结整个过程可以知道:

  1. 当前 request < min 时,需要借出 lent-to-quotas;当 request > min 时,需要借入 borrowed-qutoas
  2. 统计所有 runtime < min 的 Quota,这些总量就是接下来可被借出的量。
  3. 根据 shared-weight 计算每个ElasticQuota可以借入的量
  4. 如果最新的 runtime > reuqest,那么 runtime - request 剩余的量可以借给更需要的对象。

另外还有一种日常生产时会遇到的情况:即集群内资源总量会随着节点故障、资源运营等原因降低,导致所有ElasticQuota的 min 之和大于资源总量。当出现这种情况时,我们无法确保 min 的资源述求。此时我们会按照一定的比例调整各个ElasticQuota的min,确保所有min之和小于或者等于当前实际的资源总量。

抢占机制

Koordinator ElasticQuota 机制在调度阶段如果发现 Quota 不足,会进入抢占阶段,按照优先级排序,抢占属于同一个ElasticQuota 内的 低优先级 Pod。 同时,我们不支持跨 ElasticQuota 抢占其他 Pod。但是我们也提供了另外的机制支持从借用 Quota 的 ElasticQuota 抢回。
举个例子,在集群中,有两个 ElasticQuota,ElasticQuota A {min = 50, max = 100}, ElasticQuota B {min = 50, max = 100}。用户在上午10点使用 ElasticQuota A 提交了一个 Job, Request = 100 ,此时因为 ElasticQuota B 无人使用,ElasticQuota A 能从 B 手里借用50个Quota,满足了 Request = 100, 并且此时 Used = 100。在11点钟时,另一个用户开始使用 ElasticQuota B 提交Job,Request = 100,因为 ElasticQuota B 的 min = 50,是必须保障的,通过公平性保障机制,此时 A 和 B 的 runtime 均为50。那么此时对于 ElasticQuota A ,Used = 100 是大于当前 runtime = 50 的,因此我们会提供一个 Controller,驱逐掉一部分 Pod ,使得当前 ElasticQuota A 的 Used 降低到 runtime 相等的水位。

2. 精细化资源调度

Device Share Scheduling

机器学习领域里依靠大量强大算力性能的 GPU 设备完成模型训练,但是 GPU 自身价格十分昂贵。如何更好地利用GPU设备,发挥GPU的价值,降低成本,是一个亟待解决的问题。 Kubernetes 社区现有的 GPU 分配机制中,GPU 是由 kubelet 分配的,并只支持分配一个或多个完整的 GPU 实例。 这种方法简单可靠,但类似于 CPU 和 Memory,GPU 并不是一直处于高利用率水位,同样存在资源浪费的问题。 因此,Koordinator 希望支持多工作负载共享使用 GPU 设备以节省成本。 此外,GPU 有其特殊性。 比如下面的 NVIDIA GPU 支持的 NVLink 和超卖场景,都需要通过调度器进行中央决策,以获得全局最优的分配结果。
image.png

从图中我们可以发现,虽然该节点有8个 GPU 实例,型号为A100/V100,但 GPU 实例之间的数据传输速度是不同的。 当一个 Pod 需要多个 GPU 实例时,我们可以为 Pod 分配具有最大数据传输速度组合关系的 GPU 实例。 此外,当我们希望一组 Pod 中的 GPU 实例具有最大数据传输速度组合关系时,调度器应该将最佳 GPU 实例批量分配给这些 Pod,并将它们分配到同一个节点。

GPU 资源协议

Koordinator 兼容社区已有的 nvidia.com/gpu资源协议,并且还自定义了扩展资源协议,支持用户更细粒度的分配 GPU 资源。

  • kubernetes.io/gpu-core 代表GPU的计算能力。 与 Kuberetes MilliCPU 类似,我们将 GPU 的总算力抽象为100,用户可以根据需要申请相应数量的 GPU 算力。
  • kubernetes.io/gpu-memory 表示 GPU 的内存容量,以字节为单位。
  • kubernetes.io/gpu-memory-ratio 代表 GPU 内存的百分比。

假设一个节点有4个GPU设备实例,每个GPU设备实例有 8Gi 显存。用户如果期望申请一个完整的 GPU 实例,除了使用 nvidia.com/gpu之外,还可以按照如下方式申请:

apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
spec:
containers:
- name: main
resources:
limits:
kubernetes.io/gpu-core: 100
kubernetes.io/gpu-memory: "8Gi"
requests:
kubernetes.io/gpu-core: 100
kubernetes.io/gpu-memory: "8Gi"

如果期望只使用一个 GPU 实例一半的资源,可以按照如下方式申请:

apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
spec:
containers:
- name: main
resources:
limits:
kubernetes.io/gpu-core: 50
kubernetes.io/gpu-memory: "4Gi"
requests:
kubernetes.io/gpu-core: 50
kubernetes.io/gpu-memory: "4Gi"

设备信息和设备容量上报

在 Koordinator v0.7.0 版本中,单机侧 koordlet 安装后会自动识别节点上是否含有 GPU 设备,如果存在的话,会上报这些 GPU 设备的 Minor ID、 UUID、算力和显存大小到一个类型为 Device CRD 中。每个节点对应一个 Device CRD 实例。Device CRD 不仅支持描述 GPU,还支持类似于 FPGA/RDMA等设备类型,目前 v0.7.0 版本只支持 GPU, 暂未支持这些设备类型。
Device CRD 会被 koord-manager 内的 NodeResource controller 和 koord-scheduler 消费。NodeResource controller 会根据 Device CRD 中描述的信息,换算成 Koordinator 支持的资源协议 kubernetes.io/gpu-core,kubernetes.io/gpu-memory 更新到 Node.Status.Allocatable 和 Node.Status.Capacity 字段,帮助调度器和 kubelet 完成资源调度。gpu-core 表示GPU 设备实例的算力,一个实例的完整算力为100。假设一个节点有 8 个 GPU 设备实例,那么节点的 gpu-core 容量为 8 100 = 800; gpu-memory 表示 GPU 设备实例的显存大小,单位为字节,同样的节点可以分配的显存总量为 设备数量 每个实例的单位容量,例如一个 GPU 设备的显存是 8G,节点上有8 个 GPU 实例,总量为 8 * 8G = 64G。

apiVersion: v1
kind: Node
metadata:
name: node-a
status:
capacity:
koordinator.sh/gpu-core: 800
koordinator.sh/gpu-memory: "64Gi"
koordinator.sh/gpu-memory-ratio: 800
allocatable:
koordinator.sh/gpu-core: 800
koordinator.sh/gpu-memory: "64Gi"
koordinator.sh/gpu-memory-ratio: 800

中心调度分配设备资源

Kuberetes 社区原生提供的设备调度机制中,调度器只负责校验设备容量是否满足 Pod,对于一些简单的设备类型是足够的,但是当需要更细粒度分配 GPU 时,需要中心调度器给予支持才能实现全局最优。
Koordinator 调度器 koord-scheduler 新增了调度插件 DeviceShare,负责精细度设备资源调度。DeviceShare 插件消费 Device CRD,记录每个节点可以分配的设备信息。DeviceShare 在调度时,会把 Pod 的GPU资源请求转换为 Koordinator 的资源协议,并过滤每个节点的未分配的 GPU 设备实例。确保有资源可用后,在 Reserve 阶段更新内部状态,并在 PreBind 阶段更新 Pod Annotation,记录当前 Pod 应该使用哪些 GPU 设备。
DeviceShare 将在后续版本支持 Binpacking 和 Spread 策略,实现更好的设备资源调度能力。

单机侧精准绑定设备信息

Kubernetes 社区在 kubelet 中提供了 DevicePlugin 机制,支持设备厂商在 kubelet 分配好设备后有机会获得设备信息,并填充到环境变量或者更新挂载路径。但是不能支持 中心化的 GPU 精细化调度场景。
针对这个问题, Koordinator 扩展了 koord-runtime-proxy ,支持在 kubelet 创建容器时更新环境变量,注入调度器分配的 GPU 设备信息。

3. 调度器诊断分析

大家在使用 Kubernetes 时经常会遇到一些调度相关的问题:

  1. 我这个 Pod 为什么不能调度?
  2. 这个 Pod 为什么会调度到这个节点,不是应该被另一个打分插件影响到么?
  3. 我新开发了一个插件,发现调度结果不符合预期,但是有不知道哪里出了问题。

要诊断分析这些问题,除了要掌握 Kubernetes 基本的调度机制和资源分配机制外,还需要调度器自身给予支持。但是 Kubernetes kube-scheduler 提供的诊断能力比较有限,有时候甚至没有什么日志可以查看。kube-scheduler 原生是支持通过 HTTP 更改日志等级,可以获得更多日志信息,例如执行如下命令可以更改日志等级到5:

$ curl -X PUT schedulerLeaderIP:10251/debug/flags/v --data '5' 
successfully set klog.logging.verbosity to 5

Koordinator 针对这些问题,实现了一套 Restful API ,帮助用户提升问题诊断分析的效率

分析 Score 结果

PUT /debug/flags/s 允许用户打开 Debug Score 开关,在打分结束后,以Markdown 格式打印 TopN 节点各个插件的分值。例如:

$ curl -X PUT schedulerLeaderIP:10251/debug/flags/s --data '100'
successfully set debugTopNScores to 100

当有新 Pod 调度时,观察 scheduler log 可以看到如下信息

| # | Pod | Node | Score | ImageLocality | InterPodAffinity | LoadAwareScheduling | NodeAffinity | NodeNUMAResource | NodeResourcesBalancedAllocation | NodeResourcesFit | PodTopologySpread | Reservation | TaintToleration |
| --- | --- | --- | ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:|
| 0 | default/curlimage-545745d8f8-rngp7 | cn-hangzhou.10.0.4.51 | 577 | 0 | 0 | 87 | 0 | 0 | 96 | 94 | 200 | 0 | 100 |
| 1 | default/curlimage-545745d8f8-rngp7 | cn-hangzhou.10.0.4.50 | 574 | 0 | 0 | 85 | 0 | 0 | 96 | 93 | 200 | 0 | 100 |
| 2 | default/curlimage-545745d8f8-rngp7 | cn-hangzhou.10.0.4.19 | 541 | 0 | 0 | 55 | 0 | 0 | 95 | 91 | 200 | 0 | 100 |
| 3 | default/curlimage-545745d8f8-rngp7 | cn-hangzhou.10.0.4.18 | 487 | 0 | 0 | 15 | 0 | 0 | 90 | 82 | 200 | 0 | 100 |

找个 Markdown 工具,就可以转为如下表格

#PodNodeScoreLoadAwareSchedulingNodeNUMAResourceNodeResourcesFitPodTopologySpread
0default/curlimage-545745d8f8-rngp7cn-hangzhou.10.0.4.5157787094200
1default/curlimage-545745d8f8-rngp7cn-hangzhou.10.0.4.5057485093200
2default/curlimage-545745d8f8-rngp7cn-hangzhou.10.0.4.1954155091200
3default/curlimage-545745d8f8-rngp7cn-hangzhou.10.0.4.1848715082200

调度插件导出内部状态

像 koord-scheduler 内部的 NodeNUMAResource 、 DeviceShare和ElasticQuota等插件内部都有维护一些状态帮助调度。 koord-scheduler 自定义了一个新的插件扩展接口(定义见下文),并会在初始化插件后,识别该插件是否实现了该接口并调用该接口,让插件注入需要暴露的 RestfulAPI。以 NodeNUMAResource 插件为例,会提供 /cpuTopologyOptions/:nodeName/availableCPUs/:nodeName两个Endpoints,可以查看插件内部记录的 CPU 拓扑信息和分配结果。

type APIServiceProvider interface {
RegisterEndpoints(group *gin.RouterGroup)
}

用户在使用时,按照 /apis/v1/plugins/<pluginName>/<pluginEndpoints>方 式构建 URL 查看数据,例如要查看 /cpuTopologyOptions/:nodeName

$ curl schedulerLeaderIP:10252/apis/v1/plugins/NodeNUMAResources/cpuTopologyOptions/node-1
{"cpuTopology":{"numCPUs":32,"numCores":16,"numNodes":1,"numSockets":1,"cpuDetails":....

查看当前支持的插件 API

为了方便大家使用,koord-scheduler 提供了 /apis/v1/__services__ 查看支持的 API Endpoints

$ curl schedulerLeaderIP:10251/apis/v1/__services__
{
"GET": [
"/apis/v1/__services__",
"/apis/v1/nodes/:nodeName",
"/apis/v1/plugins/Coscheduling/gang/:namespace/:name",
"/apis/v1/plugins/DeviceShare/nodeDeviceSummaries",
"/apis/v1/plugins/DeviceShare/nodeDeviceSummaries/:name",
"/apis/v1/plugins/ElasticQuota/quota/:name",
"/apis/v1/plugins/NodeNUMAResource/availableCPUs/:nodeName",
"/apis/v1/plugins/NodeNUMAResource/cpuTopologyOptions/:nodeName"
]
}

4. 更安全的重调度

在 Koordinator v0.6 版本中我们发布了全新的 koord-descheduler,支持插件化实现需要的重调度策略和自定义驱逐机制,并内置了面向 PodMigrationJob 的迁移控制器,通过 Koordinator Reservation 机制预留资源,确保有资源的情况下发起驱逐。解决了 Pod 被驱逐后无资源可用影响应用的可用性问题。
Koordinator v0.7 版本中,koord-descheduler 实现了更安全的重调度

  • 支持 Evict 限流,用户可以根据需要配置限流策略,例如允许每分钟驱逐多少个 Pod
  • 支持配置 Namespace 灰度重调度能力,让用户可以更放心的灰度
  • 支持按照 Node/Namespace 配置驱逐数量,例如配置节点维度最多只驱逐两个,那么即使有插件要求驱逐该节点上的更多Pod,会被拒绝。
  • 感知 Workload ,如果一个 Workload 正在发布、缩容、已经有一定量的 Pod 正在被驱逐或者一些Pod NotReady,重调度器会拒绝新的重调度请求。目前支持原生的 Deployment,StatefulSet 以及 Kruise CloneSet,Kruise AdvancedStatefulSet。

后续重调度器还会提升公平性,防止一直重复的重调度同一个 workload ,尽量降低重调度对应用的可用性的影响。

5. 其他改动

  • Koordinator 进一步增强了 CPU 精细化调度能力,完全兼容 kubelet ( <= v1.22) CPU Manager static 策略。调度器分配 CPU 时会避免分配被 kubelet 预留的 CPU,单机侧koordlet完整适配了kubelet从1.18到1.22版本的分配策略,有效避免了 CPU 冲突。
  • 资源预留机制支持 AllocateOnce 语义,满足单次预留场景。并改进了 Reservation 状态语义,更加准确描述 Reservation 对象当前的状态。
  • 改进了离线资源(Batch CPU/Memory) 的声明方式,支持limit大于request的资源描述形式,可以方便原burstable类型的任务直接转换为混部模式运行。

你可以通过 Github release[6] 页面,来查看更多的改动以及它们的作者与提交记录。

相关链接

· 9 分钟阅读
Joseph

We are happy to announce the release of Koordinator v0.6.0. Koordinator v0.6.0 brings complete Fine-grained CPU Orchestration, Resource Reservation mechanism, safely Pod Migration mechanism and Descheduling Framework.

Install or Upgrade to Koordinator v0.6.0

Install with helms

Koordinator can be simply installed by helm v3.5+, which is a simple command-line tool, and you can get it from here.

# Firstly add koordinator charts repository if you haven't do this.
$ helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

# [Optional]
$ helm repo update

# Install the latest version.
$ helm install koordinator koordinator-sh/koordinator --version 0.6.0

Upgrade with helm

# Firstly add koordinator charts repository if you haven't do this.
$ helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

# [Optional]
$ helm repo update

# Upgrade the latest version.
$ helm upgrade koordinator koordinator-sh/koordinator --version 0.6.0 [--force]

For more details, please refer to the installation manual.

Fine-grained CPU Orchestration

In Koordinator v0.5.0, we designed and implemented basic CPU orchestration capabilities. The koord-scheduler supports different CPU bind policies to help LSE/LSR Pods achieve better performance.

Now in the v0.6 version, we have basically completed the CPU orchestration capabilities originally designed, such as:

  • Support default CPU bind policy configured by koord-scheduler for LSR/LSE Pods that do not specify a CPU bind policy
  • Support CPU exclusive policy that supports PCPULevel and NUMANodeLevel, which can spread the CPU-bound Pods to different physical cores or NUMA Nodes as much as possible to reduce the interference between Pods.
  • Support Node CPU Orchestration API to helper cluster administrators control the CPU orchestration behavior of nodes. The label node.koordinator.sh/cpu-bind-policy constrains how to bind CPU logical CPUs when scheduling. If set with the FullPCPUsOnly that requires that the scheduler must allocate full physical cores. Equivalent to kubelet CPU manager policy option full-pcpus-only=true. If there is no node.koordinator.sh/cpu-bind-policy in the node's label, it will be executed according to the policy configured by the Pod or koord-scheduler. The label node.koordinator.sh/numa-allocate-strategy indicates how to choose satisfied NUMA Nodes when scheduling. Support MostAllocated and LeastAllocated.
  • koordlet supports the LSE Pods and improve compatibility with existing Guaranteed Pods with static CPU Manager policy.

Please check out our user manual for a detailed introduction and tutorial.

Resource Reservation

We completed the Resource Reservation API design proposal in v0.5, and implemented the basic Reservation mechanism in the current v0.6 version.

When you want to use the Reservation mechanism to reserve resources, you do not need to modify the Pod or the existing workloads(e.g. Deployment, StatefulSet). koord-scheduler provides a simple to use API named Reservation, which allows us to reserve node resources for specified pods or workloads even if they haven't get created yet. You only need to write the Pod Template and the owner information in the ReservationSpec when creating a Reservation. When koord-scheduler perceives a new Reservation object, it will allocate resources to the Reservation object through the normal Pod scheduling process. After scheduling, koord-scheduler will update the success or failure information to ResourceStatus. If the reservation is successful, and the OwnerReference or Labels of the newly created Pod satisfy the owner information declared earlier, then the newly created Pod will directly reuse the resources held by the Reservation. When the Pod is destroyed, the Reservation object can be reused until the Reservation expires.

image

The resource reservation mechanism can help solve or optimize the problems in the following scenarios:

  1. Preemption: Existing preemption does not guarantee that only preempting pods can allocate preempted resources. With a reservation, the scheduler should be able to "lock" resources preventing from allocation of other pods with the same or higher priority.
  2. Descheduling: For the descheduler, it is better to ensure sufficient resources with the reservation before pods get rescheduled. Otherwise, rescheduled pods may not be runnable anymore and make the belonging application disrupted.
  3. Horizontal scaling: Using reservation to achieve more deterministic horizontal scaling. e.g. Submit a reservation and make sure it is available before scaling up replicas.
  4. Resource Pre-allocation: Sometimes we want to pre-allocate node resources for future resource demands even if the resources are not currently allocatable. Reservation can help with this and it should make no physical cost.

Pod Migration Job

Migrating Pods is an important capability that many components (such as descheduler) rely on, and can be used to optimize scheduling or help resolve workload runtime quality issues. We believe that pod migration is a complex process, involving steps such as auditing, resource allocation, and application startup, and is mixed with application upgrading, scaling scenarios, resource operation and maintenance operations by cluster administrators. Therefore, how to manage the stability risk of this process to ensure that the application does not fail due to the migration of Pods is a very critical issue that must be resolved.

The descheduler in the K8s community evicts pods according to different strategies. However, it does not guarantee whether the evicted Pod has resources available after re-creation. If a large number of newly created Pods are in the Pending state when the resources in the cluster are tight, may lower the application availabilities.

Koordinator defines a CRD-based Migration/Eviction API named PodMigrationAPI, through which the descheduler or other components can evict or delete Pods more safely. With PodMigrationJob we can track the status of each process in the migration, and perceive scenarios such as upgrading and scaling of the application.

It's simple to use the PodMigrationJob API. Create a PodMigrationJob with the YAML file below to migrate pod-demo-0.

apiVersion: scheduling.koordinator.sh/v1alpha1
kind: PodMigrationJob
metadata:
name: migrationjob-demo
spec:
paused: false
ttl: 5m
mode: ReservationFirst
podRef:
namespace: default
name: pod-demo-5f9b977566-c7lvk
status:
phase: Pending
$ kubectl create -f migrationjob-demo.yaml
podmigrationjob.scheduling.koordinator.sh/migrationjob-demo created

Then you can query the migration status and query the migration events

$ kubectl get podmigrationjob migrationjob-demo
NAME PHASE STATUS AGE NODE RESERVATION PODNAMESPACE POD NEWPOD TTL
migrationjob-demo Succeed Complete 37s node-1 d56659ab-ba16-47a2-821d-22d6ba49258e default pod-demo-5f9b977566-c7lvk pod-demo-5f9b977566-nxjdf 5m0s

$ kubectl describe podmigrationjob migrationjob-demo
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ReservationCreated 8m33s koord-descheduler Successfully create Reservation "d56659ab-ba16-47a2-821d-22d6ba49258e"
Normal ReservationScheduled 8m33s koord-descheduler Assigned Reservation "d56659ab-ba16-47a2-821d-22d6ba49258e" to node "node-1"
Normal Evicting 8m33s koord-descheduler Try to evict Pod "default/pod-demo-5f9b977566-c7lvk"
Normal EvictComplete 8m koord-descheduler Pod "default/pod-demo-5f9b977566-c7lvk" has been evicted
Normal Complete 8m koord-descheduler Bind Pod "default/pod-demo-5f9b977566-nxjdf" in Reservation "d56659ab-ba16-47a2-821d-22d6ba49258e"

Descheduling Framework

We implemented a brand new Descheduling Framework in v0.6.

The existing descheduler in the community can solve some problems, but we think that there are still many aspects of the descheduler that can be improved, for example, it only supports the mode of periodic execution, and does not support the event-triggered mode. It is not possible to extend and configure custom descheduling strategies without invading the existing code of descheduler like kube-scheduler; it also does not support implementing custom evictor.

We also noticed that the K8s descheduler community also found these problems and proposed corresponding solutions such as #753 Descheduler framework Proposal and PoC #781. The K8s descheduler community tries to implement a descheduler framework similar to the k8s scheduling framework. This coincides with our thinking.

Overall, these solutions solved most of our problems, but we also noticed that the related implementations were not merged into the main branch. But we review these implementations and discussions, and we believe this is the right direction. Considering that Koordiantor has clear milestones for descheduler-related features, we will implement Koordinator's own descheduler independently of the upstream community. We try to use some of the designs in the #753 PR proposed by the community and we will follow the Koordinator's compatibility principle with K8s to maintain compatibility with the upstream community descheduler when implementing. Such as independent implementation can also drive the evolution of the upstream community's work on the descheduler framework. And when the upstream community has new changes or switches to the architecture that Koordinator deems appropriate, Koordinator will follow up promptly and actively.

Based on this descheduling framework, it is very easy to be compatible with the existing descheduling strategies in the K8s community, and users can implement and integrate their own descheduling plugins as easily as K8s Scheduling Framework. At the same time, users are also supported to implement Controller in the form of plugins to realize event-based descheduling scenarios. At the same time, the framework integrates the MigrationController based on PodMigrationJob API and serves as the default Evictor plugin to help safely migrate Pods in various descheduling scenarios.

At present, we have implemented the main body of the framework, including the MigrationController based on PodMigrationJob, which is available as a whole. And we also provide a demo descheduling plugin. In the future, we will migrate and be compatible with the existing descheduling policies of the community, as well as the load balancing descheduling plugin provided for co-location scenarios.

The current framework is still in the early stage of rapid evolution, and there are still many details that need to be improved. Everyone who is interested is welcome to participate in the construction together. We hope that more people can be more assured and simpler to realize the descheduling capabilities they need.

About GPU Scheduling

There are also some new developments in GPU scheduling capabilities that everyone cares about.

During the iteration of v0.6, we completed the design of GPU Share Scheduling, and also completed the design of Gang Scheduling. Development of these capabilities is ongoing and will be released in v0.7.

In addition, in order to explore the mechanism of GPU overcommitment, we have implemented the ability to report GPU Metric in v0.6.

What’s coming next in Koordinator

Don't forget that Koordinator is developed in the open. You can check out our Github milestone to know more about what is happening and what we have planned. For more details, please refer to our milestone. Hope it helps!

· 8 分钟阅读
Jason

In addition to the usual updates to supporting utilities, Koordinator v0.5 adds a couple of new useful features we think you'll like.

Install or Upgrade to Koordinator v0.5.0

Install with helms

Koordinator can be simply installed by helm v3.5+, which is a simple command-line tool, and you can get it from here.

# Firstly add koordinator charts repository if you haven't do this.
$ helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

# [Optional]
$ helm repo update

# Install the latest version.
$ helm install koordinator koordinator-sh/koordinator --version 0.5.0

Upgrade with helm

# Firstly add koordinator charts repository if you haven't do this.
$ helm repo add koordinator-sh https://koordinator-sh.github.io/charts/

# [Optional]
$ helm repo update

# Upgrade the latest version.
$ helm upgrade koordinator koordinator-sh/koordinator --version 0.5.0 [--force]

For more details, please refer to the installation manual.

Fine-grained CPU Orchestration

In this version, we introduced a fine-grained CPU orchestration. Pods in the Kubernetes cluster may interfere with others' running when they share the same physical resources and both demand many resources. The sharing of CPU resources is almost inevitable. e.g. SMT threads (i.e. logical processors) share execution units of the same core, and cores in the same chip share one last-level cache. The resource contention can slow down the running of these CPU-sensitive workloads, resulting in high response latency (RT).

To improve the performance of CPU-sensitive workloads, koord-scheduler provides a mechanism of fine-grained CPU orchestration. It enhances the CPU management of Kubernetes and supports detailed NUMA-locality and CPU exclusions.

Please check out our user manual for a detailed introduction and tutorial.

Resource Reservation

Pods are fundamental units for allocating node resources in Kubernetes, which bind resource requirements with business logic. The scheduler is not able to reserve node resources for specific pods or workloads. We may try using a fake pod to prepare resources by the preemption mechanism. However, fake pods can be preempted by any scheduled pods with higher priorities, which make resources get scrambled unexpectedly.

In Koordinator, a resource reservation mechanism is proposed to enhance scheduling and especially benefits scenarios below:

  1. Preemption: Existing preemption does not guarantee that only preempting pods can allocate preempted resources. With a reservation, the scheduler should be able to "lock" resources preventing from allocation of other pods with the same or higher priority.
  2. De-scheduling: For the descheduler, it is better to ensure sufficient resources with the reservation before pods get rescheduled. Otherwise, rescheduled pods may not be runnable anymore and make the belonging application disrupted.
  3. Horizontal scaling: Using reservation to achieve more deterministic horizontal scaling. e.g. Submit a reservation and make sure it is available before scaling up replicas.
  4. Resource Pre-allocation: Sometimes we want to pre-allocate node resources for future resource demands even if the resources are not currently allocatable. Reservation can help with this and it should make no physical cost.

This feature is still under development. We've finalized the API, feel free to check it out.

type Reservation struct {
metav1.TypeMeta `json:",inline"`
// A Reservation object is non-namespaced.
// It can reserve resources for pods of any namespace. Any affinity/anti-affinity of reservation scheduling can be
// specified in the pod template.
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ReservationSpec `json:"spec,omitempty"`
Status ReservationStatus `json:"status,omitempty"`
}

type ReservationSpec struct {
// Template defines the scheduling requirements (resources, affinities, images, ...) processed by the scheduler just
// like a normal pod.
// If the `template.spec.nodeName` is specified, the scheduler will not choose another node but reserve resources on
// the specified node.
Template *corev1.PodTemplateSpec `json:"template,omitempty"`
// Specify the owners who can allocate the reserved resources.
// Multiple owner selectors and ANDed.
Owners []ReservationOwner `json:"owners,omitempty"`
// By default, the resources requirements of reservation (specified in `template.spec`) is filtered by whether the
// node has sufficient free resources (i.e. ReservationRequest < NodeFree).
// When `preAllocation` is set, the scheduler will skip this validation and allow overcommitment. The scheduled
// reservation would be waiting to be available until free resources are sufficient.
PreAllocation bool `json:"preAllocation,omitempty"`
// Time-to-Live period for the reservation.
// `expires` and `ttl` are mutually exclusive. If both `ttl` and `expires` are not specified, a very
// long TTL will be picked as default.
TTL *metav1.Duration `json:"ttl,omitempty"`
// Expired timestamp when the reservation expires.
// `expires` and `ttl` are mutually exclusive. Defaults to being set dynamically at runtime based on the `ttl`.
Expires *metav1.Time `json:"expires,omitempty"`
}

type ReservationStatus struct {
// The `phase` indicates whether is reservation is waiting for process (`Pending`), available to allocate
// (`Available`) or expired to get cleanup (Expired).
Phase ReservationPhase `json:"phase,omitempty"`
// The `conditions` indicate the messages of reason why the reservation is still pending.
Conditions []ReservationCondition `json:"conditions,omitempty"`
// Current resource owners which allocated the reservation resources.
CurrentOwners []corev1.ObjectReference `json:"currentOwners,omitempty"`
}

type ReservationOwner struct {
// Multiple field selectors are ORed.
Object *corev1.ObjectReference `json:"object,omitempty"`
Controller *ReservationControllerReference `json:"controller,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
}

type ReservationControllerReference struct {
// Extend with a `namespace` field for reference different namespaces.
metav1.OwnerReference `json:",inline"`
Namespace string `json:"namespace,omitempty"`
}

type ReservationPhase string

const (
// ReservationPending indicates the Reservation has not been processed by the scheduler or is unschedulable for
// some reasons (e.g. the resource requirements cannot get satisfied).
ReservationPending ReservationPhase = "Pending"
// ReservationAvailable indicates the Reservation is both scheduled and available for allocation.
ReservationAvailable ReservationPhase = "Available"
// ReservationWaiting indicates the Reservation is scheduled, but the resources to reserve are not ready for
// allocation (e.g. in pre-allocation for running pods).
ReservationWaiting ReservationPhase = "Waiting"
// ReservationExpired indicates the Reservation is expired, which the object is not available to allocate and will
// get cleaned in the future.
ReservationExpired ReservationPhase = "Expired"
)

type ReservationCondition struct {
LastProbeTime metav1.Time `json:"lastProbeTime"`
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
Reason string `json:"reason"`
Message string `json:"message"`
}

QoS Manager

Currently, plugins from resmanager in Koordlet are mixed together, they should be classified into two categories: static and dynamic. Static plugins will be called and run only once when a container created, updated, started or stopped. However, for dynamic plugins, they may be called and run at any time according the real-time runtime states of node, such as CPU suppress, CPU burst, etc. This proposal only focuses on refactoring dynamic plugins. Take a look at current plugin implementation, there are many function calls to resmanager's methods directly, such as collecting node/pod/container metrics, fetching metadata of node/pod/container, fetching configurations(NodeSLO, etc.). In the feature, we may need a flexible and powerful framework with scalability for special external plugins.

The below is directory tree of qos-manager inside koordlet, all existing dynamic plugins(as built-in plugins) will be moved into sub-directory plugins.

pkg/koordlet/qosmanager/
- manager.go
- context.go // plugin context
- /plugins/ // built-in plugins
- /cpubrust/
- /cpusuppress/
- /cpuevict/
- /memoryevict/

We only have the proposal in this version. Stay tuned, further implementation is coming soon!

Multiple Running Hook Modes

Runtime Hooks includes a set of plugins which are responsible for the injections of resource isolation parameters by pod attribute. When Koord Runtime Proxy running as a CRI Proxy, Runtime Hooks acts as the backend server. The mechanism of CRI Proxy can ensure the consistency of resource parameters during pod lifecycle. However, Koord Runtime Proxy can only hijack CRI requests from kubelet for pods, the consistency of resource parameters in QoS class directory cannot be guaranteed. Besides, modification of pod parameters from third-party(e.g. manually) will also break the correctness of hook plugins.

Therefore, a standalone running mode with reconciler for Runtime Hooks is necessary. Under Standalone running mode, resource isolation parameters will be injected asynchronously, keeping eventual consistency of the injected parameters for pod and QoS class even without Runtime Hook Manager.

Some minor works

  1. We fix the backward compatibility issues reported by our users in here. If you've ever encountered similar problem, please upgrade to the latest version.
  2. Two more interfaces were added into runtime-proxy. One is PreCreateContainerHook, which could set container resources setting before creating, and the other is PostStopSandboxHook, which could do the resource setting garbage collecting before pods deleted.
  3. cpuacct.usage is more precise than cpuacct.stat, and cpuacct.stat is in USER_HZ unit, while cpuacct.usage is nanoseconds. After thorough discussion, we were on the same page that we replace cpuacct.stat with cpuacct.usage in koordlet.
  4. Koordlet needs to keep fetching data from kubelet. Before this version, we only support accessing kubelet via read-only port over HTTP. Due to security concern, we've enabled HTTPS access in this version. For more details, please refer to this PR.

What’s coming next in Koordinator

Don't forget that Koordinator is developed in the open. You can check out our Github milestone to know more about what is happening and what we have planned. For more details, please refer to our milestone. Hope it helps!