关于腾讯云:Kubernetes-资源拓扑感知调度优化

7次阅读

共计 9652 个字符,预计需要花费 25 分钟才能阅读完成。

作者

星辰算力团队,星辰算力平台基于深刻优化云原生对立接入和多云调度,加固容器运行态隔离,开掘技术增量价值,平台承载了腾讯外部的 CPU 和异构算力服务,是腾讯外部大规模离线作业、资源对立调度平台。

背景

问题源起

近年来,随着腾讯外部自研上云我的项目的一直倒退,越来越多的业务开始应用云原生形式托管本人的工作负载,容器平台的规模因而一直增大。以 Kubernetes 为底座的云原生技术极大推动了云原生畛域的倒退,未然成为各大容器平台事实上的技术标准。在云原生场景下,为了最大化实现资源共享,单台宿主机往往会运行多个不同用户的计算工作。如果在宿主机内没有进行精细化的资源隔离,在业务负载顶峰时间段,多个容器往往会对资源产生强烈的竞争,可能导致程序性能的急剧下降,次要体现为:

  1. 资源调度时频繁的上下文切换工夫
  2. 频繁的过程切换导致的 CPU 高速缓存生效

因而,在云原生场景下须要针对容器资源分配加以精细化的限度,确保在 CPU 利用率较高时,各容器之间不会产生强烈竞争从而引起性能降落。

调度场景

腾讯星辰算力平台承载了全公司的 CPU 和 GPU 算力服务,领有着海量多类型的计算资源。以后,平台承载的少数重点服务偏离线场景,在业务日益增长的算力需要下,提供源源不断的低成本资源,继续晋升可用性、服务质量、调度能力,笼罩更多的业务场景。然而,Kubernetes 原生的调度与资源绑定性能曾经无奈满足简单的算力场景,亟需对资源进行更加精细化的调度,次要体现为:

  1. Kubernetes 原生调度器无奈感知节点资源拓扑信息导致 Pod 生产失败

kube-scheduler 在调度过程中并不感知节点的资源拓扑,当 kube-scheduler 将 Pod 调度到某个节点后,kubelet 如果发现节点的资源拓扑亲和性要求无奈满足时,会回绝生产该 Pod,当通过内部管制环(如 deployment)来部署 Pod 时,则会导致 Pod 被重复创立 –> 调度 –> 生产失败的死循环。

  1. 基于离线虚拟机的混部计划导致的节点理论可用 CPU 外围数变动

面对运行在线业务的云主机均匀利用率较低的事实,为充分利用闲暇资源,可将离线虚拟机和在线虚拟机混合部署,解决公司离线计算需要,晋升自研上云资源均匀利用率。在保障离线不烦扰在线业务的状况下,腾讯星辰算力基于自研内核调度器 VMF 的反对,能够将一台机器上的闲时资源充分利用起来,生产低优先级的离线虚拟机。因为 VMF 的不偏心调度策略,离线虚拟机的理论可用外围数受到在线虚拟机的影响,随着在线业务的忙碌水平一直变动。因而,kubelet 通过 cadvisor 在离线宿主机外部采集到的 CPU 外围数并不精确,导致了调度信息呈现偏差。

  1. 资源的高效利用须要更加精细化的调度粒度

kube-scheduler 的职责是为 Pod 抉择一个适合的 Node 实现一次调度。然而,想对资源进行更高效的利用,原生调度器的性能还远远不够。在调度时,咱们心愿调度器可能进行更细粒度的调度,比方可能感知到 CPU 外围、GPU 拓扑、网络拓扑等等,使得资源利用形式更加正当。

准备常识

cgroups 之 cpuset 子系统

cgroups 是 Linux 内核提供的一种能够限度单个过程或者多个过程所应用资源的机制,能够对 CPU、内存等资源实现精细化的管制。Linux 下的容器技术次要通过 cgroups 来实现资源管制。

在 cgroups 中,cpuset 子系统能够为 cgroups 中的过程调配独立的 CPU 和内存节点。通过将 CPU 外围编号写入 cpuset 子系统中的 cpuset.cpus 文件中或将内存 NUMA 编号写入 cpuset.mems 文件中,能够限度一个或一组过程只应用特定的 CPU 或者内存。

侥幸的是,在容器的资源限度中,咱们不须要手动操作 cpuset 子系统。通过连贯容器运行时(CRI)提供的接口,能够间接更新容器的资源限度。

// ContainerManager contains methods to manipulate containers managed by a
// container runtime. The methods are thread-safe.
type ContainerManager interface {
    // ......
    // UpdateContainerResources updates the cgroup resources for the container.
    UpdateContainerResources(containerID string, resources *runtimeapi.LinuxContainerResources) error
    // ......
}

NUMA 架构

非对立内存拜访架构(英语:Non-uniform memory access,简称 NUMA)是一种为多处理器的电脑设计的内存架构,内存拜访工夫取决于内存绝对于处理器的地位。在 NUMA 下,处理器拜访它本人的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。古代多核服务器大多采纳 NUMA 架构来进步硬件的可伸缩性。

从图中能够看出,每个 NUMA Node 有独立的 CPU 外围、L3 cache 和内存,NUMA Node 之间相互连接。雷同 NUMA Node 上的 CPU 能够共享 L3 cache,同时拜访本 NUMA Node 上的内存速度更快,跨 NUMA Node 拜访内存会更慢。因而,咱们该当为 CPU 密集型利用调配同一个 NUMA Node 的 CPU 外围,确保程序的部分性能失去充沛满足。

Kubernetes 调度框架

Kubernetes 自 v1.19 开始正式稳固反对调度框架,调度框架是面向 Kubernetes 调度器的一种插件架构,它为现有的调度器增加了一组新的“插件”API,插件会被编译到调度器之中。这为咱们自定义调度器带来了福音。咱们能够无需批改 kube-scheduler 的源代码,通过实现不同的调度插件,将插件代码与 kube-scheduler 编译为同一个可执行文件中,从而开发出自定义的扩大调度器。这样的灵活性扩大不便咱们开发与配置各类调度器插件,同时无需批改 kube-scheduler 的源代码的形式使得扩大调度器能够疾速更改依赖,更新到最新的社区版本。

调度器的次要扩大点如上图所示。咱们扩大的调度器次要关怀以下几个步骤:

  1. PreFilterFilter

这两个插件用于过滤出不能运行该 Pod 的节点,如果任何 Filter 插件将节点标记为不可行,该节点都不会进入候选汇合,持续前面的调度流程。

  1. PreScoreScoreNormalizeScore

这三个插件用于对通过过滤阶段的节点进行排序,调度器将为每个节点调用每个评分插件,最终评分最高的节点将会作为最终调度后果被选中。

  1. ReserveUnreserve

这个插件用于在 Pod 真正被绑定到节点之前,对资源做一些预留工作,保障调度的一致性。如果绑定失败则通过 Unreserve 来开释预留的资源。

  1. Bind

这个插件用于将 Pod 绑定到节点上。默认的绑定插件只是为节点指定 spec.nodeName 来实现调度,如果咱们须要扩大调度器,加上其余的调度后果信息,就须要禁用默认 Bind 插件,替换为自定义的 Bind 插件。

国内外技术钻研现状

目前 Kubernetes 社区、Volcano 开源社区均有对于拓扑感知相干的解决方案,各计划有局部相同之处,但各自都有局限性,无奈满足星辰算力的简单场景。

Kubernetes 社区

Kubernetes 社区 scheduling 兴趣小组针对拓扑感知调度也有过一套解决方案,这个计划次要是由 RedHat 来主导,通过 scheduler-plugins 和 node-feature-discovery 配合实现了思考节点拓扑的调度办法。社区的办法仅仅思考节点是否可能在满足 kubelet 配置要求的状况下,实现调度节点筛选和打分,并不会执行绑核,绑核操作依然交给 kubelet 来实现,相干提案在这里。具体实现计划如下:

  1. 节点上的 nfd-topology-updater 通过 gRPC 上报节点拓扑到 nfd-master 中(周期 60s)。
  2. nfd-master 更新节点拓扑与分配情况到 CR 中(NodeResourceTopology)。
  3. 扩大 kube-scheduler,进行调度时思考 NodeTopology。
  4. 节点 kubelet 实现绑核工作。

该计划存在较多的问题,不能解决生产实践中的需要:

  1. 具体外围调配依赖 kubelet 实现,因而调度器只会思考资源拓扑信息,并不会抉择拓扑,调度器没有资源预留。这导致了节点调度与拓扑调度不在同一个环节,会引起数据不统一问题。
  2. 因为具体外围调配依赖 kubelet 实现,所以已调度 Pod 的拓扑信息须要依附 nfd-worker 每隔 60s 汇报一次,导致拓扑发现过慢因而使得数据不统一问题更加重大,参见这里。
  3. 没有辨别须要拓扑亲和的 pod 和一般的 pod,容易造成开启拓扑性能的节点高优资源节约。

Volcano 社区

Volcano 是在 Kubernetes 上运行高性能工作负载的容器批量计算引擎,隶属于 CNCF 孵化我的项目。在 v1.4.0-Beta 版本中进行了加强,公布了无关 NUMA 感知的个性。与 Kubernetes 社区 scheduling 兴趣小组的实现形式相似,真正的绑核并未独自实现,间接采纳的是 kubelet 自带的性能。具体实现计划如下:

  1. resource-exporter 是部署在每个节点上的 DaemonSet,负责节点的拓扑信息采集,并将节点信息写入 CR 中(Numatopology)。
  2. Volcano 依据节点的 Numatopology,在调度 Pod 时进行 NUMA 调度感知。
  3. 节点 kubelet 实现绑核工作。

该计划存在的问题根本与 Kubernetes 社区 scheduling 兴趣小组的实现形式相似,具体外围调配依赖 kubelet 实现。尽管调度器尽力放弃与 kubelet 统一,但因为无奈做资源预留,依然会呈现不统一的问题,在高并发场景下尤其显著。

小结

基于国内外钻研现状的后果进行剖析,开源社区在节点资源绑定方面还是心愿交给 kubelet,调度器尽量保障与 kubelet 的统一,能够了解这比拟合乎社区的方向。因而,目前各个计划的典型实现都不完满,无奈满足腾讯星辰算力的要求,在简单的生产环境中咱们须要一套更加持重、扩展性更好的计划。因而,咱们决定从各个计划的架构长处登程,摸索出一套更加弱小的、贴合腾讯星辰算力理论场景的资源精细化调度加强计划。

问题剖析

离线虚拟机节点理论可用 CPU 外围数变动

从 1.2 节中咱们能够晓得,腾讯星辰算力应用了基于离线虚拟机的混部计划,节点理论的 CPU 可用外围数会收到在线业务的峰值影响从而变动。因而,kubelet 通过 cadvisor 在离线宿主机外部采集到的 CPU 外围数并不精确,这个数值是一个固定值。因而,针对离线资源咱们须要调度器通过其余的形式来获取节点的理论算力。

目前调度和绑核都不能到离线虚拟机的理论算力,导致工作绑定到在线烦扰比较严重的 NUMA node,资源竞争十分重大使得工作的性能降落。

侥幸的是,咱们在物理机上能够采集到离线虚拟机每个 NUMA node 上理论可用的 CPU 资源比例,通过折损公式计算出离线虚拟机的理论算力。接下来就只须要让调度器在调度时可能感知到 CPU 拓扑以及理论算力,从而进行调配。

精细化调度须要更强的灵活性

通过 kubelet 自带的 cpumanager 进行绑核总是会对该节点上的所有 Pod 均失效。只有 Pod 满足 Guaranteed 的 QoS 条件,且 CPU 申请值为整数,都会进行绑核。然而,有些 Pod 并不是高负载类型却独占 CPU,这种形式的形式很容易造成开启拓扑性能的节点高优资源节约。

同时,对于不同资源类型的节点,其拓扑感知的要求也是不一样的。例如,星辰算力的资源池中也含有较多碎片虚拟机,这部分节点不是混部形式生产进去的,相比而言资源稳固,然而规格很小(如 8 核 CVM,每个 NUMA Node 有 4 核)。因为大多数工作规格都会超过 4 核,这类资源就在应用过程中就能够跨 NUMA Node 进行调配,否则很难匹配。

因而,拓扑感知调度须要更强的灵活性,适应各种外围调配与拓扑感知场景。

调度计划须要更强的扩展性

调度器在形象拓扑资源时,须要思考扩展性。对于今后可能会须要调度的扩大资源,如各类异构资源的调度,也可能在这套计划中轻松应用,而不仅仅是 cgroups 子系统中含有的资源。

防止超线程带来的 CPU 竞争问题

在 CPU 外围竞争较为强烈时,超线程可能会带来更差的性能。更加现实的调配形式是将一个逻辑核调配给高负载利用,另一个逻辑核调配给不忙碌的利用,或者是将两个峰谷时刻相同的利用调配到同一个物理外围上。同时,咱们防止将同一个利用调配到同一个物理外围的两个逻辑外围上,这样很可能造成 CPU 竞争问题。

解决方案

为了充沛解决上述问题,并思考到将来的扩展性,咱们设计了一套精细化调度的计划,命名为 cassini。整套解决方案蕴含三个组件和一个 CRD,独特配合实现资源精细化调度的工作。

注:cassini 这个名称来源于出名的土星探测器卡西尼 - 惠更斯号,对土星进行了精准无误的探测,借此名来象征更加精准的拓扑发现与调度。

总体架构

各模块职责如下:

  • cassini-worker:负责收集节点资源拓扑、执行资源绑定工作,作为 DaemonSet 在每个节点上运行。
  • cassini-master:从内部零碎负责收集节点个性(如节点的 offline_capacity,节点电力状况),作为 controller 采纳 Deployment 形式运行。
  • scheduler-plugins:新增调度插件的扩大调度器替换原生调度器,在节点绑定的同时还会调配拓扑调度后果,作为动态 Pod 在每个 master 节点上运行。

调度整体流程如下:

  1. cassini-worker 启动,收集节点上的拓扑资源信息。
  2. 创立或更新 NodeResourceTopology(NRT)类型的 CR 资源,用于记录节点拓扑信息。
  3. 读取 kubelet 的 cpu_manager_state 文件,将已有容器的 kubelet 绑核后果 patch 到 Pod annotations 中。
  4. cassini-master 依据内部零碎获取到的信息来更新对应节点的 NRT 对象。
  5. 扩大调度器 scheduler-plugins 执行 Pod 调度,依据 NRT 对象感知到节点的拓扑信息,调度 Pod 时将拓扑调度构造写到 Pod annotations 中。
  6. 节点 kubelet 监听并筹备启动 Pod。
  7. 节点 kubelet 调用容器运行时接口启动容器。
  8. cassini-worker 周期性地拜访 kubelet 的 10250 端口来 List 节点上的 Pod 并从 Pod annotations 中获取调度器的拓扑调度后果。
  9. cassini-worker 调用容器运行时接口来更改容器的绑核后果。

整体能够看出,cassini-worker 在节点上收集更具体的资源拓扑信息,cassini-master 从内部零碎集中获取节点资源的附加信息。scheduler-plugins 扩大了原生调度器,以这些附加信息作为决策依据来进行更加精细化的调度,并将后果写到 Pod annotations 中。最终,cassini-worker 又承当了执行者的职责,负责落实调度器的资源拓扑调度后果。

API 设计

NodeResourceTopology(NRT)是用于抽象化形容节点资源拓扑信息的 Kubernetes CRD,这里次要参考了 Kubernetes 社区 scheduling 兴趣小组的设计。每一个 Zone 用于形容一个形象的拓扑区域,ZoneType 来形容其类型,ResourceInfo 来形容 Zone 内的资源总量。

// Zone represents a resource topology zone, e.g. socket, node, die or core.
type Zone struct {
    // Name represents the zone name.
    // +required
    Name string `json:"name" protobuf:"bytes,1,opt,name=name"`

    // Type represents the zone type.
    // +kubebuilder:validation:Enum=Node;Socket;Core
    // +required
    Type ZoneType `json:"type" protobuf:"bytes,2,opt,name=type"`

    // Parent represents the name of parent zone.
    // +optional
    Parent string `json:"parent,omitempty" protobuf:"bytes,3,opt,name=parent"`

    // Costs represents the cost between different zones.
    // +optional
    Costs CostList `json:"costs,omitempty" protobuf:"bytes,4,rep,name=costs"`

    // Attributes represents zone attributes if any.
    // +optional
    Attributes map[string]string `json:"attributes,omitempty" protobuf:"bytes,5,rep,name=attributes"`

    // Resources represents the resource info of the zone.
    // +optional
    Resources *ResourceInfo `json:"resources,omitempty" protobuf:"bytes,6,rep,name=resources"`
}

留神到,为了更强的扩展性,每个 Zone 内加上了一个 Attributes 来形容 Zone 上的自定义属性。如 4.1 节中所示,咱们将采集到的离线虚拟机理论算力写入到 Attributes 字段中,来形容每个 NUMA Node 理论可用算力。

调度器设计

扩大调度器在原生调度器根底上扩大了新的插件,大体如下所示:

  1. Filter:读取 NRT 资源,依据每个拓扑内的理论可用算力以及 Pod 拓扑感知要求来筛选节点并抉择拓扑。
  2. Score:依据 Zone 个数打分来打分,Zone 越多分越低(跨 Zone 会带来性能损失)。
  3. Reserve:在真正绑定前做资源预留,防止数据不统一,kube-scheduler 的 cache 中也有相似的 assume 性能。
  4. Bind:禁用默认的 Bind 插件,在 Bind 时退出 Zone 的抉择后果,附加在 annotations 中。

通过 TopologyMatch 插件使得调度器在调度时可能思考节点拓扑信息并进行拓扑调配,并通过 Bind 插件将后果附加在 annotations 中。

值得一提的是,这里还额定实现了对于节点电力调度等更多维度调度的调度器插件。

master 设计

cassini-master 是中控组件,从内部来采集一些节点上无奈采集的资源信息。咱们从物理上采集到离线虚拟机的理论可用算力,由 cassini-master 负责将这类后果附加到对应节点的 NRT 资源中。该组件将对立资源收集的性能进行了剥离,不便更新与扩大。

worker 设计

cassini-worker 是一个较为简单的组件,作为 DaemonSet 在每个节点上运行。它的职责分两局部:

  1. 采集节点上的拓扑资源。
  2. 执行调度器的拓扑调度后果。

资源采集

资源拓扑采集次要是通过从 /sys/devices 下采集零碎相干的硬件信息,并创立或更新到 NRT 资源中。该组件会 watch 节点 kubelet 的配置信息并上报,让调度器感知到节点的 kubelet 的绑核策略、预留资源等信息。

因为硬件信息简直不变动,默认会较长时间采集一次并更新。但 watch 配置的事件是实时的,一旦 kubelet 配置后会立即感知到,不便调度器依据节点的配置进行不同的决策。

拓扑调度后果执行

拓扑调度后果执行是通过周期性地 reconcile 来实现制订容器的拓扑调配。

  1. 获取 Pod 信息

为了避免每个节点的 cassini-worker 都 watch kube-apiserver 造成 kube-apiserver 的压力,cassini-worker 改用周期性拜访 kubelet 的 10250 端口的形式,来 List 节点上的 Pod 并从 Pod annotations 中获取调度器的拓扑调度后果。同时,从 status 中还能够获取到每个容器的 ID 与状态,为拓扑资源的调配创立了条件。

  1. 记录 kubelet 的 CPU 绑定信息

在 kubelet 开启 CPU 外围绑定时,扩大调度器将会跳过所有的 TopologyMatch 插件。此时 Pod annotations 中不会蕴含拓扑调度后果。在 kubelet 为 Pod 实现 CPU 外围绑定后,会将后果记录在 cpu_manager_state 文件中,cassini-worker 读取该文件,并将 kubelet 的绑定后果 patch 到 Pod annotations 中,供后续调度做判断。

  1. 记录 CPU 绑定信息

依据 cpu_manager_state 文件,以及从 annotations 中获取的 Pod 的拓扑调度后果,生成本人的 cassini_cpu_manager_state 文件,该文件记录了节点上所有 Pod 的外围绑定后果。

  1. 执行拓扑调配

依据 cassini_cpu_manager_state 文件,调用容器运行时接口,实现最终的容器外围绑定工作。

优化后果

根据上述精细化调度计划,咱们对一些线上的工作进行了测试。此前,用户反馈任务调度到一些节点后计算性能较差,且因为 steal_time 升高被频繁驱赶。在替换为拓扑感知调度的解决方案后,因为拓扑感知调度能够细粒度地感知到每个 NUMA 节点的离线理论算力(offline_capacity),工作会被调度到适合的 NUMA 节点上,测试工作的训练速度可晋升至原来的 3 倍,与业务在高优 CVM 的耗时相当,且训练速度较为稳固,资源失去更正当地利用。

同时,在应用原生调度器的状况下,调度器无奈感知离线虚拟机的理论算力。当任务调度到某个节点上后,该节点 steal_time 会因而升高,工作无法忍受这样的忙碌节点就会由驱赶器发动 Pod 的驱赶。在此状况下,如果采纳原生调度器,将会引起重复驱赶而后重复调度的状况,导致 SLA 收到较大影响。通过本文所述的解决方案后,可将 CPU 抢占的驱赶率大大降落至物理机程度。

总结与瞻望

本文从理论业务痛点登程,首先简略介绍了腾讯星辰算力的业务场景与精细化调度相干的各类背景常识,而后充沛调研国内外钻研现状,发现目前已有的各种解决方案都存在局限性。最初通过痛点问题剖析后给出了相应的解决方案。通过优化后,资源失去更正当地利用,原有测试工作的训练速度能晋升至原来的 3 倍,CPU 抢占的驱赶率大大降低至物理机程度。

将来,精细化调度将会笼罩更多的场景,包含在 GPU 虚拟化技术下对 GPU 整卡、碎卡的调度,反对高性能网络架构的调度,电力资源的调度,反对超售场景,配合内核调度共同完成等等。

对于咱们

更多对于云原生的案例和常识,可关注同名【腾讯云原生】公众号~

福利:

①公众号后盾回复【手册】,可取得《腾讯云原生路线图手册》&《腾讯云原生最佳实际》~

②公众号后盾回复【系列】,可取得《15 个系列 100+ 篇超实用云原生原创干货合集》,蕴含 Kubernetes 降本增效、K8s 性能优化实际、最佳实际等系列。

③公众号后盾回复【白皮书】,可取得《腾讯云容器平安白皮书》&《降本之源 - 云原生老本治理白皮书 v1.0》

④公众号后盾回复【光速入门】,可取得腾讯云专家 5 万字精髓教程,光速入门 Prometheus 和 Grafana。

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

正文完
 0