徐正伦,杨鹤标
(江苏大学 计算机科学与通信工程学院,江苏 镇江 212013)
随着云计算技术的迅速发展,由虚拟化技术引领的虚拟机技术逐渐走向成熟。继AWS把云计算推向业界之后,各巨头联手打造了庞大的OpenStack与之抗衡[1]。在巨头们较量之余,Docker公司另辟蹊径,将Linux Kernel中晦涩难懂的namespace与cgroup封装成简单易用的命令行工具,并将待运行的任务打包成标准的Docker镜像,成功推动了技术变革[2]。从此只有大型企业才有机会接触的容器技术现在可被普通开发者轻易使用,一大批围绕容器技术的集群调度系统应运而生,云计算领域掀起了容器化热潮[3]。
如果说Docker推动了容器走进千家万户,那么容器编排技术才是其走进生产环境的关键。现有的容器编排技术主要有Google Kubernetes、Apache Mesos和Docker Swarm。Mesos其独特的双重调度器设计要求上层调度必须由自定义框架实现[4-6]。与现有的开源解决方案Marathon相比,Kubernetes和Swarm显得功能较弱,必须加以二次开发,否则会逐渐走入幕后[7]。而Swarm虽然随着Docker 1.12的发布,原生内嵌于Docker内部[8],拥有部署简便、易于使用的优点[9-10],但在生产环境上仍然不够健壮,缺乏足够的社区支持。现Docker公司已经逐渐放弃了Swarm的推动,转而在Docker Cloud上启用Kubernetes,并在最新的master分支代码上支持Kubernetes集群部署,至此,Kubernetes已经成为容器调度的事实标准[11]。
Kubernetes采用时下流行的微服务架构,由多个独立运行的模块组成。调度模块由独立的调度器完成,易于修改和扩展。本文所有工作将基于Kubernetes的调度器进行。通过分析Kubernetes调度器源代码,发现虽然Kubernetes内建的默认调度算法已经相对丰富[12-13],但是调度时未考虑QoS等级的影响,导致高优先级应用无法保证服务质量。因此,设计一种算法对其进行扩展很有必要。
由于允许超售存在,已经调度的Pod随着业务负载的上升所消耗的资源也会逐渐上升,导致节点所需资源很有可能超出物理机极限。
Kubernetes的QoS策略主要通过Pod声明所需资源和最大资源进行限制,并通过该设置将Pod优先级从高到低分为3级:①Guaranteed:不但声明了所需的资源数,还与限制值相同;②Burstable:虽然声明了所需的资源数和限制值,但值不同;③Best-Effort:直接未声明任何资源限制。
对于CPU,此类资源将通过软性限制避免超额使用。然而对于内存等不可压缩的资源,超出配额只能通过内核抛出Out-of-memory错误地将Pod杀死[14],并将Pod重新调度。对于更低优先级的Pod将最早杀死,而对于同一优先级的Pod,将优先杀死占用内存多的。
Kubernetes调度器在预选过程中直接去除不满足运行条件(端口占用、主机名、磁盘等硬性要求)的节点,以减少优选阶段的计算量[15]。而在优选阶段则通过打分法为每个节点得出一个总分,选择最优的解[16]。当存在多个节点分数相同时,调度器会利用Roundrobin算法随机调度[17]。
虽然Kubernetes调度器已经预置了大量优选算法(如最少需求策略保证单个节点不会负载过高,使集群负载均衡,而资源平均消耗策略又保证了单节点上内存与CPU资源的平衡消耗,避免内存被使用完),然而CPU仍然空余过多导致资源浪费[18]。之前的调度算法都只考虑了Pod在调度前能否满足需求,却忽视了Pod在工作时所需资源会动态变化的特点。显然一个Pod在空载和高负载情况下对CPU和内存的使用情况是不同的[19]。虽然QoS策略保证了高优先级Pod能够在竞争时拥有更高的话语权,有能力将低优先级Pod从节点上“驱逐”[20],但由于未考虑同一节点上不同QoS优先级Pod的占比,极有可能将一组都是高优先级的Pod调度到同一节点,导致高优先级Pod无法有效挤占低优先级资源。因为在同一优先级情况下,Kubernetes将首先杀死占用内存最大的Pod,而杀死任何高优先级Pod的情况均是要尽力避免的。
Kubernetes的QoS机制主要由运行时资源限制与节点驱逐机制提供,而调度器在调度时并未考虑QoS的影响因素,这有可能造成在单个节点上缺少低优先级Pod,而在需要驱逐Pod时不得不杀死相对高优先级的Pod,即使别的节点中正在运行的几乎全是低优先级Pod。尽管高优先级任务会驱逐其它节点的低优先级任务从而恢复运行,但是频繁地重新调度Pod无疑会造成服务短暂中断并加大Kubernetes调度器压力。
为解决以上问题,需通过调整调度器算法以保证节点上不同优先级的Pod数量均衡。
为保证节点上不同优先级的Pod数量均衡,设计了一个调度器优选算法——BalancedQosPriority。要保证节点上不同优先级的Pod数量均衡,可通过使节点上各个不同QoS等级的Pod占总数的比例与集群上的尽可能相近,即Pn→Pc,其中Pn是节点上某QoS优先级占节点总数的百分比,Pc是集群中某QoS优先级占节点总数的百分比。算法流程如下:
(1)检查待调度Pod,计算该Pod的QoS等级,记为L。
(4)最后计算每个工作节点的优先级分数:
score=10× |1-(Pn-Pc)|=
(1)
算法在实现过程中有3点需要注意:
(1)由于Kubernetes是大型分布式集群管理系统,节点数量可能非常多,因此在获取节点信息并计算时采取MapReduce。为使BalancedQosPriority算法能够应用在调度器中,算法需要实现Map与Reduce两个接口。
(2)Map的结果Kubernetes以32位Int保存。为了在Reduce时数据有足够精度,Map的结果必须避免取整操作。
(3)直接Reduce结果可能差距很小,由于最后的评分结果以32位Int表示,直接取整后可能失去参考价值,因此需要在Reduce末尾将结果正规化。
最后实现的形式化描述如下:
(int32)Mi=map(nodei)=NL
(2)
(float64)indexi=reduce(M1…Mn,node1…noden)=
(3)
(4)
为验证上述算法能否有效提高QoS服务质量,设计调度器调度结果实验,并对调度器进行性能比对测试。
实验环境见表1。
表1 实验环境
本实验重点测试BalancedQoSPriority算法对集群中各个QoS等级的Pod均衡度的提高。
3.2.1 实验方法
首先定义均衡度指数为各个节点上相同QoS等级Pod数量的方差,即该指数越小代表该QoS等级在集群中分布越均衡。各个测试节点为虚拟节点,在调度时忽略虚拟节点资源消耗的影响。实验分为两组,分别将改进前和改进后的调度器通过测试用例测试两次,两次采用相同的测试方法,测试在100个节点上调度3 000个Pod后各个QoS等级Pod的均衡指数,实验结果如图1所示。
3.2.2 结果分析
图1 各个节点上Pod数量的方差
从图1可以看出,在未采用BalancedQosPriority算法时,由于调度器未考虑QoS等级的影响因素,均衡指数较高。而在应用BalancedQoSPriority算法后,均衡指数明显下降,说明BalancedQosPriority算法在提高均衡度方面作用明显,算法切实可行。
虽然BalancedQosPriority算法具有可行性,但是客观上的确增加了调度器的计算量。如果在提高均衡度的同时带来了极大的性能损失,那么无疑算法是失败的。因此必须验证BalancedQosPriority算法的效率,为此通过Benchmark进行性能测试。
3.3.1 试验方法
对Kubernetes调度器进行集成测试,通过运行API Server与调度器,并通过虚拟节点创建大量Pod对调度器发起压力测试,统计出调度完特定数目Pod需要的时间,消耗的时间越小代表效率越高。
在默认设置的基础上分别开启与关闭BalancedQo sPriority算法,对比两种情况下的性能损失,从而得出该算法对整体性能的影响。实验总共进行5次取平均值,以忽略偶然性造成的影响。
3.3.2 结果分析
图2 性能影响对比
从图2可以看出,应用BalancedQosPriority算法前后消耗时间去除误差后几乎相等,说明BalancedQosPriority算法没有影响现有调度器的执行效率,从而证明该算法可行。
合理、有效地利用资源一直是云计算领域研究的热点课题。无论是提高集群的可用率还是资源使用率,都绕不开调度器优化。本文得益于Kubernetes的开源便利与优秀的模组化设计,针对调度器在QoS策略上存在的不足,通过设计一个新的调度算法,改进了以往在调度不同QoS等级Pod时没有尽可能使其在集群中达到均衡的缺点,并通过实验验证了算法的有效性和可用性。改进方法保证了Kubernetes集群在高负载情况下QoS优先级Pod的稳定性,提高了负载的可用率。