持续集成服务器部署策略
在软件交付领域上有一个非常有用的启发式原则:提前并频繁地做让你感到痛苦的事。集成通常是一个非常痛苦的过程,所以每次有人提交代码后应立刻进行集成。
马丁福勒说:
持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。
而持续集成服务器(也称为构建服务器,又称CI服务器)是一种软件工具,它包含所有持续集成操作,并为我们构建项目提供可靠和稳定的环境。
工欲善其事,必先利其器。持续集成的重要性毋庸置疑,但是理解如何创建安全、高效、可伸缩的持续集成服务器将使得持续集成更加顺畅。
CI服务器 - 安装方式
CI服务器的安装方式有四种:
-
单机
单机的CI服务器即安装在一台单个主机上,通常用于小型项目。 -
托管
托管的CI服务器由托管平台提供,一般是根据订阅计价。这一般被那些不想维护CI服务器的企业所采纳。 -
私有云
一些大型企业或者想要完全掌控及安全使用他们自己的基础设施的组织,通常会选择私有云的部署方式。 -
半托管半私有云
这种方式是对托管部署和私有云部署的折中方案。这种部署方式通常将流水线管理,日志管理,用户管理部分进行托管,而将流水线运行时环境放在私有云中。这种方式即保证了基础设施安全,同时也降低了维护CI服务器的成本。
我所在的团队最初使用私有云的方式搭建CI服务器,但出于可维护性、成本和安全性等方面的折中考虑转向了半托管半私有云的安装方式。本文将主要讨论在私有云和半托管半私有云安装方式下CI服务器的部署策略。
CI服务器 - 主从架构
主从架构也叫主仆架构或者Master-Slave架构,主从式架构意图提供一个可伸缩的架构,其核心是基于分而治之的思想,将一个原始任务分解为若干个子任务,并由专门的工作者来执行这些任务,原始任务的结通过整合各个子任务的处理结果形成。
图1 - 主从架构主从架构看似与CI服务器毫无关系,实则联系紧密。因为很多CI服务器使用了主从架构或者服务器/代理架构。通过主从架构的Master可以进行用户管理、流水线管理、日志管理。Master作为任务调度者,给多个主从架构的Slave分配任务,Slave会将流水线任务运行的信息和结果发给Master。主从架构提供了一个可伸缩的架构,所以可以动态的添加或者删除Slave以支持团队动态变化的持续集成任务。
CI服务器 - 部署策略
通常,一个开发团队会有三个应用部署环境:开发环境(Dev),类生产环境(Staging)和生产环境(Production)。当一次修改通过持续集成后,就会被部署到开发环境,然后部署到类生产环境,然后部署到生产环境。
为此,需要CI服务器具有操作相应部署环境的权限。如图2,开发环境中部署了CI服务器,其中每个CI服务器都具有开发、类生产和生产环境的权限。
图2这种方式粗暴简单,看似药到病除实质上却会引发很严重的安全问题。通常项目中对开发、类生产和生产环境权限的管控类似于金字塔,越往上权限管控越严格。但如果开发环境的CI服务器也具有类生产和生产环境的权限,那么这座本来坚固的金字塔将有可能瞬间崩塌。
为了解决开发环境CI服务器具有多环境操作权限的问题,如图3所示,可以在不同的环境独立部署一套CI服务器,这样部署某个环境时,就可以使用相应环境的CI服务器即可。
图3但随之而来的问题是,不同的环境独立部署一套CI服务器,会使得每个环境的CI服务器之间没有任何关系。它们的流水线管理,日志管理,用户管理都是独立的。这意味着原本一个流水线完成的事情会分散的对个多个持续集成系统中,用户需要在多个系统中来回切换。这不仅降低了使用体验,并且增加了复杂性和维护成本。
那么有没有什么办法,既保证只部署一套CI服务器,同时又能保证CI服务器不能跨环境操作?如图4所示,虽然所有的CI服务器依然部署在开发环境中,但每个CI服务器代理已经被打上了开发环境、类生产和生产环境的标签,并且只具有操作相应环境的权限。
图4这种部署策略看似解决了安全问题,但实际上有点掩耳盗铃,因为只要开发环境上的用户具有足够的权限,他就有可能可以修改CI服务器代理的权限,从而达到操纵其他环境的目的。
既然如此,那么可不可以将CI服务器代理部署到相应的环境中?如图5所示,不同环境将维护相应的CI服务器代理,通过这种部署方式,既保证只部署一套CI服务器,同时又能保证CI服务器代理不能跨环境操作。
图5既然CI服务器代理可以部署到不同的环境中,那么CI服务器的Master还需要继续放在开发环境中吗?这视情况而定,如果团队非常小,也许可以考虑将CI服务器的Master部署在生产环境中,以限制操作CI服务器的Master。如果团队非常大,如图6所示,也许可以考虑将CI服务器的Master部署到一个独立的环境中,而这个环境则由一个专门的持续集成团队来维护。持续集成团队需要保证CI服务器的Master的可用性。而业务开发团队只需要维护CI服务器代理。
图6假设,某公司有3个业务开发团队:Team A、Team B和Team C。如图7所示,Team A、Team B和Team C可以共用由持续集成(CI)团队维护的Master服务器。
图7但多个开发团队共享一个CI服务器的Master又引发了另一个问题。假设,Team A现在要创建一个流水线,那么Team B和Team C如何限制Team A无法使用Team B和Team C所维护的CI服务器代理?
一个团队在创建一个流程水线时,可以根据CI服务器代理的标签来指定流水线不同的任务使用相应环境的CI服务器代理。对于CI服务器的Master来说,Team A、Team B和Team C的CI服务器代理都只是普通的代理,它们之间除了具有不同的标签,CI服务器的Master对它们其他的信息一无所知。
但对于CI服务器代理,它是有能力知道在它上面运行的流水线任务是不是合法。假设Team A、Team B和Team C的代码分别位于不同的git组织(organization): team-a-org, team-b-org和team-c-org。通常一个流水线任务在CI服务器代理上执行的第一件事情就是在git上拉取代码库的代码,而代码库是位于特定的git组织之下的,那么如图8所示,通过在团队的CI服务器代理配置git组织的白名单即可防止其他团队的流水线任务使用自己团队的CI服务器代理。
图8随着公司业务的发展,项目不断的增多,每日需要持续集成的任务越来越多,这就需要更多的CI服务器代理。如果Team A、Team B和Team C分别增加了CI服务器代理以满足自己团队持续集成任务的需要,但这些CI服务器代理在团队之间是不能共享的,这就带来了资源浪费。
仔细研究持续集成任务,持续集成的大多数任务是不需要操作环境资源的,它们只需要拉取代码的权限,然后运行测试。如此一来,如图9所示,可以由CI团队维护一组共享的CI服务器代理,这些代理不具有任何操作环境资源的能力,它只是一个测试运行的环境。
图9由此,可以将CI服务器代理分为环境操作无关和环境操作相关两类。环境操作无关的CI服务器代理由CI团队维护,通常用来做持续集成,环境操作相关的CI服务器代理由业务开发团队维护,通常用来做持续部署。
如图10所示,当代码修改后会自动触发CI流水线。对于与环境操作无关的任务:单元测试和验收测试,可以使用由CI团队维护的共享的CI服务器代理来执行。而对于与环境操作相关的任务:部署开发环境、部署类开发环境和部署开发环境则使用具有相应环境操作权限的CI服务器代理来执行。
图10对于一个组织来说,如果选择同时维护CI服务器Master和CI服务器代理,那么使用的就是私有云安装方式。但是维护CI服务器Master需要投入大量的人力来维护其可用性,并不是每个组织都愿意为它买单,在这种情况下可以选择使用半托管半私有云的安装方式,如图11所示:
图11通过使用半托管半私有云的安装方式,一个组织里每个团队只需要维护CI服务器代理。这些CI服务器代理通过Key校验后与托管的Master相连,CI服务器代理不维护任何状态,它只提供了一个任务运行时的环境,在任务运行的过程中CI服务器代理将任务运行的状态和日志发给Master。这种情况下当一个CI服务器代理执行完任务后如果没有其它的任务,其实这个CI服务器代理所占的资源就可以被释放了,当有任务时再立即分配资源给CI服务器代理,然后执行任务。
CI服务器 - 按需分配
为了合理的利用资源,需要对CI服务器进行按需分配。然而现在主流的CI服务器都没有原生的支持按需分配的功能,所以需要开发团队自己实现如何对CI服务器进行按需分配。
如图12所示,通过信息收集器(Metrics Collector)来收集Team A的CI服务器代理的使用状态,收集的信息如:正在运行任务的数量、已安排要运行任务的数量、未完成任务的数量、空闲CI服务器代理的数量、繁忙CI服务器代理的数量和CI服务器代理的总数。通过这些信息判断是否需要对CI服务器代理扩容或者减容,通过这种方式达到按需分配。
图12总结
选择何种CI服务器的安装方式或者部署策略取决于团队对于安全性、可维护性、成本和团队自身能力的权衡。团队应该根据团队当前的情况动态调整CI服务器的安装方式和部署策略。