当我们完成业务代码的开发后,就需要进入部署阶段。在部署过程中,我们将会引入持续集成、持续交付、持续部署,并且阐述如何在微服务中使用他们。

1. 持续集成、持续部署、持续交付

在介绍这三个概念之前,我们首先来了解下使用了这三个概念之后的软件开发流程,如下图所示:

首先是代码的开发阶段,当代码完成开发后需要提交至代码仓库,此时需要对代码进行编译、打包,打包后的产物被称为“构建物”,如:对Web项目打包之后生成的war包、jar包就是一种构建物。此时的构建物虽然没有语法错误,但其质量是无法保证的,必须经过一系列严格的测试之后才能具有部署到生产环境的资格。我们一般会给系统分配多套环境,如开发环境、测试环境、预发环境、生产环境。每套环境都有它测试标准,当构建物完成了一套环境的测试,并达到交付标准时,就会自动进入下一个环境。构建物依次会经过这四套环境,构建物每完成一套环境的验证,就具备交付给下一套环境的资格。当完成预发环境的验证后,就具备的上线的资格。

测试和交付过程是相互伴随的,每一套环境都有各自的测试标准。如在开发环境中,当代码提交后需要通过编译、打包生成构建物,在编译的过程中会对代码进行单元测试,如果有任何测试用例没通过,整个构建流程就会被中止。此时开发人员需要立即修复问题,并重新提交代码、重新编译打包。

当单元测试通过之后,构建物就具备了进入测试环境的资格,此时它会被自动部署到测试环境,进行新一轮的测试。在测试环境中,一般需要完成接口测试和人工测试。接口测试由自动化脚本完成,这个过程完成后还需要人工进行功能性测试。人工测试完成后,需要手动触发进入下一个阶段。

此时构建物将会被部署到预发环境。预发环境是一种“类生产环境”,它和生产环境的服务器配置需要保持高度一致。在预发环境中,一般需要对构建物进行性能测试,了解其性能指标是否能满足上线的要求。当通过预发验证后,构建物已经具备了上线的资格,此时它可以随时上线。

上述过程涵盖了持续集成、持续交付、持续部署,那么下面我们就从理论角度来介绍这三个概念。

1.1 持续集成

“集成”指的是修改后/新增的代码向代码仓库合并的过程,而“持续集成”指的是代码高频率合并。这样有什么好处呢?大家不妨想一想,如果我们集成代码的频率变高了,那么每次集成的代码量就会变少,由于每次集成的时候都会进行单元测试,从而当出现问题的时候问题出现的范围就被缩小的,这样就能快速定位到出错的地方,寻找问题就更容易了。此外,频繁集成能够使问题尽早地暴露,这样解决问题的成本也就越低。因为在软件测试中有这样一条定律,时间和bug修复的成本成正比,也就是时间越长,bug修复的成本也就越大。所以持续集成能够尽早发现问题,并能够及时修复问题,这对于软件的质量是非常重要的。

1.2 持续部署

“持续部署”指的是当存在多套环境时,当构建物完成上一套环境的测试后,自动部署到下一套环境并进行一系列的测试,直到构建物满足上线的要求为止。

1.3 持续交付

当系统通过了所有的测试之后,就具备了部署到生产环境的资格,这个过程也就被称为“交付”。“持续交付”指的是每个版本的构建物都具有上线的资格,这就要求每当代码库中有新的版本后,都需要自动触发构建、测试、部署、交付等一系列流程,当构建物在某个阶段的测试未通过时,就需要开发人员立即解决这个问题,并重新构建,从而保证每个版本的构建物都具备上线的资格,可以随时部署到生产环境中。

2. 微服务与持续集成

当我们了解了持续集成后,下面来介绍微服务如何与持续集成相整合。当我们对系统进行了微服务化后,原本单一的系统被拆分成多个可独立运行的微服务。单服务系统的持续集成较为简单,代码库、构建和构建物之间都是一对一的关系。然而,当我们将系统微服务化后,持续集成就变得复杂了。下面介绍两种在微服务中使用持续集成的方法,分别是单库多构建和多库多构建,并依次介绍这两种方式的优缺点及使用场景。

2.1 单库多构建

“单库”指的是单个代码仓库,即整个系统的多个模块的代码均由一个代码仓库维护。“多构建”指的是持续集成平台中的构建项目会有多个,每个构建都会生成一个构建物,如下如所示:

在这种持续集成的模式中,整个项目的所有代码均在同一个代码仓库中维护。但在持续集成平台中,每一项服务都有各自独立的构建,从而持续集成平台能够为每一项服务产出各自的构建物。

这种持续集成的模式在微服务架构中显然是不合理的。首先,一个系统的可能会有很多服务构成,如果将这些服务的代码均在同一个代码仓库中维护,那么一个程序员在开发服务A代码的时候很有可能会因为疏忽,修改了服务B的代码,此时服务B构建之后就会存在安全隐患,如果这个问题在服务B上线前被发现,那么还好,但无疑增加了额外的工作量;但如果这个问题及其隐讳,导致之前的测试用例没有覆盖到,从而服务B会带着这个问题进入生产环境,这可能会给企业带来巨大的损失。所以,在微服务架构中,尽量选择多库多构建模式来实现持续集成,它将带来更大的安全性。

虽然这种模式不合理,但它也有存在的必要性,当我们在项目建设初期的时候,这种模式会给我们带来更多的便利性。因为项目在建设初期,服务之间的边界往往是比较模糊的,而且需要经过一段时间的演化才能够构建出稳定的边界。所以如果在项目建设初期直接使用微服务架构,那么服务边界频繁地调整会极大增加系统开发的复杂度,你要知道,在多个系统之间调整边界比在单个系统的多个模块之间调整边界的成本要高很多。所以在项目建设初期,我们可以使用单服务结构,服务内部采用模块作为未来各个微服务的边界,当系统演化出较为清晰、稳定的边界后再将系统拆分成多个微服务。此时代码在同一个代码仓库中维护是合理的,这也符合敏捷开发中快速迭代的理念。

2.2 多库多构建

当系我们的系统拥有了稳定、清晰的边界后,就可以将系统向微服务架构演进。与此同时,持续集成模式也可以从单库多构建向多库多构建演进。

在多库多构建模式中,每项服务都有各自独立的代码仓库,代码仓库之间互不干扰。开发团队只需关注属于自己的某几项服务的代码仓库即可。每一项服务都有各自独立的构建。这种方式逻辑清晰,维护成本较低,而且能避免单库多构建模式中出现的影响其他服务的问题。

3. 微服务构建物

持续集成平台对源码编译、打包后生成的产物称为“构建物”。根据打包的粒度不同,可以将构建物分为如下三种:平台构建物、操作系统构建物和镜像构建物。

3.1 平台构建物

平台构建物指的是由某一特定平台生成的构建物,比如JVM平台生成的Jar包、War包,Python生成的egg等都属于平台构建物。但平台构建物运行需要部署在特定的容器中,如war需要运行在Servlet容器中,而Servlet容器又依赖的JVM环境。所以若要部署平台构建物,则需要先给它们提供好运行所需的环境。

3.2 操作系统构建物

操作系统构建物是将系统打包成一个操作系统可执行程序,,如CentOS的RPM包、Windows的MSI包等。这些安装包可以在操作系统上直接安装运行。但和平台构建物相同的是,操作系统构建物往往也需要依赖于其他环境,所以也需要在部署之前搭建好安装包所需的依赖。此外,配置操作系统构建物的复杂度较大,构建的成本较高,所以一般不使用这种方式,这里仅作介绍。

3.3 镜像构建物

平台构建物和操作系统构建物都有一个共同的缺点就是需要安装构建物运行的额外依赖,增加部署复杂度,而镜像构建物能很好地解决这个问题。

我们可以把镜像理解成一个小型操作系统,这个操作系统中包含了系统运行所需的所有依赖,并将系统也部署在这个“操作系统”中。这样当持续集成平台构建完这个镜像后,就可以直接运行它,无需任何依赖的安装,从而极大简化了构建的复杂度。但是,镜像往往比较庞大,构建镜像的过程也较长,从而当我们将生成的镜像从持续集成服务器发布到部署服务器的时间将会很长,这无疑降低了部署的效率。不过好在Docker的出现解决了这一问题。持续集成平台在构建过程中并不需要生成一个镜像,而只需生成一个镜像的Dockerfile文件即可。Dockerfile文件用命令定义了镜像所包含的内容,以及镜像创建的过程。从而持续集成服务器只需将这个体积较小的镜像文件发布到部署服务器上即可。然后部署服务器会通过docker build命令基于这个Dockerfile文件创建镜像,并创建该镜像的容器,从而完成服务的部署。

相对于平台构建物和操作系统构建物而言,镜像构建物在部署时不需要安装额外的环境依赖,它把环境依赖的配置都在持续集成平台构建Dockerfile文件时完成,从而简化了部署的过程。