微服务架构下 CI/CD 如何落地


本文主要围绕以下两部分展开:
  • 理论篇,讨论从单体到微服务的过程中会面临怎样的挑战,以及微服务的测试模型
  • 实践篇,围绕集成测试环境的服务发现需要怎么做,如何落地持续交付和持续部署


背景

或许大家对于互联网公司的共同印象是 996,对于我个人而言,互联网公司还有另一个特点,那就是经常需要拥抱变化。在互联网公司,新产品上线、下线、调整,这都是很家常便饭的事情。在这种情况下,一个好的松耦合的架构就显得尤为重要。

刚好我最近就有遇到这个问题,我在做的项目,账号这块是对标 GitHub 的注册制账号机制的。原本的需求是用户注册我们的平台,注册完成后可以创建一个属于自己的团队,并将其他人拉入自己的团队。但是当我们做完这部分内容后发现,客户还是更偏好「账号+子账号」的模式,公司一个总的账号,所有员工单独开子账号进行关联。这让我们已经做好的项目变得非常尴尬,需要立即拥抱变化,需要根据最新的需求进行调整。这时,我就发现拥有一套松耦合的架构的重要性,比如账号这一部分,如果把它单独拎出来,做好足够的抽象,提供必要的对外接口,可能会更加灵活,扩展性更加好。

那怎样拥有一套松耦合的架构?有什么好的方案呢?在我看来有两个,一个是几年前出现的 SOA,即将服务进行单独化,将每一块都进行拆分;另一个就是最近几年火热的微服务了。我认为,微服务跟 SOA 其实是一回事,只不过微服务比 SOA 拆分粒度更细,功能也更小。

在调研微服务过程中,很多人会有疑问:“我们是一个很小的团队,小团队适不适合上微服务呢?”。因为上微服务就意味着一个服务可能就会被拆分成 10 个、20 个甚至更多个的服务,这就让小团队不得不去考虑自己的测试、部署、更新成本是不是会翻很多倍。

那么我对于“小团队适不适合上微服务”这个问题的答案是什么呢?我认为是完全可以上的,不过你需要注意一点:做好自动化,能交给自动化来实现的,就不要人工介入了。

在聊如何做自动化集成测试(CI)之前,我先和大家谈一谈从单体如何到微服务,服务是如何拆分的,以及微服务的测试一般是怎么做的。

从单体到微服务

1.png

如上图所示,我们可以看到图左边是一个单体服务,右边则是经过微服务拆解后的。我们可以看到它有 4 个特点:
  • 根据不同领域拆分
  • 服务之间通过网络协议通信
  • 拥有独立的数据库
  • 拥有特定对外开放的接口


2.png

微服务测试模型

我们都知道,如果需要一个服务能够稳定运行,那测试肯定是少不了的。而就像我们微服务化有一套理论一样,微服务测试也拥有属于自己的金字塔理论:最底层是单元测试,成本相对较低,像我们在 API 认证部分做的签名校验模块,它一般不需要依赖其他东西,因此测试效率也比较高;第二层是集成测试,这一层你就必须要依赖一些第三方的服务模块或者组件,比如我们一般会用到数据库的测试,就属于集成测试的范畴;第三层是 e2e 测试,它会模拟客户端的行为来进行测试,大家也许都接触过这类测试,像K8S 就有一个 e2e 测试,当你去申请 CNCF 的一致性认证时,就需要通过官方提供的 e2e 测试;最上层是 UI 测试,比如对于页面的点击调整是否符合预期,这部分我们现在做的比较弱,还处在人工模式下,但我们也在努力将它更新成自动化。

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

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

那具体实践的时候要如何做呢,我推荐大家分成两步来进行:
  • 第一步是将底子打好:你需要对你的的微服务进行单元化测试,编写单元化的测试用例,然后再强化集成测试。没有好底子的微服务是不可靠的,任何时候都可能会出问题,而且出问题后的排查会非常费时。
  • 第二步是自动化的持续集成环境:将能够自动化的部分全部进行自动化,减少人工的介入。


GitLab/CI

自动化集成环境这块目前已经有很多的开源方案了,比如常见的 Jenkins,还有 GitLab。我们选择的是 GitLab,或者说是选择了 GitLab/CI,选择它的原因有以下几点:
  • 统一的 Web 页面
  • 可以再 MR 中跳转查看
  • Pipeline 编排直观展示
  • 所有操作都在项目中搞定
  • GitLab 官方支持


GitLab WorkFlow

既然我们选择使用了 GitLab,那我们内部就会严格遵守 GitLab 的 WorkFlow。WorkFlow 主要分为两个部分。

第一部分是面向代码仓库。代码仓库中,我们一般会有三类分支,第一类分支是 master 分支,一般只会有一个,我们会定义 master 分支,并基于这个分支进行线上版本的发布。第二类分支是 develop 分支,一般也只会有一个,develop 分支是从 master 分支中 checkout 出来的,功能比 master 领先,包含一些已经完成功能开发,但是还没有发布的功能。第三类分支是 feature 分支,特性分支,一般会有很多个,新功能都会在这个分支上进行开发,往往一个功能对应一个 feature 分支。最后一类是 hotfix 分支,这个就比较常见了,线上发布后,如果发现了一个需要紧急修复的bug,这时你就可以在 master 分支上 checkout 出来一个 hotfix 分支,把代码改掉。不过进行这个操作时你需要注意,master 分支和 develop 分支都需要进行该 commit 合并,否则就不能算完成了 bug 修复。

第二部分与 CI/CD 有关。以我们的流程举例,研发的同学提交代码到 GitLab 仓库,之后 GitLab 会触发事先约定好的 CI 的 pipeline 进行测试和构建。等待测试和构建成功后再进行 code review 的确认,确认无误后会合并到 develop 分支并最终合并到 master 分支进行发布。这就是 GitLabCI 的一个配置,总结来看可以划分为下图的四个阶段。
4.png

下图是配置文件对应的 pipeline 的展示,大家可以看一下。
5.png

微服务下的场景变形

其实到目前为止的方案,已经是微服务没有大热前的完备方案了。如果你想要将方案运用到微服务的集成测试里,你还需要做一些变形,不妨参考下图中所示的又拍云现在使用的整套流程。
6.png

从图中可以看到,我们目前使用的整套流程相比标准的其实有做一些小的变形,变形主要集中在中间的集成测试环境这一块,我们将每个服务器都部署在了集成环境内,使集成环境变成一个准发布环境。具体流程是,当我们的创建 projectA 后,由它来 push 代码,完成后触发 CI,也就是在 GitLab runner 上进行测试。

在跑测试的过程中,因为 A 服务需要调用 B 和 C 服务,所以通过 API 去请求集成环境中的对应服务。如果测试完成后没有问题,则合并到主线。再通过在 master 分支打 tag 的方式来触发容器构建并推送到 Harbor 镜像仓库。最后我们会做一个线上 release。这个就是我们的大致流程了。

那么接下来我们来具体看一下变形中会遇到的问题。

服务发现

在微服务场景下的变形中遇到了很多问题,我觉得其中值得注意的是“服务发现”。比如我们现在有这样一个场景,A 服务在跑测试时需要依赖 B 服务和 C 服务,面对这个需求,在没有引入 Kubernetes 之前,我们可以通过使用一台共用机器,将服务都布置到这台机器上,并在测试代码里写死 IP 地址,让每一次测试都在这个环境内跑。但这个方法会有下面四个无法忽视的问题:
  • 服务更新延迟
  • 环境权限混乱
  • 人工操作容易出错
  • 维护成本过高


因此我们引用了 Kubernetes Service 的方案进行优化。

Kubernetes Service

7.png

Kubernetes Service 的流程大家可以大概看一下。我们先定义一个 Service, 我们这边创建的 ClusterIP 类型的,定义了暴露端口 8000,目标端口 8000,协议是 TCP,标签选择器是 app=holdon。通过这种方式,我们可以把一组相同功能的服务,绑定在同一个 Service 下。在 Kubernetes 集群内,定义好 Service 后,会提供内部的 DNS 解析,你可以通过一个固定的域名访问指定的 Service。这样当在跑测试的时候就可以通过这个域名加对应端口,调用到对应服务了。

持续交付

持续交付(英语:Continuous Delivery,缩写为 CD)。每个项⽬都要有⼀个 Dockerfile,提供了服务运⾏所需的环境,以及服务对应的软件包。当需要发版本的时候,我们会在主线上打上⼀个 tag,触发镜像构建,然后推送到 Harbor 镜像仓库。其中,这个 tag 也会对应到镜像的版本号。
8.png

上图是我们的 CD 流程大家可以参考看一下。需要提一下的是,我们引用 Harbor 的原因是因为它相对官方的 Registry 更安全。大家应该都知道,官方的 Registry 本身不带权限校验,当你公司内部使用的时候,这个问题会导致你的镜像有被其他部门的人覆盖掉的可能性,所以我们引入了 Harbor。但这里也有一个问题,使用同一个 tag 去推依然会被覆盖的情况。不过好歹做到了小组和小组之间、部门和部门之间的隔离。

持续部署

持续部署(英语:Continuous deployment,缩写为 CD),目前这块,在实践过程中,我们是只针对集成测试环境,线上更新还是走常规的流程。给项⽬增加⼀个 k8s-deploy.yaml 的⽂件,⾥⾯包含了服务相关的配置、部署⽅式、访问⽅式等等,等待镜像构建完成后,apply 该⽂件就可以了。
9.png

持续部署流程图

回顾

现在我们再回到服务变形的流程图来看一下,当我们有 A、B、C 三个服务,且 A 服务在测试时需要调用集成环境内的 B 服务与 C 服务时,可以通过 K8S 提供的内部域名进行访问。等待整块测试跑完后,我们在主线上打 tag,让 CI 去帮执行 image build 构建镜像并推送到 Harbor 仓库。
10.png

其中涉及到的准发布环境,可以通过 kubectl apply 的方式进行部署。由于线上环境更复杂,推荐大家通过自研的容器云平台来进行操作,我们就是这么处理的,通过云平台发布,功能更加全面安全,更加符合线上部署的流程。

成果展示

最后,跟大家分享下最近正在做的项目的情况。从 2019 年 12月 开始到现在,我们每天基本保持在一个较高的 commit 数上,而这其中一共进行了大约 4500 次的测试。想象下,如果没有这套自动化持续集成环境,测试需要怎么来进行,需要投入人力资源。
11.png

原文链接:https://mp.weixin.qq.com/s/wFg30dgDDHk47SozZ3qT8w

0 个评论

要回复文章请先登录注册