【编者的话】私有构建是指开发者不依赖于任何外部资源(数据库、MQ、文件服务器等等)而在本地环境完成应用的构建、运行和测试的全部工作。这种工程实践在为研发和交付带来极大便利的同时,对应用本身的架构、配置管理以及本地环境的资源利用有效性都提出了很高的要求。我们的分享将解释如何基于Docker搭建一套轻量级的私有构建环境并及集成到持续集成系统中。
Docker技术的应用谈的比较多的是改变服务器管理和运维模式,在日常开发工作中如何使用Docker看到的实践比较少。最近在团队中尝试着基于Docker改进开发团队的私有构建设置,取得了不错的效率改进。这也证明了同样的工具能有不同的玩法,记录一下希望能够对同业有一些启发作用。
首先介绍一下项目背景。我们的系统,是一个另类投资的会计和净值计算系统。它基于Client/ Server的架构,基础开发语言是Java,前台使用了AWT/ Swing的框架。这个应用有超过20年的历史,目前整个代码库中JAVA代码有超过140万行,SQL代码接近120万行。
当前有将近100名开发人员在全球超过3个时区工作,日均代码提交次数在400次左右。
尽管开发时间很长,代码规模也很大,但通过良好的编码规范,严谨的开发过程约束,以及自动化程度很高的持续集成体系,代码得到了妥善的维护,技术债很轻。超过140万行的JAVA代码,单元测试覆盖率超过60%,总技术债在600+人天左右,这个对于百人规模的开发团队来说,是一件非常了不起的事情。
交付质量的另一个重要保障因素,就是大规模的持续自动化回归测试(acceptance test,缩写AT)。我们使用了jemmy的测试框架,整个测试仓库包括8000+测试用例,有十几台PC作为Jenkins slave,按照测试套件的组织,在每天晚上全量运行所有8000+测试用例,这样开发团队每天得到一份基于最新代码的回归测试报告和相应的代码覆盖率报告,为及时发现和解决质量问题提供了可靠的帮助。最长的测试套件单次运行时间超过12小时。
这些运行AT的PC,他们的环境setup和开发人员的机器设置是一样的,换句话说,当有实际需要的时候,任何一个开发的机器都能够被用作独立完整的AT环境运行回归测试,或者对整个系统进行集成测试。这种应用的设置和部署方式我们叫做Private Build,中文叫做 “私有构建”。我尝试过搜索private build的定义但是并没有找到任何相似的实践方法,所以我这里尝试着给一个定义:
所谓私有构建,是指开发者不依赖外部共享资源(数据库、MQ、文件服务器等等),在本地环境以统一、简便的方式完成软件应用的构建、运行和测试的全部工作。
在一个涉及大规模团队开发协作的项目中,经常碰到的问题就是“环境永远少一套”。抛开从无到有构建一个完整可工作应用的代价不谈,当代码库还在很活跃地进化的时候,维护一套可以平滑集成代码变更保持稳定工作的独立环境是一件非常困难的事情。当项目团队需要一个独立的环境与他人进行集成或者执行某种特殊测试(比如说性能、安全等)的时候,他们通常会发现需要花费很大的代价去“更新”这套环境,这种代价可能大到让人望而生畏。如果拥有一种“统一、简便的方式(甚至简单到一键式)完成软件应用的构建、运行和测试的全部工作”,会让这种畏惧消失无踪。
私有构建能够有效减少新成员在工作环境构建的学习成本,减少团队内部由于各自工作环境设置不一样带来的对交付质量的影响。标准化的私有构建设置意味着能够以很低的代价快速建立一套独立的测试环境,当系统随时间演化积累了越来越多的回归测试用例的时候,拥有这样快速扩展测试环境能力的重要性是不言而喻的。
当然,私有构建需要被包含在完整的持续集成体系里面才能发挥更好的作用,我们持续集成的概念图如下:
中间把“本地开发环境”和“回归测试环境”两个方框联系在一起的部分,就是我们的私有构建环境。开发人员的本地环境既有IDE,也能够快速启动包括了服务器、客户端和一个Oracle Express数据库的完整的AT环境。
要能真正实现“一键式”建立私有构建环境,对应用架构、配置和变更管理以及本地环境资源利用的有效性都有很高的要求。这件事情能够当作一个完全独立的话题来讨论,这里就不展开了。这套体系稳定运行了很多年,为项目的发展立下了汗马功劳。但是在最近的几年里面,我们遇到了一些新的挑战。
- 为了保持持续交付能力(代码的健康程度),AT运行必须在第二天工作开始之前完成,这个时间窗口在4到8小时左右。
- 随着业务的发展,开发团队的规模也在不断扩大,同时每日提交的代码变更数量也在不断地提升。这也意味着更多的AT测试用例,更长的总运行时间。为了保证在固定的时间窗口完成这些测试,需要更多的测试机器。之前的AT环境,需要专门的PC桌面运行,但能够提供出来运行AT的资源是有限的,并且我们也希望控制需要“专门”用来运行AT的PC机器,因为专门的环境意味着需要有专门的人去维护。
为了解决这个矛盾,我们首先的尝试是把服务端和数据库打包进一个Ubuntu的VirtualBox镜像(因为AT运行需要AWT/ Swing环境,保留在windows比较方便)。改动前的私有构建拓扑结构如下:
改动以后变成了这样:
新的工作方式以VirtualBox虚拟机镜像的方式统一了AT环境主要模块(服务端和数据库)的设置,简化了AT环境搭建的代价。但是从实战的效果来说,一台PC机上启动一个VirtualBox镜像加上Client端运行AT,CPU和内存的使用就已经超过70%(16G内存,双核四线程CPU)。这样从本质上说仍然是一台机器上运行一套AT环境,并且有一部分模块(client端)的构建和PC的设置是直接相关的。尽管后期我们通过标准化的构建命令以及VAGRANT的引入进一步简化了构建环境设置和AT启动的工作,但是仍然觉得有些缺憾。尤其是当需要同时启动AT测试和运行自己的IDE的时候,这种模式有点捉襟见肘的感觉。这时候,Docker进入了我们的视野。
由于Docker的轻量化特性,能够以更少的资源运行所需要的进程。如果能够将AT环境的所有模块都通过Docker运行在同一台PC上,并且使得一台PC能够支持多套AT同时运行,理论上每个开发人员的PC都可以成为一套单独的环境,并且不影响日常的开发工作。要达到这个目标,我们也面临着一些实际的挑战:
- 公司内部认证的Linux版本是3.0,我们重新基于Linux 3.1制作了一个VirtualBox镜像。
- 由于公司网络安全的限制,Boot2Docker没法在PC上面安装,我们必须通过比较重的VirtualBox来装载docker engine。最终拓扑结构变成了以composite的方式将Oracle Express,服务端和客户端分别包装进三个不同的docker image,在同一个docker engine上启动。
- 当所有模块都通过Docker启动的时候,client端AT需要在Linux环境下驱动。一方面需要基于Linux的AWT/ Swing重新编译客户端,另一方面在客户端的Docker环境里面安装tightVNC来支持用户界面的渲染从而让AT能够被驱动。
解决这些问题之后,新的拓扑结构如下:
在一个VirtualBox的镜像里,包括Oracle、服务端和客户端三个Docker镜像。不同Docker镜像通过docker compose被组合起来构建和启动。客户端通过tightvnc进行swing UI的渲染,从而实现在Linux环境下也能运行AT测试。关于docker compose,可以参考docker blog:http://blog.docker.com/2014/12 ... apps/或者https://docs.docker.com/compose/。
简单的说,docker compose是官方提供的容器业务流程框架,只需要通过简单的yml配置,就能按照指定的依赖关系完成多个容器服务的构建和运行。它提供了一些必要的命令和参数像links、ports、volumes。我们只使用了最基本的来指定三个镜像的启动顺序。
oracle:
image: $machine_name:$port/oracle
server:
image: $machine_name:$port/server
client:
image: $machine_name:$port/client
links:
- oracle
- server
env_file:
- ./$app.env
这里的启动顺序是先oracle,再server,最后client。在实践中,基于Docker的私有构建环境,一台PC运行一套AT环境,CPU和memory的利用水平大概在40%不到;同时运行两套AT环境,CPU和memory的利用水平也就80%左右。开发人员完全可以做到在一台机器上既做开发也运行AT互不干扰。更重要的是,所有的AT模块都被封装到一个虚拟机环境中,成为一个完全标准化的“私有构建”环境,我们的期望也比较完整地被满足了。
最后整理一下我们基于Docker的私有构建环境建立过程:
- 制作三个Docker镜像,分别对应Oracle、客户端和服务端。客户端镜像中一并包括tightVNC server提供linux环境中的桌面行为能力;
- 在VirtualBox虚拟机里面安装Docker镜像;
- 将VirtualBox镜像分发给开发团队;
- 将VirtualBox虚拟机注册成一个Jenkins Slave;
- 每个虚拟机镜像上启动两套AT环境;
拓扑示意图如下:
这样每个开发人员的PC,在工作的时候可以提供一个独立的测试环境供开发人员调试和测试。在空闲的时候,可以作为回归测试环境运行两套AT。并且每次启动AT的时候,私有构建环境都是非常干净的,避免了环境差异带来的AT运行不稳定问题。
下一步我们的计划是构建自己的Docker Registry,并且通过Docker Swarm实现Docker进程的管理和水平扩展。心目中的目标架构是这样:
回顾整个实验的经历,坦白说我觉得技术上遇见的挑战并没有想象的那么大。反而是打破在大公司工作的舒适区或者说无形的束缚,寻找解决问题新思路的第一步比较难跨越。呼应一下开头的话,这个例子只是一个普通的工程问题,很可能许多人在日常工作中也能遇见类似的问题。我们的实践说明了有了合适的切入角度,Docker在这样巨细而微的场景下也成为一个很接地气的解决方案。
Q&A
Q:是否Windows 2016原生支持Docker运行之后Virtualbox/Ubuntu这一层就可以跳过了?Q:Oracle Express和Oracle企业版的区别是?能否开发测试环境用Oracle Express,生产环境用Oracle 企业版?
A:我们使用VB/Ubuntu的原因不是因为Windows原生不支持,实际上Boot2Docker在Win7上就可以用,主要原因是公司机器要装非认证的软件流程没权限,本地认证的虚拟机只有VB。。。
Q:选择的什么操作系统运行的Docker ?
A:Oracle Express 和Oracle Enterprise 区别还是比较多的,私有构建环境用Express是为了限制资源占用。但是集成和测试环境都是Enterprise。
Q:在Docker 用Oracle数据库存储如何做?
A:Ubuntu 14 LTS。
Q:请问你们一直是使用Virtual box吗,有没有尝试在VMware下部署?为什么考虑前者?谢谢!
A:通过Volume挂上去, 由于是较小的测试库,所以数据量不大。
Q:能分享下jenkins master与jenkins slave上与各个Docker容器是怎么调度的?
A:没有用过VMware,关键因为公司认证的是Virtual box,而且是特定版本的。VMware还是Virtual box,对于我们的部署区别不大。还有一个考虑是希望有一个松耦合(不强依赖于特定产品)的开发环境,并且VIRTUALBOX开源,免费,够用。
Q:Oracle对资源要求比较高,你们用Docker做性能如何,有对比数据吗?
A:Jenkins有个Docker的插件,可以做到这三者间的调度协调。但是我们没有用,由于我们的实践相对还是比较特殊的,所以都是自己写的shell 脚本。
Q:vagrant用于开发环境,Docker用于生产环境,这里面哪个坑最多,你们怎样解决的?
A:我们没有特意做过测试,但经过一段时间的实践,基于Docker的测试比基于Windows桌面的测试速度快。稍微量化一点来说,用VIRTUALBOX,起一套私有环境跑AT,16G内存,2核4线程的开发机器,内存和CPU的占用率将近80%。用Docker的话,跑一套私有环境做AT,内存和CPU占用率大概40%多一点,基本上能省一半的资源。
Q:使用容器起一套AT环境,代码是通过Volume挂载到容器内部吗?运行的输出,如测试结果,编译出的binary是怎么交付的?
A:现在Docker并没有用在生产环境,只是用来辅助开发和回归测试。技术本身的坑并不多,因为我们用的方式都很直接,基本上查docker docs加google一下都能解决了。
Q:开发测试环境下的数据库每次是全量构建还是增量构建?基准数据是如何管理和导入的?
A:对的,代码通过volume挂载。我们的输出结果是一个文本文件包含测试的结果,以及日志;文本文件我们会自动上传至一个共享的位置,而日志是通过logstash收集,发送至elasticsearch。关于binary,我们AT的产出不包括binary,我们也有构建的job会产生binary,这些我们会上传至中央共享位置。
========================================================
A:每次都是全量构建。基准数据是来自于生产环境,对敏感数据进行清洗后得到一个轻量的测试库,然后再应用最新的代码,就成了我们每天的测试库。这个测试库是在Oracle Enterprise版本上,通过Oracle 的export/import导入到私有构建环境,这样保证我们的所有测试环境都是一致的而且干净。基准数据不是每天导出的,大概一周到两周才会做一次。
以上内容根据2016年2月2日晚微信群分享内容整理。分享人李卓,美国道富银行研发经理,副总裁,计算机工学博士。超过10年的IT系统研发和技术团队管理经验。对新鲜事物有旺盛的好奇心,持续集成、DEVOPS等任何能够帮助我自己和团队提升能力的工具方法的积极实践者。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题可以给我们留言。
有疑问加站长微信联系(非本文作者)