张兆晨,罗铁坚
(中国科学院大学 计算机与控制学院, 北京 101408) (2017年3月10日收稿; 2017年5月16日收修改稿)
随着软件项目规模逐渐扩大,市场需求不稳定,软件产品需要快速、持续、高质量的实现软件交付,从而更好地应对市场及用户的需求。软件构建是软件生命周期的关键环节,包含从源码编译到产品交付的整个过程。但缺乏规范化和标准化的软件构建流程往往成为影响软件交付的瓶颈所在,敏捷开发和DevOps思想理念的产生很好地解决了这一问题。
以迭代开发为特征的敏捷开发,灵活应对不断变化的需求,大大提高了开发效率。持续集成构建作为敏捷开发中的一项基本实践[1],将开发周期的集成阶段放到日常中,开发者每天都会集成代码,通过自动化的构建和测试尽早定位软件缺陷,降低开发风险。DevOps思想[2]是一组流程、技术和工具的统称,促进了开发、技术运营和质量保障部门之间的沟通协作,提供端到端的测试、交付和发布工作流。
各软件企业和相关研究人员对迭代式开发、持续集成等实践不断地研究尝试,促进了软件开发模式的发展,开发效率也在不断提升。虽然相关研究实践能够一定程度提高开发效率,但硬件资源的分配与限制是影响开发周期的另一个因素。本文的研究目的是将持续集成构建和容器技术相结合,应用在项目开发中,利用容器技术对硬件资源的分配特性,研究如何标准化环境从而重复、循环地交付软件价值。
本文总结国内外研究人员对敏捷开发中的持续集成实践和虚拟化容器技术的相关研究工作,分析传统持续集成系统的基本组成及其不足。提出将Docker技术应用在开发环节中,设计并实现一个容器化的持续集成构建系统(简称CCI),通过设计系统的一体化工作流方案,使各团队成员之间相互协作,优化软件开发流程。最后通过具体应用对系统平台进行分析和验证。
不少国内外研究人员对敏捷开发中的迭代式开发和持续集成等实践展开深入的研究,促进了软件开发模式的转变和发展。
Lai和Leu[3]总结敏捷开发和持续集成的优势,提出一种持续集成过程以有效降低Web应用的开发风险。Meyer[4]论述实现一个持续集成系统的关键要素包括版本控制系统和持续集成服务器。使用版本控制系统,改变了开发人员之间的交流和协作模式,使不同团队之间可以更好地相互合作[5]。Seth和Khare[6]讨论版本控制系统在软件开发中的重要作用和分布式版本控制系统Git的优势。
Jenkins是一个开源的持续集成服务器,支持分布式构建。Rai等[7]讨论Jenkins的背景和发展历史,提供安装和配置Jenkins的方法,同时对比分析其他相关持续集成工具的优缺点。Jenkins不仅提供丰富的插件支持,有可扩展的对象模型,这一特性使得可以定制化地开发插件以扩展Jenkins的功能。另外,Jenkins工具除可以实现持续集成外,还可以实现持续交付。Armenise[8]提出Jenkins不仅能够提供自动化的构建实现持续集成,还可以通过丰富的插件机制实现产品的发布、部署,从而实现软件的持续交付。
可重复性实验在科学领域中得到越来越多的关注[9],为解决实验环境的可复用性和实验数据的可再现性,出现了两个主流的解决方案:工作流(Workflow)软件和虚拟机[10-11]。但是工作流软件往往不能覆盖每个研究人员的需求,虚拟机相比工作流软件而言是一个好的方法,但是它也有自身的一些限制。虚拟机虚拟出一个完整的操作系统实现环境的隔离,并在此基础上配置相应的环境,系统维护性和灵活性比较差且不易扩展[10],不容易实现环境的可复现性。
轻量容器技术Docker的出现,引起业界很大的反响,受到广泛关注。它是一个开源的应用容器引擎,与传统虚拟机的主要不同之处在于它共享宿主机的Linux内核,启动快、占用资源少。Docker容器的虚拟化是建立在主机的操作系统之上的,是系统级的虚拟化而不是针对硬件虚拟化,虚拟镜像的移植性比传统基于硬件的虚拟化移植性要强。这使它比传统虚拟机轻量且具有高性能[12],与传统虚拟机分钟级的启动速度相比,Docker启动速度达到秒级。传统的计算机能够同时运行几个虚拟机,但却能够同时运行上百个甚至更多Docker容器。Boettiger[10]论述Docker在可重复性研究中发挥的重要作用,它解决了计算重复性所面临的技术挑战。在集群、云环境等分布式系统中使用Docker技术可以使底层应用环境保持一致性,增加系统的可移植性[13]。另外,Docker在持续集成和软件测试领域应用也很广泛。例如,与性能测试工具结合,保证实验环境的可复用性和数据的可再现性[14]。
基于容器的虚拟化技术,利用增量的方式生成镜像环境,灵活性更高。根据Docker的特性,将其应用到软件项目的生命周期(开发、测试、部署、维护),能有效地提高各个环节的效率。
持续集成(CI)系统的核心价值在于自动化,服务器的每次集成都是通过自动化的构建来验证,包括自动编译、测试、部署等。一个传统的持续集成系统通常有3个组成部分(如图1):实现代码托管的版本控制系统,执行集成构建的持续集成服务器和自动构建编译的工具。
图1 传统持续集成系统基本组成Fig.1 Basic components of traditional CI system
版本控制系统在项目不同团队开发人员之间维护一个统一的代码库,实现项目的协同开发,保证项目的源代码处于有序的管理中,方便项目开发人员随时获取和提交变更。在持续集成系统中,版本控制系统起到的主要作用是源代码的管理。
持续集成服务器在整个系统中起到至关重要的作用,连通各个部分的基本组件,是实现过程自动化的关键。当开发者提交代码到版本库中,服务器轮询到变更,便会触发自动构建工具,进入到后续的自动化构建过程。这一自动化过程使开发人员能够快速收到Bug和相关故障的反馈通知,快速修复软件质量的缺陷,从而及时交付软件价值。
在自动化编译构建工具引入之前,项目人员需要进行一系列手动的过程,下载源码、编译、测试、部署。手动构建不仅浪费大量的人力进行重复性的工作,而且容易引入人为因素导致的错误。
自动化的编译构建工具对于持续集成系统是不可或缺的,是实现系统自动化的前提。持续集成服务器集成构建工具完成构建任务,实现项目的自动化构建、测试、部署流程。
在传统的持续集成系统中,基本解放人力,实现了自动化,但仍然存在一些不足之处。
1)环境的搭建、维护比较复杂且耗时。持续集成环境、开发测试环境的搭建需要在本地完成种类繁多的软件安装和配置,是一件非常复杂且耗时的工作,且环境移植性比较差。
2)开发环境、测试环境和生产环境不一致。维护不一致的环境,影响软件的交付效率。另一方面容易导致在生产环境中暴露出开发测试环境没有出现的错误。
3)集成构建过程触发单元测试、集成测试,但没有考虑其他方面的测试,不方便测试人员开展工作。
在本文的容器化实现中,采用Github作为版本控制系统,Jenkins工具作为持续集成服务器。与传统集成平台相比,将安装在宿主机本地或虚拟机中的集成环境进行容器化,将集成服务器主从节点分别放在不同的容器环境中。利用Jenkins的分布式特性,将Jenkins主服务器(Master节点)和Jenkins从服务器(Slave节点)分别放在Docker容器中,CCI系统架构如图2所示。
Jenkins主机器负责任务(job)的配置及相关调配,从版本控制系统中获取代码变更来触发构建,根据对构建任务的相关配置,将任务分发到持续集成从机器中执行构建。可以根据项目需要,配置不同的从机器环境执行构建,保证任务构建测试环境的可扩展性。将配置好的集成构建环境生成镜像保存到镜像库中,保证构建环境的快速可再现性和可移植性。
3.2.1 Jenkins服务容器化
在CCI系统的实现中,关键是将集成构建环境进行容器化,利用Docker容器技术快速生成软件构建环境。为了生成Jenkins容器,Docker提供两种创建方式:
1)利用现有的镜像库生成,使用docker run命令快速创建Jenkins容器。在Docker命令中将容器内部的8080端口重定向到宿主机的8080端口,可以通过主机端口访问容器内部Jenkins服务。
图2 CCI系统架构Fig.2 Architecture of CCI system
dockerrun-d-p8080:8080-namezzcjenkinsjenkins:latest
运行docker run命令时,如果本地没有镜像,则会默认从远端镜像仓库中拉取镜像,将镜像缓存到本地,再启动基于镜像创建的容器。
2)使用Dockerfile自定义的构建Jenkins镜像环境。Dockerfile包含创建镜像所需要的全部指令,使用docker build命令来执行命令并创建镜像,自底向上地打包软件及其环境。
两种方式各有优缺点。利用现有镜像库方式生成容器,能够快速获取现有镜像生成可运行的容器环境,但是不方便构建自定义的环境。而通过Dockerfile的方式,可以减少镜像和容器的创建过程以简化部署,方便系统的升级。另一方面,在具体项目应用中,开发测试人员和运维人员可以利用Dockerfile文档来沟通软件项目的执行环境,促进了不同开发团队之间环境的统一。
使用Dockerfile构建拥有更高的灵活性和可维护性,本文的Jenkins容器环境的实现利用Dockerfile文档自定义构建Jenkins环境。在Dockerfile文档中实现Jenkins及其插件、版本控制服务git及其他相关依赖软件的安装配置。
Jenkins容器构建完成之后,需要进行相关任务配置以运行构建任务。为了解决Docker中数据的安全性问题,保证项目数据的安全,我们不在容器中存放任何与项目相关的数据。Jenkins在运行构建时,相关配置和数据都放在$JENKINS_HOME下,通过Volume技术将Jenkins的运行目录挂载到宿主机的/var/lib/docker目录下。
3.2.2 构建配置从机器
对于一个持续集成工具而言,Jenkins的分布式特性,给我们带来很大的便利。在Jenkins分布式架构中,主机器主要负责任务的自动触发及调度分配,实际的构建任务在从机器上执行,保证干净的构建环境和整体性能。我们把从节点的环境也放到容器中,与主机器相似,从机器容器中也不能存放项目数据,利用Volume技术将容器环境中的目录挂载到宿主机。
本文中,Jenkins从机器负责任务的构建和测试,从机器通过Docker技术虚拟出不同的构建环境执行主机器上的构建任务。通过这种方式构建出不同的从机器环境,使构建、测试环境多样化,更好地保证软件质量。本文通过第一种方式执行docker run命令直接在镜像库中获取基础镜像,使用docker exec命令进入到容器内部进行相应的环境安装配置操作。
3.2.3 任务的分发
Jenkins分布式构建机制使同一项目或代码能够在不同的环境或系统中进行编译、部署,主机器和从机器的关联通信主要有3种方式,通过ssh启动连接、通过java web启动连接、使用命令行方式启动连接。
针对容器环境,在本文中采用SSH机制连接不同机器的通信,减少配置人员的工作量。
对于一个持续集成系统平台而言,关键是根据项目需求进行相关的集成配置,将流程进行自动化。从开发人员提交代码的那一刻,中间过程通过自动化实现,而无需人工参与。
本文设计了CCI系统的主要工作流(如图3所示)。工作流中的主要参与人员有开发者、测试人员、部署人员,系统组成要素有版本控制系统Github、Jenkins服务器节点(Docker容器)、Jenkins从服务器节点(Docker容器)和Docker镜像库。
图3 CCI系统工作流Fig.3 Flow chart of CCI system
版本控制系统与容器中的持续集成主服务器Jenkins之间配置了自动触发的钩子,开发人员将代码提交到版本控制系统Github上之后,自动触发集成服务器进入到构建过程。Jenkins服务器的主节点轮询版本控制系统,发现变更,将任务分发到Jenkins从机器节点上执行构建。在构建过程中,配置相关的测试脚本,执行自动化的单元测试。若项目构建成功,则将容器环境生成镜像,保存到Docker私有镜像库中。测试人员和运维部署人员在私有镜像库获取镜像,进行测试环境和线上环境的搭建,继而执行其他相关操作。通过共享镜像的方式,统一不同团队之间的环境,有利于快速生成软件价值,实现持续交付。
传统的持续集成平台是将Jenkins安装在主机或虚拟机上,本文通过利用Docker将持续集成平台进行容器化,可以得到以下几点好处。
1)Jenkins服务器的容器化提高系统平台的可重用性和可移植性。
2)将Jenkins从节点容器化,提供多样的构建环境和测试环境。
3)维护一致的开发、测试和部署环境,减少因环境不一致所造成的时间成本。
本文的基础实验环境主要有3个,分别为宿主机环境、CCI系统容器环境和虚拟机环境,各环境操作系统及主要配置如下:
1)宿主机的操作系统采用ubuntu14.04版本,分别安装了docker、git、jdk、vmware软件环境;
2)CCI系统包括3个容器环境,分别是Jenkins容器环境和2个从节点容器构建环境(slave1-ubuntu,slave2-centos);
3)在vmware虚拟机环境中,生成3台虚拟机,分别作为Jenkins服务器机器和2个从节点机器环境。
本实验主要有两个验证目标:验证CCI容器系统及本文所提出的工作流方案的可行性;验证系统资源占用少、快速构建部署的优势。
为了验证系统平台在软件开发各阶段中发挥的重要作用,我们在容器化的Jenkins系统中执行相关集成构建任务测试其可行性。容器内部的8080端口重定向到宿主机的8080端口,通过宿主机端口访问容器内部Jenkins服务,系统界面图如图4所示。
图4 容器内的Jenkins服务Fig.4 Jenkins services in Docker container
我们利用此系统平台分别执行基于Tomcat的Web项目和对Web应用进行测试的Selenium测试脚本。测试脚本目的是测试应用的系统平台兼容性,对中国科学院仪器设备共享管理(V3.0)系统[15]的镜像进行测试,分别在容器中的两个不同操作系统环境(slave1-ubuntu,slave2-centos)中执行测试构建。
另一方面,为了验证CCI系统资源占用少、快速构建部署的优势,我们分别在虚拟机和Docker环境中安装配置了基于Jenkins的持续集成构建环境,记录安装部署的时间耗时。
实践应用表明,基于Docker的Jenkins持续集成平台给开发活动中的各阶段都带来了便利。我们在系统上执行的基于Tomcat的Web应用程序,从源码的获取到测试构建,能快速地得到应用的部署结果,并将结果反馈给开发人员,极大地方便了开发活动。针对应用的测试而言,Selenium测试脚本在两台从节点容器环境上执行构建的时间花费如图5所示,可以通过系统日志观察到在不同操作系统环境上的测试构建趋势和执行状态,方便测试人员定位兼容性错误。
图5 Selenium测试脚本的执行结果Fig.5 Selenium script execution results
从开发人员的角度来说,开发者可以利用Docker快速搭建开发环境分发给团队成员,并利用容器化CCI持续集成系统平台,快速得到项目的反馈,提高开发效率。从测试人员出发,利用容器化CCI持续集成平台,可以快速构建出不同的测试环境,通过分析在不同测试环境下得到的测试结果对软件质量进行评估。对于运维人员来说,能够快速搭建与生产环境一致的平台对系统进行评估和维护。
为了验证容器化系统相较于本地或虚拟机环境下的优势,分别在虚拟机和Docker环境中安装配置基于Jenkins的持续集成构建环境。根据实际安装测试,在虚拟机或本地宿主机上搭建持续集成系统,往往需要耗费1~2天的时间,需要分别安装底层操作系统、Jenkins服务器及相关依赖和Slave从节点机器环境等各种复杂的配置。而用Docker在10 s左右就能快速生成一个可运行的持续集成容器环境,基于docker构建环境的时间与虚拟机相比几乎可以忽略不计,搭建环境的效率提升90%以上,系统启动时间的效率提高大约80%。表1通过各个指标对比CCI系统与虚拟机持续集成系统。
表1 容器化CI和虚拟机CI的对比Table 1 Comparison between CCI and virtual machine CI
基于Docker的持续集成平台,在环境搭建耗时上远远优于传统的持续集成系统的搭建,只需要在秒级的时间里快速启动一个镜像就得到一个持续集成环境。Docker占用系统资源少,启动速度快,这点是虚拟机没有办法比拟的。在测试效率方面,容器化保证开发测试环境的统一和环境的重用性,从而减少测试重复率,提高测试效率。
本文利用Docker实现一个容器化的CCI持续集成系统,提出代码管理、持续集成和持续交付的一体化工作流方案。将搭建环境的复杂度降低九成,确保环境的一致性和可移植性,有效促进开发、测试和运维团队之间的协作,能够大大提升软件开发效率。目前本文实现的集成系统还没有实现容器的管理和部署编排工具,缺乏可视化的用户管理界面。在下一步的研究工作中,将此系统框架迁移到私有云环境上进行实验,将云平台与容器技术相结合,在云环境下进一步验证系统的可靠性,并深入研究容器相关管理工具的应用,进一步开发可视化的管理界面。