且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

自部署CI与云CI的环境管理

更新时间:2022-05-08 14:02:49

自部署CI与云CI

自部署(self hosted)CI指的是把一套软件部署在自己的机房或内网中,你需要自己提供机器(通常需要多台)来完成CI软件的安装及任务运行等任务。有可能还需要对不同机器之间的连接和组网做一些特殊的配置。常见的例子有Jenkins、Bamboo、Gocd等

云(hosted)CI指的是由第三方提供的CI服务,你只需要在它们的网站上进行一些配置,包括你的源代码在哪里、你希望何时及如何构建你的代码,然后云CI就会乖乖地按照你的要求跑起来了。在这种方案下你不需要考虑装机器,装软件,配网络等繁杂的事情。常见的例子有Travis CI,CRP等。

本文会从环境管理这个角度来对比一下两种CI。

什么是构建环境管理?

首先让我们明确一下什么事构建环境管理。

举个例子,我有多个项目,每个项目的技术栈有所不同,Java项目需要JDK、Maven、Gradle、Ant等工具;Ruby项目需要Ruby解释器、bundler,还有一堆的本地库,因为很多Ruby的gem都依赖它们;如果你想要运行浏览器功能测试,那么需要运行的机器上安装有图形界面,或者Xvfb等工具。所以环境管理要解决的问题就是:如何进行给相应的任务分配合适的构建环境。

自部署CI的做法

先看看自部署CI的代表Jenkins的做法。自部署CI一般采用master-slave的结构:
自部署CI与云CI的环境管理
其中master负责任务调度,slave负责实际运行任务。传统的做法是在不同的slave上安装不同的环境,然后在一个环境对应一种技术栈。在进行任务的配置时候可以指定其运行的slave。Jenkins的实际上是使用label来指定某个任务会运行在哪台机器上的。

看看下面的例子来理解label的概念:Java的项目很多,而Ruby项目很少,则Java的slave需要不止一台,而给Ruby单独的一台slave又是浪费的。这种情况下我可以有三台slave,然后给所有的slave都装上Java环境,并给它们打上“Java构建”的标签;然后只给其中一台安装Ruby环境,并给它们打上“Ruby构建”的标签。如下图所示:
自部署CI与云CI的环境管理
同时安装Ruby和Java的slave的配置:
自部署CI与云CI的环境管理

对于Java任务来说,我只需要告诉Jenkins所有的Java任务都能够在所有的带Java标签的slave上运行,然后Jenkins就会寻找一台带Java标签的slave来运行当前任务。如下图所示:

Java项目的配置:
自部署CI与云CI的环境管理

Ruby项目的配置:
自部署CI与云CI的环境管理

但是,还存在一些问题

如果Ruby的slave的机器挂掉了,怎么办?当然是赶紧重建一个啦!首先这件事情很花时间,其次,没人能保证再建一次出来的slave能够和原来的那台一模一样。

要解决这个问题有几种方案:

镜像

做好一个slave之后,打个虚拟机的镜像,下次用镜像来还原。这种方式的问题在于,环境不是一成不变的,有可能需要安装更多的软件在机器上,那么每安装点新东西(或者仅仅是做一些配置修改)都需要重新再打个镜像吗?虽然你可以通过纪律保证这件事情的发生,但还是太耗时了。

镜像+provision工具

镜像还是要有的,但镜像里面的东西可以是所有的环境的基础,并且是相当稳定的。然后针对不同的构建环境使用provision工具编写一些脚本,有些provision工具甚至可以提供批量安装的功能。常见的provision工具有Chef、Puppet及Ansible。重新做一个镜像的代价太大,但修改一个脚本,并做调试的代码就没那么大了。所以这是一个很好的企业级CI构建环境管理方案。

使用Docker

虽然使用provision工具可以大大减小环境管理的成本,但是还是无法避免有人ssh到slave上做一些事情,但并没有记录在脚本中,然后机器的环境就又不一致了。使用Docker可以很好地解决这个问题,每次运行任务都是从镜像启动的,这可以保证每次运行的环境都是一样的,从而避免了构建环境配置被篡改的问题。同时也不会把不同的构建环境(Java、Ruby)放在一个镜像上,避免了互相冲突的风险。显然,这种方式比镜像+provision工具的方式更胜一筹。

云CI的做法

看起来在自部署CI上你需要做很多事情来管理你的构建环境,那么同样的事情要在云CI上做会是什么情况呢?

拿CRP举个例子。当你需要构建一个Java项目时,你只需要选择编译这个任务,然后选择你需要的语言,再输入你的构建命令等配置就可以了。
自部署CI与云CI的环境管理

详情可以参看CRP的帮助文档。你完全不用操心构建环境是个虚拟机还是Docker容器,只需要知道它能够提供满足你的环境即可。看起来很简单是吗?其实也没有那么简单。

局限

让我们看看云CI的局限

对环境没有控制权

环境不是用户自己装的,所以肯定不是百分百为某个用户的场景定制的。所以有可能不能完全满足所有人的需求,这时候就需要用户自己在云CI提供的镜像的基础上再自己安装一些额外的软件。但是在安装软件之前,你首先要知道这个镜像已经包含了什么对吧。所以云CI平台需要提供这样一份清单,告知用户。但这样就足够了吗?凭一份清单让我去写构建脚本,无异于让我在纸上写程序。所以云CI平台还必须提供让用户对构建环境进行调试的能力。那么如何提供这种能力呢?目前市面上比较常见的方式是在构建结束之后保留构建环境一段时间(比如15分钟,30分钟等),然后提供给你ssh的权限,让你登录到构建机器上,这样你就可以随意地运行各种命令,看看到底为什么前面那次构建没有成功。

构建时间会比较长

同样因为用户的需求和预置的镜像不会完全匹配,所以用户需要安装一些软件,这也会花费更多的时间。解决这个问题的一种可能性是允许用户上传自定义镜像,但目前市面上还没有见到提供这样服务的云CI平台。另外,每次都使用完全干净的环境,就意味着每次构建都需要把应用程序依赖的包重新下载一次(比如mvn install)。这个问题Travis CI提供了一个解决方案,即你可以指定构建机器上的某个路径作为共享的目录,每次构建这个目录的内容都会是一样的。

总结一下:

  1. 使用自部署CI能对构建环境有完全的控制权,够得到完全的定制性,使得效率最高,但需要花费很多的精力来搭建环境和配置,尤其是当规模变大了之后,想要优化资源的配置,需要花费更大的精力。
  2. 使用云CI,在基本上零投入的情况下,它就可以能够满足大多数场景的需求,但是对构建环境缺乏控制权,构建环境也非完全定制化的,所以很难保证效率最高。
  3. 在云CI环境管理这件事情上,业界的产品一直在进步和演化。相信未来能够提供更好的服务。