基于docker的私有构建实践

PilarAkin 9年前

来自: http://dockone.io/article/1017

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的私有构建环境建立过程:

1. 制作三个docker镜像,分别对应Oracle、客户端和服务端。客户端镜像中一并包括tightVNC server提供linux环境中的桌面行为能力;

2. 在VirtualBox虚拟机里面安装docker镜像;

3. 将VirtualBox镜像分发给开发团队;

4. 将VirtualBox虚拟机注册成一个Jenkins Slave;

5. 每个虚拟机镜像上启动两套AT环境;

拓扑示意图如下:

这样每个开发人员的PC,在工作的时候可以提供一个独立的测试环境供开发人员调试和测试。在空闲的时候,可以作为回归测试环境运行两套AT。并且每次启动AT的时候,私有构建环境都是非常干净的,避免了环境差异带来的AT运行不稳定问题。

下一步我们的计划是构建自己的docker registry,并且通过docker swarm实现docker进程的管理和水平扩展。心目中的目标架构是这样:

回顾整个实验的经历,坦白说我觉得技术上遇见的挑战并没有想象的那么大。反而是打破在大公司工作的舒适区或者说无形的束缚,寻找解决问题新思路的第一步比较难跨越。呼应一下开头的话,这个例子只是一个普通的工程问题,很可能许多人在日常工作中也能遇见类似的问题。我们的实践说明了有了合适的切入角度,docker在这样巨细而微的场景下也成为一个很接地气的解决方案。