云原生应有的大的关注点有四个:微服务、容器、CI/CD以及DevOps,本文详细介绍一些使用 CI/CD 促进微服务开发的实践。

一、什么是持续集成、交付和部署
CI/CD 包含几个整体流程:

持续集成:

开发人员频繁地将代码合并到主分支,所有的构建和测试都会每天自动执行,以确保主分支代码每天都是可以就绪发布的。CI 需要具备这些:

全面的自动化测试。这是实践持续集成&持续部署的基础,同时,选择合适的 自动化测试工具也极其重要;
灵活的基础设施。容器,虚拟机的存在让开发人员和 QA 人员不必再大费周折;

版本控制工具。如 Git,CVS,SVN 等;

自动化的构建和软件发布流程的工具,如 Jenkins,flow.ci;

反馈机制。如构建/测试的失败,可以快速地反馈到相关负责人,以尽快解决达到一个更稳定的版本。

持续交付/部署:

所有通过集成和交付的最新代码可以自动部署到(类)生产环境。持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。持续交付和持续集成的优点非常相似:

快速发布。能够应对业务需求,并更快地实现软件价值。

编码->测试->上线->交付的频繁迭代周期缩短,同时获得迅速反馈;

高质量的软件发布标准。整个交付过程标准化、可重复、可靠,

整个交付过程进度可视化,方便团队人员了解项目成熟度;更先进的团队协作方式。从需求分析、产品的用户体验到交互 设计、开发、测试、运维等角色密切协作,相比于传统的瀑布式软件团队,更少浪费。

单体应用的常规流水线

从历史上看,单体应用的 CI/CD 流程有一个相同的特征,每个项目都有一个单一的,相当复杂的流水线,不同的项目之间的流水线又截然不同,每个单体应用的流水线几乎总是连接到一个单独的 Git 仓库。

每个管道的独特复杂性使其很难长久维持,此外,每条流水线通常都仅由一小部分既熟悉应用内部结构、同时又对部署环境相当熟悉的人控制。对于每个项目来说,运维人员维护流水线结构,而开发人员只关注源代码,这与 Devops 理念是完全相反的,应该是所有人对基础设施共同承担责任,共同解决常见的问题。

微服务的 CI/CD

针对微服务架构的健壮的 CI/CD 流程有主要几个优点:

每个团队可以独立构建、集成和部署自己的微服务,而不会影响或破坏其他团队;

在将新版本的微服务部署到生产之前,它将首先部署到开发、测试和 QA 环境,在集成的每个环境都有质量管控;

可以非常容易并排部署新版本和之前的多个版本,以此来评估新版本的微服务;

易于建立分段且易于管理的访问控制。

在微服务开发团队中,绝不应该让每个团队都需要经过漫长的排队才能发布,建立一个微服务的团队可以随时发布更新,不需要等待另外一个或多个微服务集成、测试和部署。

为什么强大的CI/CD管道很重要

在传统的整体应用程序中,只有一个构建管道,其输出是应用程序可执行文件。所有开发工作都将馈入此管道。如果发现高优先级错误,则必须集成,测试和发布修补程序,这可能会延迟新功能的发布。您可以通过使用结构良好的模块并使用功能分支来最大程度地减少代码更改的影响来减轻这些问题。但是,随着应用程序变得越来越复杂,并添加了更多功能,整装件的发布过程往往变得更加脆弱,并可能会破裂。

遵循微服务理念,永远不会有一个漫长的发布培训,每个团队都必须排队。构建服务“A”的团队可以随时发布更新,而无需等待服务“B”中的更改被合并,测试和部署。

CI/CD整体图

为了达到较高的释放速度,您的释放管道必须自动化且高度可靠,以最大程度地降低风险。如果您每天一次或多次发布产品,则回归或服务中断必定很少发生。同时,如果确实部署了错误的更新,则必须以可靠的方式快速回滚或前滚到服务的先前版本。

微服务开发中 CI/CD 的挑战

尽管它可能对微服务开发工作更加有益,但 CI/CD 也带来了很多挑战:

安全、快速并持续发布新功能:管理频繁发布的功能需要保持警惕,尤其是当这些功能涉及到多个微服务的更改

管理复杂技术栈之间的部署:用户可能会遇到微服务应用在包含不同技术栈的环境中,这将面临许多繁琐的挑战

维护复杂分布式系统的完整性:如果项目涉及到将单体应用分解为较小的微服务,则系统整体复杂性将增加,可将将面临分布式系统的问题

微服务 CI/CD 的实践

这里有5种可靠的实践,可以帮助用户团队设计 CI/CD 流水线来管理微服务开发

1. 制定可靠的测试策略

如上图所示,从单体到微服务,我们可以看到图左边是一个单体服务,右边则是经过微服务拆解后的。我们可以看到它有 4 个特点:

  • 根据不同领域拆分
  • 服务之间通过网络协议通信
  • 拥有独立的数据库
  • 拥有特定对外开放的接口

微服务的测试和验证系统比测试传统的单体应用要复杂得多,有效的测试策略必须考虑对单个微服务的隔离测试,而且也需要验证整个系统的行为。

常规方法依然适用于微服务的上游测试,尤其是隔离测试。测试金字塔对于团队在保持各种类型的测试之间的平衡方面也很有用。但是这种方法在同时测试多个服务时有局限性,造成这种情况的主要原因是,用户无法模拟许多错误。这样的示例包括高度分布式系统中的不一致性或因为硬件/网络故障而导致的系统问题。

从这个微服务测试金字塔我们可以看到,越靠近底层成本越低,你只需要几行代码就能完成,效率也非常高。同时越底层它对于三方或组件的依赖也越低,自动化也就越简单。到这里可能就有人想问:“既然越底层的成本越低,那我们能不能只跑单元测试?”在解答这个问题前,大家先看下面这张图。

这两扇窗户,每一扇单独存在的时候都是完好的窗户,可以正常开合。但是两个都安装到墙上后就没有办法正常开合。这就是我们不能只跑单元测试的原因了,不跑集成测试就无法发现一些问题。同理不跑单元测试也会有一些无法发现的问题,所以我们在跑测试的时候,集成测试和单元测试,一项都不能少。

那具体实践的时候要如何做呢,我推荐大家分成两步来进行:

第一步是将底子打好:你需要对你的的微服务进行单元化测试,编写单元化的测试用例,然后再强化集成测试。没有好底子的微服务是不可靠的,任何时候都可能会出问题,而且出问题后的排查会非常费时。

第二步是自动化的持续集成环境:将能够自动化的部分全部进行自动化,减少人工的介入

2. 精心设计环境

环境计划概述了用户如何使用各种环境,以及在各种环境中移动或促进所有组件的策略。首先,考虑团队在各种环境中需要处理的用例非常重要,请记住,组织中不同小组有不同的需求,因此环境规划需要考虑所有这些不同的需求。

考虑团队如何利用云基础架构也很重要,以便后期可以动态创建这些环境。另外,用户需要制定一种策略,可以将所有组件从一个环境升级到另一个环境。由于 CD 流水线会不断生成很多组件,所以需要仔细考虑需要管理多少组件以及需要多少资源仓库。

3. 引入 CI 实践

在任何成功的 CD 策略中,持续集成都是至关重要的做法,这超出了有关构建定义和构建服务器的考虑范围。基于主干开发和特性功能开关模式是两个非常好的实践,它们对于建立可靠的 CI 流程非常有效。

通过基于主干开发,开发人员可以一起在主干分支中修改代码,这样做的目的是避免各分支中产生偏差,从而避免合并代码而带来的挫败感,基于主干的开发还需要用于实现特性功能开关控件。

通过特性功能开关控件,可以对正在进行的工作和已完成的工作都进行提交,这些开关可以使团队在不完整的功能投入生产后暂时禁用它们。通常,特性功能开关保留在代码库附近的配置或规范文件中,CD 流水线自动化机制将在相关环境中根据需要启动或禁用这些开关。

同样,用户可以使用其他类型的开关,例如使用发布开关对不完整的代码限制访问权限。其他还包括通过行为开关改变生产代码的访问权限、实验性开关用于多变量进行 AB Test,以及权限开关对特定用户启用特定的行为。

4. 管理好配置

应用程序的配置包含与部署相关的所有内容,因此应使配置文件与代码保持不同,可以想象,管理一个或多个微服务组的部署时,配置是不同的。

一种有效的方法是使用集中管理存储库(如 Vault 或 Consul)。另一种方式是标准化配置的分发方式,无论服务使用什么技术栈,通过这种方法,服务本身将通过自身的技术栈挑选配置。

最后,团队需要建立一个流程来保护证书等机密信息,以确保对他们进行适当的管理。通常,这个流程是手动的,但至关重要的是及早考虑将将其设置到位。

5. 为失败做准备

在微服务系统开发中,多个服务不断且频繁地发生更新,当服务部署过程中引入错误或者不稳定时,响应是什么?

在许多情况下,最好的补救措施是前滚,这意味着确定故障的根本原因,然后迅速修复程序。请记住,前滚的一项要求是团队已经设置了将修补程序分支发布到生产的功能,由于时间和协调原因,最好不要通过流水线解决生产问题。

相比之下,回滚在生产系统中通常是有问题的。大多数情况下,回滚很容易,更改粒度可以满足其他服务。但是,如果部署中包含更多复杂的更改,例如数据库表结构更改,则有必要将数据库的更改与代码更改分开部署,这需要单独来部署,以确保数据库更改和早期代码版本的兼容性。

总结

我们已经了解到,缩短发布周期、使发布更加灵活是我们追求微服务架构的两个主要优势,但是,缺乏持续集成和持续交付流程会使团队无法拥有支持微服务开发和交付所必需的的敏捷性。我们研究了挑战,并提出了一些将 CI/CD 实践与微服务应用程序开发相结合的方法和建议。

二、微服务架构如何构建持续交付工具链

持续交付工具链

持续交付在上一篇文章中已经提到,它是指所有开发人员始终让 Master 分支保持可随时发布的状态,根据实际需要来判断是否进行一键式发布。而工具链(Tool Chain)通常是指一系列工具,它们按照一定的逻辑顺序运行,最终完成一件比较复杂的事情。

因此,持续交付工具链是帮助我们把持续交付进行落地的工具集合或自动化平台,它可以固化产品交付过程中的各个环节,实现自动化地构建、部署、测试、输出报告等工作。如下图所示。

持续交付示意图

构建持续交付工具链需要考虑哪些内容?

通过上面的描述,不难看出,构建持续交付工具链涉及如下工作。

基础设施盘点:持续交付包含了产品交付过程的方方面面,因此需要盘点清楚在整个公司或项目里,现有的研发基础设施是怎样的,如何做代码和配置的管理,各个环境如何管理,构建和部署在多大程度上实现了自动化,测试阶段是如何流转的,有着怎样的质量目标,各种类型的自动化测试的建设情况,如何感知关键节点的变更和反馈,等等。可见,如果工具链是一座摩天大楼,那么研发基础设施就是它的地基。

组织支持:持续交付的建设涉及交付过程中的多个团队共同协同,所以不仅需要各部门管理层的支持,还需要一线员工有强烈的改进意愿。

关键过程自动化:针对上述基础设施进行盘点后,需要对其中的每一个环节尽可能地进行自动化管理,引入合适的工具或者自建工具来完成。比如,如果测试过程是纯手工测试,那么就难以在持续交付中发挥作用,因此可以把重复性的手工测试工作工具化或自动化,比如使用 Curl 或 HTTPclient 编写 HTTP 接口的自动化脚本,使用 Selenium 进行端到端测试,等等。在这个过程,要特别注意的是,尽量基于现有的研发基础进行工具化或自动化改造,持续交付涉及的环节太多了,切记不可重复造轮子,尽量和其他团队共同建设,否则太容易和其他团队形成对立的局面,最终拖垮整个工具链的建设。

工具的整合:最后需要用持续交付工具对上述工具或自动化设施进行整合,实现“链”的效果。

现阶段持续集成和持续交付思想已经盛行起来,绝大多数的公司和团队能够认识到这种变化的重要性,因此组织方面的支持通常没有太大问题,但需要考虑落地的成本。因此,对于持续交付工具链的建设,可以借鉴常规的产品研发项目,使用小步快跑的方式,以“先有后优”的心态建设。如以下措施。

规模:在落地规模上,先在小范围试点,逐渐成熟了之后再推广到更大的范围。

成熟度:持续交付体系的搭建几乎是永无止境的,应先实现框架,再逐步丰富或完善各个环节,使成熟度逐渐改进。

持续交付全流程

在产品研发交付过程中,不外乎有如下几个方面:代码&配置管理、构建&部署自动化、各种测试、反馈相关等内容。

1、代码&配置管理

通常来说,代码用成熟的工具管理起来的成本不高,比如常见的代码管理工具 GitHub、Atlassian Stash、GitLab 等。但在配置方面,常常有比较明显的问题,需要将配置进行统一化、自动化地管理。

2、构建&部署自动化

在构建方面,比较推荐的是 Maven,它的“惯例胜于配置”的原则,使你只要按它指定的方式组织代码,就可以使用一条命令执行所有的构建、部署、测试和发布任务。而且它能自动管理项目间的依赖,这对于构建持续交付来说太友好了。

环境部署则需要能够对测试环境、预发布环境、生产环境的修改用自动化来完成。如果你还在以手工的方式远程登录到这些环境上执行部署工作,那就太 out 了。现阶段将部署完全脚本化已不存在任何技术难度。如果需要部署多台机器,充其量就是先分发到这些机器上,再在这些机器的本地执行部署脚本。

3、各种测试

在没有持续集成和持续交付建设的团队里,测试环节通常比较滞后,且人工占比较高,这无疑给项目带来比较大的质量风险。因此,在测试环节的一个非常重要的策略是要尽可能地把各种测试过程自动化,且覆盖度和稳定性达到一定要求。

整个测试过程不仅有静态测试,也有动态测试。静态测试中的各类文档评审,比较难以自动化,但静态代码检查通常有现成的工具,比如最流行的 Sonar。动态测试包含了功能测试(微服务架构的分层测试)和非功能测试(性能测试、安全性测试),可以针对这些类型的测试进行自动化的改造。比如使用 Spring Cloud Contract 和 Pact 可以进行微服务的契约测试,使用 Jmeter 以非 GUI 的方式做性能测试、使用 SQLmap 检测诸如 SQL 注入的安全问题。自动化建设的投入一定要遵循“测试金字塔”,努力提升单元测试自动化的比重,同时降低端到端自动化测试用例的比重。

在进行自动化建设时,并不是每种测试都需要自动化,尽量只把执行过程中易出错、烦琐的步骤变成可靠且可重复的自动化步骤。比如,每次测试用户评价都需要先构造一笔真实的订单,那么构造订单和用户评价都属于操作烦琐的步骤。当然对于有些测试内容,人比机器更靠谱,手工测试必不可少,比如体验类、界面类功能等。

另外,在持续发布过程中,还需要在 Staging 和 Prod 环境进行回归。通常,这两个环境因为涉及线上数据库,以自动化的方式在这两个环境写入数据会有比较高的质量风险,因此可以只进行读取操作的自动化,其他内容用手工测试来完成。

持续交付过程中,对自动化测试有四个关键要求。

速度 :自动化测试必须快速运行,以便在发生故障时向团队提供快速反馈。应尽可能多地使用单元测试,引入少量的端到端自动化用例,以便发现单元测试可能无法发现的故障。最后,应该少进行 UI 类的测试,因为它们花费很长的编写时间和很长的运行时间,尽管它们有时能够发现其他缺陷。

可靠性:持续交付过程中运行自动化的关键在于自动化需要足够稳定。如果自动化发现了新的缺陷,那么其运行不稳定是良性的。否则,持续交付过程中自动化用例执行的频率很高,不稳定的自动化 case 会消耗掉一个测试人员太多的维护时间。

数量:由于每次代码或环境变更都会触发自动化测试,随着用例数不断增多,自动化用例执行的时间也在增加,因此在编写用例时要避免增加无效的 case,避免设置无效的轮询等待时长,尽可能多地捕获相关的异常。

维护:保持高稳定性离不开自动化测试用例的维护。

4、反馈相关

要想能做到快速反馈,就需要关键阶段有结果并对结果进行通知。

1)结果:结果分为两种:构建或部署结果、测试结果。

构建和部署的结果相对明确,判断是否与构建和成功部署的状态匹配即可。

测试结果需要设置预期结果,比如测试通过率、代码覆盖率等,也可以设置不同阶段的测试目标,如代码提交后的测试、研发人员提交测试给测试人员、发布前测试等。不同阶段的测试内容也不尽相同,比如代码提交后的测试,主要是静态代码检查、分支规范检查,而提测时的检查则涉及自动部署检查、冒烟测试自动化、功能自动化测试和非功能测试自动化等内容。

2)通知

通知的方式主要通过 IM 或者邮件的方式进行,尽可能做到每一个关键步骤都有通知。对于不满足质量要求的通知,需要有更强烈的通知,比如标红的文字或者多种方式联合通知。

3)运营

只是通知还不够,很多时候一些不太好的数据需要持续的运营。比如,部署环境总是失败,阻塞了工具链的执行;提测后自动化用例执行失败较多,需要进一步查看是自动化用例稳定性问题、环境稳定性问题还是代码质量问题;这些内容都需要有量化的数据才有利于改进。

工具的整合

用于持续交付的工具有很多,这里不一一列举,借用James Bowman的一张图:

除了图片中的工具,还有很多在持续交付过程中发挥作用的工具:

服务发现和全局配置存储,例如 ZooKeeper 等;

安全管理和监视工具,例如 Fortify、Vault 等;

静态代码分析工具,例如循环复杂度、覆盖范围、质量、标准等;

编程语言,工具和框架,例如编译器、IDE 等;

用于测试的模拟工具,例如 Mockito 等;

质量管理工具,例如 Jira 等;

发布管理工具,例如 LaunchDarkly 等;

于云供应商的工具和工具链,例如适用于 AWS 的 Cloudformation 和 CodeDeploy 等。

可以对持续交付过程的工具进行整合的工具有很多,最常用的是 Jenkins,其中尤为推崇 Jenkins 2.x 。