本文将重点讨论在 Jenkins 管理的持续集成以及测试的环境中,我们如何通过引入 Docker 来优化资源的配置,提高整个环境的性能以及稳定性。
关于 Jenkins
Jenkins 是被广泛应用的持续集成、自动化测试、持续部署的框架,甚至有些项目组顺便将其用来做流程管理的工具。根据任务的多寡,Jenkins 通常有两种典型的部署方式。
- 单节点(Master)部署
这种部署适用于大多数项目,其构建任务较轻,数量较少,单个节点就足以满足日常开发所需。 - 多节点(Master-Slave)部署
通常规模较大,代码提交频繁(意味着构建频繁),自动化测试压力较大的项目都会采取这种部署结构。在这种部署结构下,Master 通常只充当管理者的角色,负责任务的调度,slave 节点的管理,任务状态的收集等工作,而具体的构建任务则会分配给 slave 节点。一个 Master 节点理论上可以管理的 slave 节点数是没有上限的,但通常随着数量的增加,其性能以及稳定性就会有不同程度的下降,具体的影响则因 Master 硬件性能的高低而不同。
关于 Docker
Docker 是一款针对程序开发人员和系统管理员来开发、部署、运行应用的一款虚拟化平台。Docker 可以让你像使用集装箱一样快速的组合成应用,并且可以像运输标准集装箱一样,尽可能的屏蔽代码层面的差异。Docker 会尽可能的缩短从代码测试到产品部署的时间。简单来说 Docker 提供了一种技术,可以让开发人员方便地将应用代码已经运行时的环境一并打包到一个镜像中,然后将这个镜像上传至镜像仓库。在测试或者产品环境只需要下载这个镜像然后将其启动就完成了部署(就好比打开一个集装箱那么简单)。关于 Docker 更详细的内容请参考官网文档。
当前 Jenkins 遇到的困难
随着敏捷开发的普及,自动化测试成为每个项目的必须。一个经过多年开发的项目,其累积的自动化测试数量是惊人的。为了保证每次的部署都是正确的,就需要每次回归所有的自动化测试用例。根据项目的不同,有些需要每周跑一轮回归测试,而有些项目则需要每天一轮。所以我们会把所有的测试用例进行分组,同时在多台测试机上运行,以减少一轮测试所需要的时间。而这就要求我们有足够多的硬件资源来满足这需求。下图展示了一个典型的通过 Jenkins 来管理自动化测试的拓补结构。一台 Master 主机管理多台测试机,Master 将测试任务分配给测试机。
这种结构存在一些缺陷:
- 自动化的测试通常都是通过捕捉屏幕控件来模拟用户的行为以达到测试的目的,这就意味着一台测试机上只能同时运行一组测试用例,否则用例之间就会相互干扰。这就存在资源浪费,因为测试机的配置往往可以支持多组测试用例。
- 维护人员得确保测试机都保持在线,当测试机器数量较多的时候,比如 100 台甚至 1000 台的时候,这维护的压力就比较大。
- 通常回归测试是在晚上运行(如此就能在开发组第二天上班时发现前一天提交的代码是否正确),但这上百台测试机不论白天晚上都在工作状态,这是另一维度的浪费。
- 很重要的一点,测试机器会因为各种原因导致测试环境被污染,导致测试不能顺利进行,而此时除了维护人员手工处理外,没有特别好的方案。
- 之前我们提到过,当 slave 数量达到一定程度的时候,作为 Master 的节点就会出现性能变差,稳定性下降。
- 每个 Slave 节点在开始跑测试之前都需要从中央库下载最新的分发包,解压,设定运行环境。这个过程通常也占用不少时间,想象一下,突然间上百个 slave 开始下载最新的库,这对中央库是一个极大的冲击。
如何引入 docker 来解决这些问题
很自然的一个想法就是将测试机全部替换成 Docker 容器(container),而管理这些容器的工作交给更专业的工具,如 google 的 Kubernetes 或者 Docker 官方提供的 Swarm。所有构建的环境都打包进 Docker 的镜像文件中,自动化测试是一个镜像,编译单元测试是一个镜像等等。改进后的拓扑结构如下图所示:
在这个方案中唯一的技术问题是自动化测试需要桌面系统,而通常 Docker 容器中运行的都是无图形界面的应用。解决的方法也非常直接,我们在容器中提供桌面系统(VNC 服务)。根据不同的 linux 发行版本,我们可以选择 tightvnc 或者 tigervnc。不论选择哪种 vnc 服务,他们都提供一个虚拟桌面,可以通过 vnc 的客户端远程连接到该桌面进行操作。
对应前文提到的 6 个不足,在这种结构下悉数得到了解决。
- 每个测试机可以同时运行多组自动化测试用例,也就是说跑原来相同数量的回归测试,这种方案下可以至少节省一半的测试机。
- 通过 Kubernetes 或者 swarm,我们可以实现对容器的自动化管理,从而减轻维护人员的工作。
- Docker 容器可以根据需要动态增减,又因为构建所需的一切环境都被封装在 Docker 的镜像中,这些测试机器可以被方便的用于其他任务的构建,而不需要进行额外的配置。只需要下载其他构建任务的镜像,然后将其启动。
- 每次测试启动一个全新的容器,所以环境是完全干净的。当某个容器出现问题的时候,这个容器就会被销毁,同时新起一个容器完成相同的测试工作。
- 在这种结构下 Jenkins 只需要管理一个 slave 节点,而 Kubernetes 和 swarm 则可以管理成千上万个容器。
- 在同一个 Docker 环境中(同一台测试机)只需要下载一次最新的自动化测试的镜像就能起多个容器,而在这种结构下,测试机数量已经大大减少,从而对中央库(私有镜像仓库)的冲击也明显减轻了。
更多的思考
一个项目开发了 5 年,就累积了上万回归测试,需要几十台测试机不间断的运行 8、9 个小时才能完成;设想下,这个项目还要继续运行 5 年甚至 10 年呢?我们的测试机的数量将会是多少,我们的测试反馈周期还是 8、9 个小时么?根据我们的观察,经过多年的维护,很多功能模块已经是非常完善,基本很少有代码的修改。那么这些功能对应的测试用例是否有必要每次都跑呢?答案我想是否定的,但问题是怎么来判断当前代码的修改是否会影响到这些功能模块呢?按照现在的设计,我想没有人敢百分百的肯定。我们是否有必要对现有代码做些调整,依照微服务的思想,把我们的分发包拆分下,每次只需要发布有更新的模块。如此一来我们递归的自动化测试用例也只需要包含有改动的那些模块。那时候,很可能我们可以在每次代码提交就运行一次回归测试。