随着计算机技术的更新迭代, 传统的应用正在变得越来越复杂, 需要支持更多的用户、更强的计算能力, 而且还需要更加稳定和安全, 为了支撑这些不断增长的需求, 一种新型的计算模式“云计算”应运而生. 云计算是一种新的基于互联网的计算方式和资源应用平台, 从计算方式上来说, “云计算”是通过网络将庞大复杂的数据交由多台服务器组成的集群系统进行存储、计算、分析后, 将处理结果回传给用户的一种计算模式[1].
虚拟化是云计算的基石, 传统虚拟化技术是在硬件资源级别的虚拟化, 需要有虚拟机管理程序和虚拟机操作系统. 随着Docker[2]的出现, 容器技术正在成为打包和部署应用程序的新趋势, Docker是直接建立在操作系统上的虚拟化, 它直接复用本地操作系统, 更加轻量级. 使用Docker比使用虚拟机(VM)部署应用启动更快, 系统的开销更小, 能够更有效地支持应用程序的快速迭代[3]. 目前, Docker容器已在云基础架构中得到广泛部署, 例如Amazon Ec2 Container Service, Google Container Engine, Rackspace, Docker DataCenter[4].
Docker 是一个相对底层的容器引擎, 在大规模的服务器集群中, 需要一个集中的控制器来进行任务的编排, 调度和控制. 而Kubernetes[5]提供了大规模运行容器的编排系统和管理平台, 它实现了包括应用部署、高可用管理和弹性伸缩在内的一系列基础功能, 并封装成为一整套完整、简单易用的RESTful API对外提供服务.
Kubernetes的弹性扩展功能是由Horizontal Pod Autoscaler (HPA)[6]控制器来实现的, HPA会根据自定义指标控制副本集中的容器数量, 这种自动扩展伸缩机制使用固定阈值的规则, 并且Kubernetes的扩展和收缩Pod数量是根据同一套算法, 同一个检测指标来判定是否需要扩容或者收缩.
由于Kubernetes的扩容和收缩决策判断基于同一个指标, 这就会产生频繁扩容缩容的问题; 在应对大规模突发流量时, Kubernetes的扩容幅度不足以应对大规模流量, 就会造成访问中断; 在访问流量骤降的时候, Kubernetes内置算法则会迅速收缩Pod 的数量, 收缩速度太快, 当出现第二次突发流量, 收缩后的Pod还来不及扩展, 就会导致应用负载过大, 造成应用崩溃.
针对Kubernetes弹性扩展的调度策略, Casalicchio等[7]对CPU密集型的工作负载, 提出KHPA-A算法, 该算法采用绝对度量指标来做扩展决策, 使用绝对指标来触发容器弹性伸缩和确定Pod的数量, 将度量指标保持在设定阈值以下. Al-Dhuraibi等[8]在Docker层面提出ELASTICDOCKER算法, 该算法是垂直弹性扩展容器的CPU和内存大小, 当应用程序工作负载上升/下降时, ELASTICDOCKER将分配给每个容器的CPU和内存向上/下弹性伸缩. 垂直弹性仅受限于主机容量, 当所有主机资源都已分配给容器时, 容器将会执行从源主机到目标主机的动态迁移.
以上研究是针对于特殊场景的CPU密集型计算和纵向扩展增加容器的CPU和内存, 而如何在通用常规情况下, 使用方便简单的方法保证系统平稳、稳定运行则是更需要解决的问题. 因此, 本文提出步长容忍度算法水平伸缩的控制器, 它能在当应用服务器中业务负载上升的时候, 迅速创建多个新的Pod来保证业务系统稳定运行, 当Pod中业务负载下降的时候, 在保证系统稳定运行的前提下逐步销毁Pod来提高资源利用率. 对于应用, 需要保障能够稳定平滑的运行, 而不只是尽可能的节约成本. 步长容忍度算法在扩展的时候, 保证应用服务器能够承担不断增长的连接数, 做到快速扩展, 甚至过度扩展. 在收缩的时候采取较为宽松的收缩策略, 达到逐渐收缩的目的.
1 Kubernetes内置算法HPA是Kubernetes里面Pod弹性伸缩的实现, 它在Kubernetes中被设计为一个controller, 能根据设置的监控阀值进行Pod的弹性扩缩容[9]. HPA Controller默认30 s轮询一次, 查询指定的resource中Deployment/RS的资源使用率[10], 并且与创建时设定的值和指标做对比, 从而实现自动伸缩的功能. HPA控制器工作原理如图1所示.
|
图 1 HPA控制器原理图 |
HPA使用式(1)中的方法计算
| ${{dP = ceil }}\left[ {cR*\left( {cM/dM} \right)} \right]$ | (1) |
其中, dP (desired Replicas)为期待的副本数量, 即算法计算后得到的副本数量; cR (current Replicas)为当前的副本数量, 探测器检测到的副本的数量; cM (current Metric value)为当前设置的检测指标的值, 比如CPU利用率, 内存使用情况; dM (desired Metric value)为期望的Pod 的检测指标的值; ceil为表示取大于或等于某数的最近一个整数.
HPA中Pod水平弹性伸缩的实现是通过定期轮循查询Pod的状态(默认轮询时间为30 s)通过式(1)计算. 在每个周期时间, 控制器管理器根据每个HPA定义中指定的度量标准查询资源利用率. 控制管理器从资源指标的API中获取度量, 获取到Pod的监控数据, 然后通过现有Pod使用率的平均值跟目标使用率进行比较, 计算出需要伸缩的具体值并进行操作. 在每次扩容和收缩都有一个窗口时间, 在执行伸缩操作后, 在这个窗口时间内, 不会再进行伸缩操作, 默认扩容为3 min, 收缩为5 min.
例如当前的副本数量为1个, 设置当前度量标准值cM是内存, 检测到当前Pod 的内存为150 MB, 而初始设定的期望为100 MB, 则副本的数量将变为2个, 因为 1×(150/100) = 1.5 向上取整为 2, 所以Pod数量会弹性扩展到 2个.
参考 Kubernates源代码发现, 原生弹性伸缩的实现非常简单: (1)计算所有Pod的度量指标使用率; (2)根据弹性伸缩算法计算Pod的需求量; (3) 按照计算出的Pod数量进行扩容和伸缩. 这一自动缩放算法灵活性差, 扩容和收缩都是用同一个算法, 对突发流量会造成频繁扩容问题, 突发流量过后会造成频繁收缩的问题; 容器启动是需要一定的时间的, 在应对大规模突发流量时, 扩容还来不及扩展Pod, 就可能会造成当前应用承受不了负载而崩溃, 应当为新扩容的容器实例预留启动时间, 给正在运行的Pod充足的资源应对负载; 逐渐收缩, 在保证访问量承载的前提下, 逐步递减收缩Pod, 防止收缩太快低于系统的承载能力, 造成系统的不稳定, 针对这些问题本文提出了步长容忍度算法.
2 步长容忍度算法设计步长容忍度算法的时候, 考虑到自动扩展的决策需要一段时间才会生效, 若当前Pod的CPU负荷过大, 在创建一个新Pod的过程中, 应用系统的CPU使用量可能会同样有一个攀升的过程. 所以在循环探测中, 在每一次作出决策后的一段时间内, 将不再进行扩展/收缩决策. 对于扩容而言, 这个时间段为3分钟, 缩容为5分钟. 因为需要尽可能满足Pod业务的正常使用, 所以扩容的优先级要大于缩容. 步长容忍度算法会通过调整副本数量使得检测指标使用率尽可能向期望值靠近, 而且不是完全相等. 步长容忍度算法在HPA Controller中引入一个tolerance (容忍度)的概念, 它允许在一定范围内使用量的不稳定, 设置容忍度为15%, 这也是出于维护系统稳定性的考虑. 例如, 设定HPA调度策略为CPU使用率高于60%触发扩容, 那么只有当使用率大于75%或者小于45%才会触发伸缩活动, HPA会尽力把Pod的使用率控制在这个范围之间. 自动伸缩的监测指标包括CPU平均使用量、内存平均使用量、用户自定义一些监控指标.
步长容忍度算法包含两个部分: 快速扩展算法和逐步收缩算法.
2.1 快速扩展算法步长容忍度算法的快速扩展将Pods的目标期望利用率 Tmetric(作为MetricValue 指标的百分比)、前一个轮询控制周期(T s)中运行的Pods的 ActPod集合、实例化的初始Pod扩容数量、设定的扩展步长UpSetup等作为输入, 输出是则要部署的Pods的目标数量P.
系统启动之后, 每隔T s(如30 s)循环检查一次所有HPA, 检测结果若有一个Pod指标超过初始设置的期望值加上容忍度的值, 就以步长(如2)执行扩展. 每次扩展完成后, 记录扩展时间间隔, 保证扩展操作间隔不小于3 min以确保系统的稳定性. Pod的数量按照式(1)进行计算, 而利用率/期望的比例大于1.15(容忍度为15%), 才能进行扩容, 防止抖动.
系统每T s(实验设置为30 s), 算法收集Pod的 MetricValue 利用率, 用cAdvisor(容器监控工具)测量并将其存储在向量U 中. 最后, 计算出 DisredPods的目标数量P, P由式(2)计算得出.
| $P = \left\lceil {\frac{{\displaystyle \sum\nolimits_{{{i}} \in Actpod} {{U_i}} }}{{{T_{metric}}}} + S} \right\rceil \;\;\;\;$ | (2) |
算法1为步长容忍度算法中的快速扩展算法.
假设Tmetric = 60%. 正在运行3个应用程序副本, 每个Pod的CPU利用率分别为73%, 75%和82%. 在下一个控制周期, 步长容忍度算法确定应该部署新的Pod P = 6. 这样快速扩展以应对大规模的数据流量, 防止应用崩溃. 负载将在Pod之间分配, 预计的每Pod调整利用率由式(3)计算出:
| $\frac{{\displaystyle \sum\nolimits_{i = 1}^p {Ui} }}{p} = 38.33$ | (3) |
在下一次扩容的时间间隔3 min内, 以便有足够的资源来负载访问流量, 达到快速扩容的目的.
2.2 逐步收缩算法步长容忍度算法节点收缩采取的策略是当监测指标值低于所设定的缩容条件, 以默认步长2减少节点数量. 当监测节点数量为2, 停止减少容器数量.
约束规则为缩容条件的可选范围是(0%~45%). 缩容的时候当节点数<= 集群最小节点数(设置为2)的时候, 不会进行缩容操作.
算法2为步长容忍度算法中的逐步收缩的算法.
假设Tmetric = 60%. 正在运行6个应用程序副本, 每个Pod的CPU利用率分别为50%, 45%, 47%, 52%, 43%和40%. 在下一个控制周期, 步长容忍度算法确定收缩节点的数量P=4. 这样以步长为2逐步收缩. 负载将在Pod之间分配, 预计的每Pod调整利用率由式(4)计算出变为:
| $\frac{{\displaystyle \sum\nolimits_{i = 1}^p {Ui} }}{p} = 59.25$ | (4) |
在下一次收缩的时间间隔5 min内, 保持系统的稳定性, 达到逐步收缩的目的.
3 实验结果与分析 3.1 实验环境在实验中, 为了验证步长容忍度算法, 实验环境采用2套相同的Kubernetes实验环境做对比测试, 一套Kubernetes实验环境使用原生的弹性伸缩算法, 对比实验采用步长容忍度算法. Kubernetes实验环境都是1个master节点和2个node节点, Kubernetes版本1.11.3, docker版本为17.03.3-ce. 在实验中使用的环境配置如表1所示.
| 表 1 实验环境配置 |
设定了一个在进行压力测试的情况下检验Pod个数的变换以及每个Pod的CPU负载的变换情况.
3.2 实验步骤实验包含两个部分: (1) 使用ApacheBench进行压力测试, 在压力测试的时候检验pod的变化情况, 在压力测试完成后, 观察Pod随即的数量变化状况, 验证在应对突发流量时扩容/收缩情况. (2) 使用不同的并发请求数, 请求5 000 000个连接, 在高压高并发的情况下检测服务的吞吐率、用户平均请求等待时间、服务器平均处理时间, 验证步长容忍度算法是否提高系统的稳定性. 每个实验重复进行5次迭代, 取实验结果的平均值.
实验设定: 创建部署2个一主两从的 Kubernetes集群. 建一个包含nginx-stress容器的deployment, 对这个deployment持续进行并发请求, 模拟用户在客户端频繁地访问web应用, 应用会将访问流量转发到后端一个Pod上, 继而增加这个Pod的压力, Kubernetes系统检测到Pod内存和CPU在不断升高, 到达扩容的临界值就会扩展Pod, 来应对这些流量.
实验表明, 启动一个Pod容器实例所需的时间大约是6 s. 在实验中, Kubernetes资源探测的时间间隔定义为30 s, 虽然也可以将监视间隔指定为更短的时间, 但是将其设置为30 s可以减少通信流量负载和测量的监视开销. 在实验中, 本文把阈值被设置为65%, 这样步长容忍度算法将有足够的时间对工作负载中的运行时变化作出反应, 而不是阈值非常接近100%才进行反应, 从而防止压力过大应用崩溃的问题. 扩容间隔时间设置为3 min, 可以防止运行容器实例数量的过于频繁的变化. 收缩时间间隔为5 min, 避免收缩过快引发系统不稳定性.
对应用进行持续不断的请求, 进行压力测试, 图2显示了在工作负载急剧上升的场景下内置算法和步长容忍度算法的Pod的数量变换情况.
由图2可见, 在应用收到大规模流量请求时, 内置算法扩容的比例比步长容忍度算法小的多, 从应用的负载情况来看, 步长容忍度算法能够很好的支持大规模访问. 步长容忍度算法几乎没有失败请求数, 而内置算法出现了不少的失败请求数(见表2), 由于工作量增加导致系统过载, 内置算法的应用提供了一段时间的慢响应时间. 步长容忍度算法这种快速扩展能够很好支撑大规模流量访问.
|
图 2 Pod增长变化图 |
| 表 2 内置算法并发请求测试 |
当工作负载密度下降到1个请求, Pod的数量会根据执行时到达的请求的数量而减少. 图3显示了在工作负载下降的场景的Pod的数量变化情况.
|
图 3 Pod收缩变化图 |
步长容忍度算法和内置算法的自动弹性伸缩方法都不会立即停止在集群中运行的容器实例, Pod数量是呈现逐渐减少的状态. 与内置算法的自动缩放方法相比, 步长容忍度算法分配的Pod数量明显多于内置算法, 是递减收缩. 在工作负载突然增加时, 步长容忍度算法在开始及启动时有足够的Pod实例, 比内置算法自动扩展方法更快. 因此, 对于急剧变化的工作负载模式, 步长容忍度算法有更好的负载能力和维持系统的稳定性能.
本文也对在弹性扩展中Pod 的性能做了测试, 在进行5 000 000个连接数情况下对应用的失败请求数、吞吐率、用户平均请求等待时间和服务器平均处理时间进行了测试, 表2为内置算法的并发请求性能测试, 表3为步长容忍度算法的并发请求性能测试.
| 表 3 步长容忍度算法并发请求测试 |
对比内置算法和步长容忍度算法并发请求测试的结果, 在并发请求数比较少的情况, 内置算法和步长容忍度算法表现都差不多, 在失败请求数这个指标, 内置算法由于持续压力测试当前启动的Pod不能负载流量, 启动新的Pod需要一定的启动时间, 造成应用的不稳定, 产生较多的失败请求. 在并发请求数比较大的时候, 步长容忍度算法的优势就体现出来了, 应用能在更短的时间内处理请求, 响应客户端的速度也更快, 服务器的吞吐率也更大.
实验表明, 在并发请求数较大的情况下, 步长容忍度算法的Kubernetes集群处理能力比内置算法的集群应对突发流量的能力更强, 内置算法的集群对突发流量会出现承载过大, 出现较多失败请求, 对比使用不同并发请求数的请求测试, 步长容忍度算法的失败请求数比内置算法的失败请求数下降了 97.83%, 整个弹性伸缩系统更加稳定健壮. 步长容忍度算法集群的吞吐率比内置算法集群的吞吐率更大, 处理请求的速度越快, 使用步长容忍度算法的服务器的响应速度(依据用户请求等待时间)比内置算法算法的响应速度更快, 提高了4.54%. 步长容忍度算法在其他方面的结果比内置算法结果好. 此实验通过用压力测试模拟用户请求触发阈值进而进行扩容验证了本文提出的步长容忍度算法的有效性, 是在应对大规模流量横向扩展的有效手段.
4 结论与展望Kubernetes内置的 Horizontal Pod弹性缩放算法扩容和收缩采用相同的伸缩机制, 会造成频繁的扩容和收缩问题. 本文提出了一种新的自动缩放算法, 该算法能够实现快速扩容和逐步收缩以达到系统稳定运行的状态, 对于服务的稳定性是一个很好的提升. 本文提出的方案在并发请求数较大的情况下, 步长容忍度算法比内置弹性伸缩算法应对突发流量的能力更强.由于目前弹性伸缩策略是根据实时的使用率来进行扩容, 但其实对于很多应用来说, 很多业务高峰期是有规律的, 因此如果未来能做到提前预测出高峰期, 做适应的扩容, 那将会极大地提高资源使用率.
| [1] |
危烽. 浅谈云计算在互联网中的应用. 电脑知识与技术, 2009, 5(3): 583-584, 670. DOI:10.3969/j.issn.1009-3044.2009.03.029 |
| [2] |
Boettiger C. An introduction to Docker for reproducible research. ACM Sigops Operating Systems Review, 2015, 49(1): 71-79. DOI:10.1145/2723872 |
| [3] |
肖俊. 基于Docker的跨主机容器集群自动伸缩设计与实现[硕士学位论文]. 西安: 西北大学, 2015.
|
| [4] |
Bernstein D. Containers and cloud: From lxc to docker to kubernetes. IEEE Cloud Computing, 2014, 1(3): 81-84. DOI:10.1109/MCC.2014.51 |
| [5] |
Burns B, Grant B, Oppenheimer D, et al. Borg, omega, and Kubernetes. Communications of the ACM, 2016, 59(5): 50-57. DOI:10.1145/2930840 |
| [6] |
Vohra D. Using autoscaling. Kubernetes Management Design Patterns. Berkeley, CA: Apress, 2017. 299–308.
|
| [7] |
Casalicchio E, Perciballi V. Auto-scaling of containers: The impact of relative and absolute metrics. 2017 IEEE 2nd International Workshops on Foundations and Applications of Self* Systems. Tucson, AZ, USA. 2017. 207–214.
|
| [8] |
Al-Dhuraibi Y, Paraiso F, Djarallah N, et al. Autonomic vertical elasticity of docker containers with elasticdocker. 2017 IEEE 10th International Conference on Cloud Computing (CLOUD). Honolulu, CA, USA. 2017. 472–479.
|
| [9] |
Huang W, Zhang W, Zhang DY, et al. Elastic spatial query processing in openstack cloud computing environment for time-constraint data analysis. International Journal of Geo-Information, 2017, 6(3): 84. DOI:10.3390/ijgi6030084 |
| [10] |
Khazaei H, Ravichandiran R, Park B, et al. Elascale: Autoscaling and monitoring as a service. Proceedings of the 27th Annual International Conference on Computer Science and Software Engineering. Markham, Ontario, Canada. 2017. 234–240.
|
2019, Vol. 28


