目录

私有化交付平台——调度部署篇

容器调度通过一系列规则将容器分配到各个节点上,包括容器资源需求,节点的负载情况,容器、节点间的亲和性等。

该交付平台调度模块参照 Kube-Scheduler 框架,并对相应的流程进行简化,针对相关业务对一些调度插件进行特化,从而实现专用的调度器。

Kube-Scheduler

Kube-Scheduler 是 K8s 容器平台的调度器模块,用于将 Pod 放置到合适的节点上,相应节点上的 Kubelet 监听新调度的 Pod 并运行。

Kube-Scheduler 通过 K8s 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。

Kube-Scheduler主要包括调度(Schedule)和绑定(Binding)两个阶段。其中,调度由预选(Predicates)和优选(Priorities)两个核心过程构成。

  • 调度阶段为 Pod 选择一个合适的节点,是顺序执行的。
  • 绑定阶段将调度结果提交给集群。是并发执行。
  • 无论是在调度还是绑定过程中,如果发生错误或者 Pod 无法成功调度,那么 Pod 就会被重新放回调度队列,等待重新调度。

调度框架

调度框架的核心是定义一组扩展点,用户可以实现扩展点定义的接口来接入自己的调度逻辑。调度框架在执行调度工作流时,遇到对应的扩展点时,将调用用户注册的扩展。

调度框架中的扩展点有些可以改变调度程序的决策方法,有些扩展点只是发送一个通知。

/posts/scheduler/schedule-plugin.png
Kube-Scheduler调度插件框架
  • QueueSort 扩展用于确定 Pod 调度顺序,用以决定调度的优先级。
  • Pre-filter 扩展用于对 Pod 进行预处理,并对集群做一些基本检查。
  • Filter 扩展用于实现预选逻辑,过滤掉不符合要求的节点。
  • Post-filter 扩展用于实现可选节点列表相关的通知机制。
  • Scoring 扩展用于实现优选逻辑,按照规则从可选节点列表中选出得分最高的节点。
  • Normalize scoring 扩展用于对得分进行修正。
  • Reserve 扩展可以获得节点为该 Pod 预留的资源,避免 Pod 绑定发生资源冲突。
  • Permit 扩展用于最终准入、推迟 Pod 与节点的绑定。
  • Bind 扩展用于将 Pod 绑定到节点上,结合 Pre-bind 扩展对绑定进行预处理,结合 Post-bind 扩展对绑定结果进行通知。

扩展的调用顺序为:

  • 如果某个扩展点没有配置对应的扩展,调度框架将使用默认插件中的扩展。
  • 如果为某个扩展点配置且激活了扩展,则调度框架将先调用默认插件的扩展,再调用配置中的扩展。

完整Pod调度过程

  • 客户端通过 API-Server 的 API 或者 kubectl 工具创建 Pod 资源。

  • API-Server 收到请求后将数据持久化到 etcd 中。

  • Kube-Scheduler 监控 API-Server,将未分配 host 的 Pod 放入到调度队列中,并按优先级进行排序。

  • 从调度队列中不断弹出 Pod,开始完整的调度周期(预选、优选、绑定)。并将绑定的结果提交到 API-Server,持久化到 etcd 中。

  • Kubelet 监控 API-Server,发现新增了调度到本 Node 的 Pod,执行创建 Pod 的操作。

交付平台

该私有化交付项目存在以下特点:

  • 平台底座容器平台基于自研,不是通用的 K8s。
  • 项目应用数较少,集群的规模较小,一般为几台到十几台服务器,因此需要最大限度提升资源利用率。
  • 交付的应用主要对 GPU、NVMe 资源有强要求,并且需要通过 Quota 机制复用此类资源。
  • 项目中存在大量应用共生与极个别的应用互斥的要求。
  • 交付项目中,所有服务不可被驱逐,要么全部调度成功,要么全失败。
  • 交付项目中,应按依赖顺序启动,即当数据库服务可以对外服务时,才启动业务服务。
  • 交付平台部署的服务中,存在平台级应用(如计算平台),用户基于计算平台可以更改计算实例,即某些服务可以直接对接底座容器平台。

借鉴 Kube-Scheduler,并结合交付项目的特点,我们对调度框架进行了简化与改造。

统一调度

由于交付规模较小,并且实例部署不存在抢占,我们采用串行的统一调度(类似于gang scheduling)方式进行调度。

统一调度意味着每次都基于全量的服务实例进行调度。为了简化交付操作复杂度,交付平台最核心的目标是一键部署,这意味着一次安装操作可以将所有应用解析出来进行调度,这种全局调度便于找到最优的部署策略,避免逐个应用调度引起频繁的驱逐腾挪。

新安装一个实例时,所有已部署的实例同样跟着参与调度。对于有状态的应用,不应改变其调度结果(因为集群并没有将存储进行虚拟化);对于无状态的应用,尽量保证其调度结果,当新的实例所需的资源不够时,会尝试腾挪此类应用以合并资源碎片。

这种统一调度可以将流程简化为sort->filter->score->bind。不通过预分配行为来腾挪资源,利用 sort 阶段将重资源的实例顺序提前的方式简化资源腾挪。

专用调度插件

为满足交付项目的需求,我们主要定制了以下插件:

  • 基于 GPU 显存与类型的调度插件。
  • 基于 NVMe 按 label 进行共享的调度插件。

此外我们简化了 Pod 亲和性调度策略:

  • 对于服务共生,我们抽象出 App Sidecar 概念来确保两个应用的实例可以调度到同一节点上。
  • 对于服务互斥,我们采用灵活地植入 node label 的方式避免应用部署到同一节点上。

部署顺序

K8s 支持通过 init containers 来确保一个 Deployment 中的若干的 container 按指定的顺序部署。在微服务架构下,显然无法将几十上百个 container 放在一个Deployment 中,因此需要一个更完善的机制保证应用的部署顺序。

kapp 将一组资源视作应用,其提供了两个特别有价值的功能特性:

  • 依赖感知部署。通过定义两个 annotations kapp.k14s.io/change-groupkapp.k14s.io/change-rule 对一组资源进行排序,后者定义对前者的依赖,只有保证依赖的change-group所有声明方就绪后,才部署当前资源。
  • 配置变更实时生效。通过将 ConfigMap 版本化的方式,让关联的应用可以在其变更后自动重启。

参考 kapp 依赖感知的实现,我们通过以下策略实现服务按序部署:

  • 将服务间的依赖层级映射为 rank。
  • 按 rank 启动实例,结合健康检查,确保某一 rank 的所有实例就绪后启动下一 rank 的服务。

可扩展性

由于交付平台会部署计算平台,而计算平台需要调整计算实例的个数,因此需要参与调度部署的流程。这意味着调度部署需要和编排解耦,计算平台需要利用调度器提供的 api/sdk 来触发实例调度与部署。这就无法避免引入了高层平台对底层调度模块的引用耦合,这驱使我们将系统架构由显式调用风格切换到以数据共享的风格上。

架构演进

为增加平台的可扩展性,我们将交付平台的架构由同步架构演进成异步架构。

同步架构

同步架构采用显式调用风格,架构图如下:

/posts/scheduler/sync-arch.png
同步部署架构
  • 用户输入系统配置,选择相应的应用提交到交付平台上。

  • 交付平台通过编排工具,从包管理器中选择相应的服务模板,进行配置渲染,生成一组应用 Services。

  • 编排后的 Services 传递给调度器进行资源分配,并根据副本数、AppSidecar 切分成若干 Instances,部署到自研平台。

  • 上层计算平台通过调用交付平台的 schedule api 创建计算实例。

    p.s. 此架构下 Service 类似于 K8s Deployment,Instance 类似于 Pod。

异步架构

异步架构采用以资源为中心的异步处理方式,使用 K8s Operator 实现编排、调度、部署的解耦。

/posts/scheduler/async-arch.png
异步部署架构
  • 交付平台启动器将其所依赖的服务(API-Server, Service-Controller, Instance-Controller)部署到容器平台上。
  • 用户输入系统配置,选择相应的应用提交到交付平台上。
  • 交付平台通过编排工具,从包管理器中选择相应的服务模板,进行配置渲染,生成一组应用 Services,提交到 API-Server。
  • Service-Controller 监控 API-Server 中的 Service CR,拆分成 Instances 并进行统一调度,随后提交到 API-Server。
  • Instance-Controller 监听 Instance CR,并部署到容器平台上。
  • 上层计算平台通过提交 Service CR 创建计算实例。

异步架构的优势:

  • 使用 K8s 声明式架构,便于向云原生演进,可以无缝地兼容 Helm chart。
  • 增强平台扩展性。调度不再局限于交付平台,而更贴近容器平台,这样在交付平台之上可以扩展出高动态性的计算平台,按需生成计算实例调度部署到容器平台上。这也是架构改造的核心原因。
  • 编排、调度、部署相解耦。异步的处理方式避免了用户长时间的等待,简化各组件的网络中断重试、高可用机制的实现。

引用