面向Docker容器的动态负载集群伸缩研究∗

2018-08-28 02:50
舰船电子工程 2018年8期
关键词:网络流量镜像容器

杨 忠

(成都理工大学工程技术学院 乐山 614000)

1 引言

随着互联网的迅猛发展,传统计算机架构已不能满足爆炸式增长的数据处理需求,云计算的出现为这一问题提供了新的解决思路,然而在实际使用时,服务器通常会面临多种不同种类的工作负载[1],企业需要根据这些负载的变化动态地调整服务器的数量以保证服务的稳定性[2]。在某些特定的互联网应用场景中,服务器时常会出现不可预知的负载请求,在这些额外负载出现时,企业需要及时增加服务器的数量,从而保证服务的稳定性,在这些额外负载消退时,企业也需要即使终止部分服务器,节省运维和管理费用。目前已有很多基于动态负载的集群伸缩方法研究,文献[3]提出了一种基于工作流模型的虚拟机动态分配以及回收的方法,该方法通过对作业指定截止时间的方式来进行作业的调度分配,从而节省计算资源,但是该方法并不能对未来的工作负载进行预测。文献[4]提出了一种面向混合负载的集群资源弹性调度方法,该方法允许作业基于自身负载特征提出多维度的资源申请,从而实现同一集群内各业务的统一部署与管理。文献[5]提出了一种基于用户负载预测的虚拟资源动态调度管理框架,该框架可以对用户的高负载进行预测并快速做出响应,保证服务的可用性。在以上关于动态负载的集群伸缩研究中,集群的底层都是基于传统的虚拟机技术,然而虚拟机需要额外的虚拟化控制层来实现硬件的虚拟化,因此给云平台带来了性能上的额外损失,并且虚拟机的启动速度也比较慢。

为了解决以上问题,本文通过引入Docker容器技术,提出了一种基于混合负载的容器集群动态伸缩方案,这一方案能够根据不同的工作负载进行相应的容器集群伸缩,从而有效地应对网络峰值,提升服务质量。

2 Docker容器技术

2.1 Docker概述

Docker是一款轻量级的容器管理引擎,其最初实现主要是基于Linux的容器技术(LXC),它使用Go语言进行系统开发,属于操作系统层面的虚拟化技术[6]。Docker在容器的基础上进行了进一步的封装,包括文件系统、网络通信和进程隔离等,因此能够给用户提供一个更加易于使用的接口,极大地简化了容器的创建和管理操作。

Docker容器从本质上来说是宿主机中的一个进程,它的底层核心技术主要包括Linux上的命名空间(Namespaces)、控制组(Control groups)和联合文件系统(Union file systems),其中Namespaces主要用来解决容器间的资源隔离问题,cgroups主要用来实现容器的资源限制功能,而UFS则实现了容器内高效的文件操作。

1)Docker资源隔离

Docker使用Namespaces技术来实现底层的资源隔离,Namespaces是Linux内核中的一个强大特性,在同一个Namespace下的进程能够相互感知彼此的存在,但是对外界的其他进程却一无所知,由于这一特性,不同容器中的进程虽然都共用同一个内核和某些运行时环境(如系统命令和系统库),但是彼此都看不到,都以为系统中只有当前容器存在,这样便达到了独立和隔离的目的,实现了操作系统层的轻量虚拟化服务。

2)Docker资源控制

Docker使用cgroups技术来实现对底层资源的控制功能,cgroups是Linux内核提供的一种机制,它能够根据用户的需求对共享资源(CPU、内存和I/O等)进行隔离和限制,因此可以解决多个容器同时运行时的资源竞争问题。cgroups能够提供以下四种功能[7]:资源限制、优先级分配、资源统计和任务控制。

资源限制:cgroups能够限制同一个进程组中能够使用的资源总量。

优先级分配:cgroups能够通过控制对一个进程组分配的CPU时间片的数量或磁盘I/O带宽的大小从而控制进程组的优先级。

资源统计:cgroups能够统计一个进程组中的资源使用总量。

任务控制:cgroups能够对进程执行挂起和恢复等操作,从而控制任务的执行状态。

3)Docker文件系统

Docker使用的联合文件系统(UFS)是一种高性能的分层文件系统,它支持将文件系统中的修改进行提交和层层叠加,这一特性使得Docker镜像能够通过分层来实现继承,在实际使用时,通常会基于基础镜像来制作各种不同的应用镜像,这些应用镜像都是共享的同一个基础镜像,因此能够提高存储效率。

若开发者需要将Docker镜像升级到新版本,则在改动原始Docker镜像时,系统会自动创建一个新的镜像层,因此开发者无需重新构建应用镜像,只需要在老版本的镜像上添加新的层即可。在开发者分发镜像时,也只需要分发这些被改动过的新层内容,这使得Docker的镜像管理变得更加方便和快捷。

Docker使用UFS作为其文件系统主要有以下几点优势[8]:首先由于多个容器能够共享同一个基础镜像存储,因此能够有效地节省存储空间;其次是能够实现不同应用的快速部署,并且节省内存,这是因为当多个容器共享基础镜像时,容器中的进程命中缓存内容的几率也会大大增加;最后就是应用的升级也会更加方便,开发者可以通过更新基础镜像从而一次性更新所有基于此基础镜像的容器。

2.2 Docker的优势

在实际的使用中,Docker通常被用来与传统的虚拟机技术进行比较,图1分别列出了Docker和传统的虚拟机技术的体系结构。传统的虚拟机技术主要是通过虚拟出一套硬件系统后,在虚拟的硬件上运行一个完整的操作系统,所有的应用程序都运行于这个系统上。而容器中的应用进程则是直接运行于宿主机的操作系统内核上,它不需要进行虚拟硬件的操作,也没有自己的操作系统,因此会比传统的虚拟机技术更加轻便快捷[9]。

图1 Docker与虚拟机的比较

3 动态伸缩技术

集群的动态伸缩指的是集群能够根据工作负载的大小自动调整集群的规模,从而调整其对外服务的能力。借助于动态伸缩技术,企业能够在网络峰值到来时自动地扩展资源从而处理额外的工作负载,并且在峰值过后可以自动减少资源从而降低管理和运维成本。动态伸缩技术按照其实现方式可以划分为响应型伸缩和预测型伸缩。

3.1 响应型伸缩

基于阈值的响应型伸缩算法是一种比较简单的算法[10],因此被广泛应用于云平台的自动扩展中。具体来说,基于阈值的响应型伸缩算法通过对系统资源(如CPU、内存、网络流量和磁盘I/O等)的使用情况进行实时监控,一旦出现某些指标的实际值超过设定的阈值时,就会发出扩展或收缩命令。在基于阈值的响应型伸缩算法中,最重要的就是响应规则和阈值的制定,通常只有在系统的负载变化很明确的情况下才能很好地制定响应规则,并设置阈值,因此它的优点主要是实现较为简单,通用性强,但是在一些负载变化不明确的场景中无法很好地使用,系统的伸缩调整往往会晚于工作负载的变化,因此导致用户的体验变差。

3.2 预测型伸缩

在基于阈值的响应型伸缩算法中,算法只会对系统资源的实时情况进行响应,因此具有滞后性的特点,而在预测型伸缩算法[11]中,系统会对过去一段时间内的数据进行分析、建模并完成对未来时刻的预测,其中使用到的分析方法主要有时间序列分析、队列理论和增强型学习等。通常在使用预测型伸缩算法时也会使用到基于阈值的响应型伸缩算法,在预测型伸缩算法计算出预测结果时,再通过响应规则和阈值来决定是否需要进行容器集群的扩展或收缩。预测型伸缩算法能够很好地弥补基于阈值的响应型伸缩算法的缺点,它能够在系统的工作负载变化之前就发出扩展或收缩命令,因此能够保证系统的稳定性和连续性,缺点就是预测算法的选择非常重要,当预测算法过于复杂时会使得预测建模过程过于缓慢,若预测造成较大偏差又会给容器集群带来很大的资源浪费。

现有的预测算法主要包括两大类:机器学习[12]和时间序列分析[13],前者通常需要在学习过程上花费大量的时间,因此不太适用于容器集群的快速伸缩中,而时间序列分析则相对简单且无明显的学习过程,计算速度较快。时间序列分析的常见预测算法有简单移动平均法、指数平滑法和自回归模型等。

4 容器集群动态伸缩方案

4.1 方案总体架构设计

基于混合负载的容器集群动态伸缩方案的整体架构如图2所示,整个方案主要由底层容器集群、负载均衡和伸缩控制系统这三部分组成。

图2 方案总体架构图

伸缩控制系统是整个方案架构的核心部分,它包括资源监控模块、伸缩决策模块和资源调度模块。其中资源监控模块负责统计物理主机和Dock⁃er容器的资源使用情况并报告给伸缩决策模块,伸缩决策模块会分析统计数据从而决定是否需要进行扩展或收缩,资源调度模块负责对底层的容器资源进行调度,负责容器的创建和销毁工作。

4.2 资源监控模块

4.2.1 资源监控类型

对容器集群的资源监控可以划分为对物理主机的资源监控和对Docker容器的资源监控。

1)基于物理主机的资源监控

相对于Docker容器来说,物理主机的生命周期更长,因此更应该进行资源优化,当多个Docker容器运行于同一台物理主机时,需要很好地考虑资源分配问题,因此需要对物理主机的相关指标进行监控。物理主机的相关监控指标包括主机CPU使用情况、主机内存使用情况、主机网络带宽和主机上运行的容器数量等。

2)基于容器的资源监控

主机资源的共享需要对容器进行合理的调配,在容器集群的弹性伸缩中,需要根据容器资源的使用情况来确定是否需要进行扩容或缩容。Docker主要通过cgroups来实现容器的资源限制,限制的对象包括容器的CPU占用率、内存使用量、磁盘I/O,网络流量也是需要监控的因素之一。

4.2.2 资源监控架构设计

资源监控模块主要对主机和Docker容器运行时的各项指标数据进行收集并进行存储,其架构如图3所示,主要分为数据采集(Collector)、数据处理(Processor)、数据存储(Storage)和数据展示(Dash⁃boards)这四个模块。其中Collector运行在主机上,它会将所有指标数据上传到Processor,Processor在获取到监控数据后会通过Storage定时进行数据的存储,用户能够通过Dashboards获取到监控数据并进行展示。

图3 资源监控架构图

4.3 伸缩决策模块

伸缩决策模块主要负责根据资源监控模块的数据判断是否需要进行容器集群的扩展或收缩,然后向资源调度模块发出对应的指令,其架构图如图4所示。伸缩决策模块主要由两个部分组成:Mod⁃eler模块和Controller模块,其中Modeler模块负责对监控历史数据进行建模,从而预测未来时刻的数据,而Controller模块则负责对所有的输入数据进行判断,并在满足伸缩条件时对资源调度模块发出伸缩调整命令。

图4 伸缩决策模块系统架构图

在实际的互联网应用场景中,通常可以将服务划分为计算密集型服务和网络密集型服务,计算密集型服务主要依赖于CPU,在这种服务场景下,CPU的使用率会在很短的时间内提升到一个较高的值,并且变化很快,因此很难对其进行预测,而网络密集型服务则主要依赖于网络带宽,在这种服务场景下,网络流量的变化更为平缓,连续性也较强,因此对于网络流量使用预测算法通常能够得到一个比较合理的预测值。综合考虑CPU和网络流量的变化特征,本文中对计算密集型服务采用基于阈值的响应型伸缩算法,而对网络密集型服务则采用预测型伸缩算法。

4.3.1 Modeler模块

Modeler模块主要负责预测型伸缩算法的实现,它会读取数据库中的网络流量历史数据,并对其进行建模,然后预测下一时刻的网络流量值,最后将这一预测值发送给Controller模块。综合考虑时序序列分析的各种预测算法,本文选用了二次指数平滑法来对网络流量值进行预测,二次指数平滑法的计算公式为

二次指数平滑法的预测模型为

其中,Ft+T为第t+T次的预测值,T为未来预测的期数,at和bt分别为模型参数,其计算公式为

Modeler模块的工作流程图如图5所示。

图5 Modeler模块流程图

4.3.2 Controller模块

Controller模块主要负责基于阈值的响应型伸缩算法的实现,它会对Modeler模块提供的网络流量预测值和CPU的实时使用数据进行判断,若有任意一种资源的使用情况超过给定的阈值,则会立即计算需要扩充的容器数量,并向资源调度模块发送对应的伸缩命令,完成容器集群的伸缩。这一模块主要包含两个过程:伸缩条件判断和容器数量计算。

1)判断伸缩条件

Controller模块会根据容器的实际资源使用情况对各种硬件资源(主要是CPU使用率和网络流量大小)设定一个阈值T0,当容器资源的实际使用值Ut第一次超过这个阈值的时候,会进入伸缩评估阶段,算法会维护一个长度为L的数组A,当后续的资源使用值Ut大于阈值T0时,会将Ut加入数组A,若在后续的L个资源使用值中有一个小于阈值T0,则会将数组清零,重新等待下一次的评估。若在L长度的时间内,资源的实际使用值一直都大于阈值T0,则认为需要进行对应服务的扩容,并且在这之后Controller模块会进入一段时间的冷却阶段,这期间不会进行伸缩评估,避免容器集群的频繁伸缩给系统服务带来剧烈抖动[14]。

2)计算容器数量

当伸缩条件判断过程给出了服务扩容的信号,则系统会进行扩充容器数量的计算。扩充容器数量的计算主要通过网络流量进行估算,若设定单个容器的服务能力为C0,额外网络流量对应的服务需求为C,则系统所需要扩充的容器数量为

整个Controller模块的流程图如图6所示。

图6 Controller模块流程图

在容器集群扩展时,综合考虑CPU和网络流量的实际情况,在CPU突破阈值的场景下,系统只会进行第一步,也就是只会判定是否满足扩展条件,在满足扩展条件时,Controller模块会向资源调度模块发出扩展命令,并且指定扩展数量为1,而在网络流量突破阈值的场景下,系统会首先进行扩展条件的判断,然后再计算需要扩充的容器数量n,此时Controller模块也会向资源调度模块发出扩展命令,指定扩展数量为n。

在容器集群收缩时,考虑到业务容器数量的突然变小会给负载均衡模块带来较大的影响,因此在这种情况下,系统也只会进行第一步,即判断是否满足收缩条件,若满足指定的收缩条件,则Control⁃ler模块会向资源调度模块发出收缩指令,并指定收缩数量为1。

4.4 资源调度模块

资源调度模块主要负责对容器集群的底层资源进行分配和调度,当集群需要进行扩展或收缩时,资源调度模块会确定在哪台宿主机上进行容器的创建或销毁[15]。资源调度模块主要分为两个部分:Manager模块和Scheduler模块,其架构图如图7所示。

图7 资源调度模块系统架构图

4.4.1 Manager模块

Manager主要负责根据给定的调度策略确定待创建或销毁的容器对应的宿主机节点,其调度策略主要包含两个步骤:节点筛选和节点打分。

在节点筛选过程中,主要考虑的筛选条件包括端口占用和资源占用,端口占用主要是查看待调度容器指定的端口是否已经在宿主机上被占用,若端口已被占用则舍弃此节点,资源占用主要是查看宿主机上的可用资源是否能够满足待调度容器的资源使用需求,若不满足则同样舍弃此节点。

在经过了筛选过程后,会得到一个备选主机节点列表,然后便进入打分过程,打分过程中的分值主要用于体现宿主机上的资源消耗情况,资源消耗越小,则其对应的分值越高,这一策略使得集群中的宿主机资源使用情况能够尽量平衡。其具体计算过程为:首先计算出待调度容器和宿主机上所有容器的CPU占用总量totalCpu、内存占用总量to⁃talMem和网络带宽占用总量totalNet,然后对其打分,打分规则为

其中,CpuCap为宿主机的CPU计算能力,Memory⁃Cap为宿主机的内存总容量,NetCap为宿主机的网络带宽大小,int()为取整函数。

在经过筛选和打分两个阶段后,对于待调度的容器会确定其对应的宿主机,系统会将这一配对信息写入到etcd中,供各个主机上的Scheduler模块使用。

4.4.2 Scheduler模块

Scheduler主要负责对Manager生成的调度信息进行监听,在获取到本节点的调度信息后,自动从本地仓库下载容器镜像,并启动容器。其调度过程如下:

1)Scheduler根据etcd中的容器信息确定其对应的Docker镜像和资源限制情况;

2)若当前节点上不存在对应的Docker镜像,则向本地仓库进行镜像的拉取;

3)创建容器对应的工作目录并生成容器必要的环境变量和参数;

4)构造Docker run container命令行所需的参数并调用Docker REST API来进行容器的创建。

5 方案验证

5.1 基于CPU负载的容器集群动态伸缩

为了测试基于CPU负载的容器集群动态伸缩功能,在本地部署了一套基于Docker容器的Web服务,Web服务的后端通过Python实现,其主要功能是在收到Web请求的时候进行一系列的数值运算,提高CPU的运行频率,从而模拟某些场景下的计算密集型服务。用户的请求模拟主要通过一个Python脚本实现,并通过Apache JMeter工具来实现并发从而提升容器集群的CPU负载。最终得到的CPU负载变化和对应容器数量变化如图8所示。

图8 CPU负载及容器数量变化图

从图8可以看出,容器集群的确能够随着CPU负载的变化而动态伸缩,但是具有一定的滞后性,容器集群在CPU负载升高后的第20s左右才会开始进行扩展,由于每次只扩充1个容器,因此容器集群的扩展速度也比较慢,与此同时,容器集群的收缩也是在CPU负载降低后的第20s左右才会开始进行。

5.2 基于网络流量的容器集群动态伸缩

为了测试基于网络流量的容器集群动态伸缩功能,在Web服务的基础上编写了一个Python脚本来实现文件的传输,然后在使用Apache JMeter工具进行并发测试时,同时往对应的容器发送一个文件,从而模拟某些场景下的网络密集型服务,最后得到的网络流量变化和对应的容器数量变化如图9所示。

图9 网络流量及容器数量变化图

从图9可以看出,容器集群的确能够根据网络流量的变化而进行相应的动态伸缩,并且基于网络流量的伸缩策略是预测型伸缩算法,因此会比基于阈值的响应型伸缩算法的滞后时长短一些,容器集群在扩展时的滞后时长大约为10s,同时预测型伸缩算法能够确定即将到来的额外网络流量,因此可以及时确定容器集群的扩展数量,从而扩展速度也比较快。

6 结语

Docker作为一种新兴的虚拟化技术正在打破传统的以虚拟机为基础的云平台部署方式,本文正是通过使用Docker容器技术作为集群的底层虚拟化技术,针对互联网应用场景中的不可预知负载,提出了一种基于混合负载的容器集群动态伸缩方案,这一方案通过对不同的工作负载采用不同的容器集群伸缩策略从而实现业务的动态伸缩,保证服务的稳定性,文末通过设计两种不同的工作负载场景对容器集群的伸缩功能进行了测试,验证了这一方案的可行性。但是本方案也存在一定的不足之处,文中使用的二次指数平滑法在遇到网络流量的突然大规模变化时预测结果会不太准确,对于这种情况,后续可以使用如线性自回归模型、机器学习等方法来进行预测,提升预测的准确率。

猜你喜欢
网络流量镜像容器
大数据驱动和分析的舰船通信网络流量智能估计
基于双向长短期记忆循环神经网络的网络流量预测
镜像
难以置信的事情
镜像
一种用于敏感图像快速加密的图像注入技术仿真
液体对容器底及容器对桌面的压力和压强
取米
镜像
基于时间序列分析的网络流量预测模型研究