作者简介
本文作者为携程Cloud Container团队的鸿飞,静雪,诗燕。该团队负责K8s容器平台的研发和优化工作,专注于推动基础设施云原生架构升级,以及创新产品的研发和落地,持续提升资源利用率、弹性和治理能力。
一、背景介绍
携程K8s容器服务承载着各BU的核心业务,容器数量超过10W,并仍然以每年数倍的速度增长。调度体系也经历了从Mesos上的自研调度器到K8s fork版本的调度器演进过程。
业务目标上,最初调度器只服务于在线应用发布,现在则致力于打造统一弹性调度体系,随着容器化的深入,逐渐完成统一资源池的构造,在此基础上持续提升资源效率,以及应对流量变化、灾难甚至业务国际化需求的混合弹性调度能力构建。实现上,我们当前以K8s官方版本调度器为基础,融合了在公司业务演进过程中积累的经验,进行大量改造和优化。
本文将部分分享Cloud Container团队在打造统一调度接入能力、算法和性能优化等方面的实践。
二、统一调度接入能力优化
整个K8s容器平台上已经接入了多种不同类型的应用,包括在线应用、Redis、MySQL、Spark Job、AI训练等,这些应用为了满足自身业务目标,会存在多样性的调度需求。同时调度体系本身也会基于资源统一调配、对资源效率持续优化的目的,也要能对调度行为进行透明干预。
如果要打造一个统一调度的资源池,为后续价值释放创造尽可能大的基数,就必须收口调度能力。构建统一调度体系的一大挑战,便是如何通过一个调度器,来抽象和管理这些需求,在业务和调度体系间构建一个良好的解耦边界,我们总体的解决思路可以用下图描述。
首先需要对共性需求进行整理,通过Configmap或者CRD抽象成Policy。对于一些需要参数化的Policy,还可以通过模板机制创建一个中间抽象PolicyTemplate。平台方或者业务方可以针对一类Pod创建Binding实例,指明需要绑定的Policy;或者绑定哪一个PolicyTemplate,并在Binding对象实例中指明用来渲染模板的参数集。
平台可以利用Pod绑定的Policy,重写Pod的Spec;也可以由Controller或者Scheduler来消费这些Policy,从而动态配置针对Pod的行为。当然,有时也可以将Binding对象简化为使用Pod的Annotation实现。以下,将用两个实际场景来说明。
2.1 算法集参数化配置
我们知道,K8s scheduler调度过程分为预选(Predicate)和优选(Priority)两个阶段,每个阶段又都有一系列的算法参与计算。Pod所属的业务类型不同,对Pod的调度算法选取以及权重参数等可能不同。在原生调度器中,调度器初始化入口处可以根据配置文件或者configmap来读取schedulerapi.Policy,从而配置调度器的算法集和参数。
type Policy struct {
metav1.TypeMeta
Predicates []PredicatePolicy
Priorities []PriorityPolicy
ExtenderConfigs []ExtenderConfig
HardPodAffinitySymmetricWeight int32
AlwaysCheckAllPredicates bool
}
但这种方式的配置只在调度器启动时加载,运行过程中无法进行热更新,也无法根据Pod不同来指定不同的配置。如果想要同时支持多个算法集,就必须运行多个调度器实例,并通过指定Pod的schedulerName来选择对应的调度器实例。
这种方式不够灵活,而且当多个调度器对同一组Node进行调度管理时,资源分配状态并不能共享。我们希望能在一个调度器中通过参数化配置,针对Pod的类型动态指定需要的算法集以及算法集参数。
基于这个原因,我们增强了调度器Policy机制,引入了PolicyCacheProvider对象。PolicyCacheProvider对象通过watch调度Policy的configmap,在本地内存构建一个Policy对象的查询缓存。当某个Pod在创