ThreadFix团队是如何把Docker应用到测试环境?
互联网+的时代已经到来了,Docker+的时代还远吗?ThreadFix团队成功将Docker运用到测试环境的案例告诉我们,Docker+的时代已经来临。下面希云把ThreadFix团队成功运用Docker的案例和大家做个分享。
ThreadFix的技术团队发现他们经常要面临一些非常通用的问题:如何快速构建应用实例。从开发部门到质量控制部门都有这个需求,因为它带来的好处非常明显,能让用户用上最新、最稳定的代码版本。我们基于Docker构建的容器系统(我们内部称为"ThreadFix + Docker"),使实现这个需求前所未有的简单。
组件
这小节会概述一下“ThreadFix + Docker”系统的组件。
Docker以及Docker守护进程
这个系统最不可或缺的一块就是Docker守护进程,这个进程部署在一个远程Ubuntu虚拟机上,它和相关的文件组成了"ThreadFix + Docker"的后端。
简单来说,Docker就是一个工具,能让我们动态地生成容器,或者说是能与系统进程空间共存的轻量独立进程空间。这让我们能不用在全虚拟化的虚拟机中部署独立的ThreadFix实例,也就是说虚拟机相关的负载也可以省了下来。
Docker守护进程运行在宿主虚拟机上,等待着指令来创建新的容器,当它接收到相应的指令,它就会以一个镜像作为模板来创建新的容器。Docker镜像代表容器一启动时就会拥有的系统环境,而镜像又是通过一个面向过程的配置脚本来生成(称为"Dockerfile"),这套系统能非常迅速地启动一个开箱即用的容器。我们会在本文的“Jenkins持续集成“小节中讲到这部分内容。
这是一个简单的Dockerfile。
sh FROM tomcat:7.0.65-jre7 ADD ./threadfix /usr/local/tomcat/webapps/threadfix LABEL branch="Dev-QA" LABEL version="Enterprise"
这是"ThreadFix + Docker"界面上的可用镜像列表,以及各自的创建时间:
Docker API
Docker提供了一套REST风格的API,通过简单的配置修改,我们把Docker守护进程的API暴露在虚拟机上的Unix端口上,“ThreadFix + Docker”中有两个组件会通过这个通道与Docker进程通讯。
管理Shell脚本
我们有一个交互式shell脚本,用来与虚拟机中的Docker进程通讯,从而实现创建或者杀死容器,由于这个脚本中是使用Unix的<code>curl</code>程序来调用Docker的REST接口,所以这个脚本也能在用户的机器上运行,而不一定必须从宿主机上运行。这个脚本可以指定这些参数:
- 显示容器的名称(用于AngularJS界面)
- ThreadFix的git版本和分支(社区版或企业版,开发版或稳定版,等等)
- 虚拟机暴露出来的端口
- ThreadFix实例要用到的数据库文件
- 要调用的数据库操作(创建 或者 更新)
轻量的AngularJS客户端
终端用户主要会用到的“ThreadFix + Docker”组件是web界面,这个页面是由AngularJS构建的,它会直接调用GET请求与Docker进程通讯,从而获取到Docker中可用镜像和容器的相关信息。
对每个运行中的容器来说,都有一个链接是带有ThreadFix实例的映射端口,用户访问这些链接会跳转到具体某个ThreadFix实例首页。
另外,还有一个链接是可以查看那个容器的运行日志,这对于测试团队来说是非常方便的,能简化发现以及重现问题的流程。
最后,警告图标会告诉用户容器还没构建,他们所使用的镜像和代码很可能不是最新的。
更有趣的是,ThreadFix + Docker的Web界面它本身也运行在一个容器中。
Jenkins持续集成
拼图上最后一块是把我们现有的Jenkins持续集成任务也整合到这套系统中,我们利用了现有的CI任务,特别是那些会在代码更改后构建ThreadFix包,以及会作单元测试来验证代码质量的任务,这些任务被修改成了把构建好的包复制到Docker运行着的虚拟机中,然后运行脚本去构建新的Docker镜像,并指定特定的ThreadFix版本号。这样的话,当用户操作一个ThreadFix实例时,他们就能确保是正在使用由最新代码所构建的镜像。
背后原理
现在我们将讨论一下ThreadFix + Docker背后的进程,当一个ThreadFix容器被管理脚本创建,在界面上将能调用REST API来配置一些运行时参数。
作为参数传进脚本的端口号,会把在容器内的ThreadFix 应用8080端口,映射到宿主虚拟机上,这样实现了用户通过不同端口同时访问他们的实例。
ThreadFix 的版本和分支(社区版或企业版,开发版或稳定版,等等)让Docker进程知道启动容器时应该使用哪个镜像,正如上边所说,Jenkins任务会确保这些镜像是最新构建的。
数据库名称参数会在虚拟宿主机上查找同名的目录,如果这个目录不存在将会被创建。ThreadFix容器会关联这个目录作为“卷”,挂载容器中的一个路径到这个卷上。在Docker术语中,一个“卷”是宿主机上的文件路径,这个路径会被映射到容器中的一个路径上。我们的ThreadFix应用利用了这个特性,应用中把生成的HSQL数据库文件放在卷中,这样的话,当这个容器以新的镜像启动,只要关联回这个卷上,那数据仍然是完整的。
数据库操作参数同样也是利用了卷的特性,如果你指定“创建”操作,ThreadFix + Docker会替换默认的jdbc.properties文件,类似地,“更新”会使用一个“update. jdbc.properties“文件来建立数据库连接。
最后,一定要记得ThreadFix + Docker并没有用到独立的后端,而是直接与Docker进程通讯了。要存储容器的元数据,我们依赖于Docker的“标签",这些键值对可以生成镜像前在Dockerfile中指定,然后你们在Web界面上看到这些信息,诸如容器名称、版本、分支等等。
管理脚本打印出来的创建容器结果是一串Json,以下是一段摘要:
json
Craft JSON Data for Create Call
json="{\"OpenStdin\": true, \"Image\": \"threadfix/${version}\", \"Tty\": true, \"Labels\": {\"user\":\"${name}\", \"db\": \"${database}\", \"dbMethod\": \"${dbMethod}\"},\"HostConfig\": {${databaseJson} \"PortBindings\": { \"8080/tcp\": [{ \"HostPort\": \"${port}\" }]}, \"DnsSearch\": [\"denimgroup.com\"]}}"
总结
上边我们介绍了ThreadFix + Docker系统的各个组件,我们另外还遇到另一些用例,也是希望能通过Docker来提升我们的效率的,例如在一个远程ThreadFix容器中连接一个本地Mysql数据库,或者运行一个SQL Server数据库实例来作数据库驱动测试。
就目前来说,ThreadFix + Docker已经显著地减少了我们的构建时间,并且提高了我们产品的鲁棒性,使有时需要10分钟的工作量变成了现在的30秒。无论是开发第三方集成应用,排查问题,跟踪缺陷或者使新成员快速上手,Docker带给我们的好处不止上边这些。