大师Martin Fowler对“ 持续集成 ”是这样定义的: 持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。
当微服务产生后,持续集成也不得不被考虑起针对这种可以独立部署的服务, 在数字化企业云平台中有十四个微服务同时运行 ,如何建立起与之的映射,即微服务、CI构建与源码的映射变得极为重要,如果还像简单软件那样集中管理是否还行得通,那可能会是一场灾难?在如此复杂的背景下,优良的持续集成方案同样也会给我们带来焕然一新的便利体验。
1.一个代码库、一个CI构建
这种方式就是将所有的微服务放在同一个代码库中,并且使用一个CI构建。这么做唯一的好处就是只需要管理一个代码库,但随之而来的麻烦会让你应接不暇。
每当我们修改一个服务中的一行代码后,我们必须重新构建所有的服务,所有的构建产物都是在同一个构建中完成。事实上其他的服务完全没有重新构建的必要,这样大大延长了上线速度。而在众多构建物中要找出保证能够让你修改生效的服务来部署,也是足够让人头疼了,这导致最终让我们选择重新部署所有代码。再试想一下,所有人共享一个CI构建,每个人的修改都有可能造成CI的构建失败,想要将这个CI构建成功稳定下来,头痛又升级了,有没有?
在众所周知的谷歌使用的就是一个代码库,然而他们采用了自己的版本管理系统 “piper” 来管理超过20亿行代码的超级大库,所以对于更多的小公司来说,这时谷歌就是一个特例。
在桌游盛行的年代有一款叫做 bucket king的桌游 ,每位玩家会被分到五种不同颜色的小桶,每种颜色有三个小桶,玩家必须以金字塔的形式堆叠起这些桶(即第一排5个第二排4个以此类推第五排1个)。游戏过程中当玩家没有某种颜色手牌可出的时候必须抽掉一个相应颜色的桶,所有倒下的桶都会作废。
说到这里大家想到了什么?是否很像我们一个代码库对应一个CI构建的持续集成模式?每种颜色的桶都是一个微服务,当我们某个微服务中的某一个环节出现了变动,我们的整个金字塔就变得不再完整,甚至出现崩溃。这个时候你想象每种颜色小桶分别堆成一个金字塔,分成五堆就可以了,这就像我们之前提到的采用多个CI构建。当然游戏规则是不允许玩家这么做的,但是持续集成是可以的。
2.一个代码库、多个CI构建
在这种方式中,代码库还是那个代码库,不过在代码库中我们创建了多个子目录,每个子目录对应一个CI构建。现在的很多项目中都会采取这种持续集成,这让我们可以比较方便的同时提交对多个服务的修改。然而,来让我们琢磨一下这种方式依旧会存在的弊端,随着代码修改的增加,无可避免的会在不经意间造成服务耦合度的增加,对不对?
我们来假设一下,在bucket king游戏中所有的桶都是一个代码库,允许我们将这些桶分成五个小金字塔,看上去我们降低了游戏的难度,但我们不能阻止玩家将不同颜色的小桶混搭。比如在一个2+1的紫色小金字塔中,玩家将底层的一个紫色小桶换成了蓝色小桶,游戏进行到我们必须抽取掉一个蓝色小桶,并且其他两个蓝色小桶已经“阵亡”了,这时候当我们抽取掉这个蓝色小桶时,势必使得上层的紫色小桶一起掉落。这就类似于一个代码库多个CI构建一样。我们无法避免开发者在修改代码过程中的耦合,当这些代码出现问题的时候受到影响的绝对不会只有一个构建。
3.多个代码库、多个CI构建
每个微服务都有一个对应的代码库,每个代码库对应一个CI构建。这时候每个微服务变得独立,修改运行部署不再相互依赖,大大降低了耦合度,方便了代码的管理和维护。另外多代码库还给我们带来另一个好处,就是共享问题,当你只有一个代码库的时候,如果想要共享,那必然所有代码都必须共享,拆分多个代码库,这样根据自己情况,你可以选择性的共享代码。当然,你又要说了,跨服务的修改变的麻烦了啊。没错,这也不是一个十全十美的方案,我们要做的就是选择一个最佳方案,是不是?
所以,bucket king游戏中最理想的状态就是我们规定五堆小金字塔必须为同种颜色不允许混搭,这时候即使三个蓝色小桶全部“阵亡”也不会影响到紫色小桶。这就好比同种颜色的小桶是一个业务,由于微服务按照业务划分即一个微服务对应一个业务,也就意味着每个微服务都是一个单独的代码库,也对应一个CI构建。当然作为游戏它就失去了它的趣味性,不过类比到持续集成我们当然是要选择对整体影响最小的方法。
针对微服务的持续集成,我们选择多个代码库多个构建的方案。 当我们创建一个微服务时,会产生一个git代码库,虽然我们每个微服务可以创建多个版本,但是我们是不可以同时编辑多版本的,因为我们开发代码是在git库master支线上,当一个版本发布后我们会给这个发布的版本打一个分支,这时候我们才能编辑新的版本。所以我们的CI构建就映射到一个微服务版本中,每个微服务代码提交代码库时会产生一个commitId,通常我们会针对这个commitId来进行一次构建,因此代码库分治也一定程度上避免了commitId不能与某个微服务对应的问题。 对于数字化企业云平台的集成,每次构建编译结束,产物就是一个完整的镜像。
我们甚至想过可以选择每个组件一个代码库,这样做的好处就是更加细化的分治,使得我们编译部署影响范围更加缩小。
就微服务而言,笔者认为的最佳的实践方案无疑是每个微服务对应一个代码库,每个代码库对应一个CI构建,分治远远好过于集中管理,你怎么看?