微服务部署管道入门

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

自从我上次发帖以来已经有一段时间了。与此同时,微服务炒作方面当然没有任何变化。我一直在参加许多微服务讲座,但我总是缺少关于许多不同主题的具体细节。其中之一是部署。在这篇文章中,我将尝试描述我们希望如何在 4financeIT 进行微服务部署。我将向您介绍我们将尝试 在 GitHub 上以 Jenkins Job DSL 的 形式开源的最基本的部署管道。

目标

我们的目标是:

  • 执行标准

    • 拥有部署所有微服务的独特方式——我们需要执行标准。

  • 解决微服务依赖复杂性问题

    • 从基础架构和操作的角度使部署过程可维护。

  • 使流水线快速、确定

    • 尽可能确定我们的功能运行良好。

    • 我们希望使部署管道尽可能快。

    • 如果出现问题,添加自动回滚的可能性至关重要。

执行标准

至关重要的是,如果您从微服务入手,就应该开始引入标准。运行应用程序、配置它们(外部化属性)的标准,而且您还应该在如何部署应用程序方面执行标准。在某个时间点,我们已经看到不同的应用程序以不同的方式执行共同的任务。

我们为什么要费心——我们有商业价值可以交付,而不是浪费时间执行标准,你的经理可能会说。实际上他真的错了,因为您在支持此类非标准应用程序上浪费了大量时间(因此浪费了金钱)。想象一下,新开发人员需要多少才能理解在这个特定过程中规则是如何设置的。

这同样涉及部署和部署管道。这就是为什么我们决定强制采用一种单一的微服务部署方式。

解决微服务依赖复杂性问题

如果您有两个相互通信的单体应用程序,并且没有太多开发人员在代码库上工作,您可以对两个应用程序的部署进行排队,并始终执行端到端测试。

为端到端测试部署了两个单体应用程序

在微服务的情况下,规模开始成为一个问题:

部署在不同版本中的许多微服务

让流水线变快,就会出现某些问题:

  • 我应该在一个测试环境中对微服务部署进行排队,还是应该为每个微服务提供一个环境?
    • 如果我对部署进行排队,人们将不得不等待数小时才能运行他们的测试——这可能是个问题
  • 为了解决这个问题,我可以为每个微服务创建一个环境
    • 谁来买单(想象 100 个微服务——每个都有自己的环境)?
    • 谁将支持这些环境中的每一个?
    • 我们应该在每次执行新管道时生成一个新环境然后将其包装起来,还是应该让它们启动并运行一整天?
  • 我应该在哪个版本中部署依赖的微服务——开发版还是生产版?
    • 如果我有开发版本,那么我可以针对尚未投入生产的功能测试我的应用程序。这可能导致生产异常
    • 如果我针对生产版本进行测试,那么我将永远无法在部署到生产之前的任何时候针对正在开发的功能进行测试。

使流水线快速而确定

由于我们真正相信敏捷方法和持续部署,我们希望我们的功能尽快交付到生产环境中。在使用单体应用程序时,我们遇到了以下问题:


  • 对于整体应用程序,我们有大量的单元、集成和端到端测试
  • 不同类型的测试覆盖了相同的功能多达三遍
  • 测试需要很长时间才能运行

考虑到所有这些,我们不希望我们的新部署管道出现此类问题。

简化基础设施的复杂性

由于技术问题,以及维护衍生环境的困难,我们决定尽可能简化管道。这就是为什么我们是 TDD 的狂热者并且我们知道消费者驱动契约是什么,所以我们决定不进行端到端测试。我们正在将我们的应用程序部署到一个虚拟机,其中执行的测试不会干扰同时执行的其他管道。

在存根依赖项上对已部署的微服务执行测试

这样您就可以按以下方式查看您的应用程序测试(我们称之为冒烟测试):

我们正在孤立地测试微服务

为什么要进行冒烟测试?因为我们故意要以下列方式强制执行测试金字塔:


  • 在构建期间执行了很多单元测试
  • 在构建期间执行的依赖服务存根上运行的一些集成测试
  • 在构建期间执行的依赖服务存根上运行的验收测试不多(这些可以被视为集成测试的特例)
  • 在已部署的应用程序上执行少量冒烟测试,以查看应用程序是否真正正确打包

这种测试和部署方法具有以下好处:


  • 无需部署依赖服务
  • 用于在部署的微服务上运行的测试的存根与集成测试期间使用的存根相同
  • 这些存根已经针对生成它们的应用程序进行了测试(查看 Accurest 了解更多信息,或者 我的演讲之一
  • 我们没有在已部署的应用程序上运行很多慢速测试 - 因此管道执行得更快
  • 我们不必排队部署 - 我们正在隔离测试,因此管道不会相互干扰
  • 我们不必每次都为了部署目的而生成虚拟机

然而,它带来了以下挑战:


  • 生产前没有端到端的测试——你不能完全确定某个功能是否正常工作
  • 由于这种功能正在运行的确定性降低
  • 应用程序第一次以真实的方式对话将在生产中

克服对不确定性的恐惧

我们不知道功能是否正常工作的论点促使我们在工具上投入更多的时间和精力,这些工具将为我们提供有关我们的应用程序如何在生产环境中工作的更多信息。这就是为什么我们通过 Graphite 添加了大量的技术和业务监控。此外,我们还引入了 Seyren 作为警报机制,以便在生产中出现问题时立即通知我们。

无论您花多少时间通过无休止的点击来改进测试、测试环境或 UAT——这永远不会表示您的应用程序在生产环境中将以相同的方式运行。

我们的决定与权衡有关。我们决定放弃人工测试环境中的复杂性。这种复杂性被推到了生产实例的监控上。使用微服务从来都不是一个容易的决定——总是需要付出一些代价。

解决方案的技术概述

我们将最简单的微服务部署流水线场景分为以下几个步骤。

微服务部署流水线(无 A/B 测试)

构建应用程序(提交后)

最理想的情况是,我们希望在每次合并 PR 后触发部署管道(因此进行持续部署)。

此步骤的结果是通过以下方式测试应用程序:


  • 运行单元和集成测试
  • 声明的存根规范的有效性根据应用程序本身进行测试

最后发布到 Nexus 的是应用程序的 fat-jar 及其存根。

部署到暂存

我们将新构建的应用程序部署到暂存环境。 4financeIT Stub-runner 负责下载已声明的微服务依赖项存根的当前 开发 版本。

在管道的第一个版本中,我们决定转向开发版本,因为我们希望每个应用程序在每次提交后都能上线。这意味着存根的开发版本很可能实际上是生产版本。当然,这不一定是真的——但这是我们的权衡。

在管道的未来版本中,我们希望针对存根的开发和生产版本测试应用程序。

非常重要的一点是,在这一步中,我们正在升级微服务的数据库模式。

测试应用回滚场景

我们不想回滚数据库。如果您有类似 MongoDB 的数据库,那么实际上没有架构。如果你有 Liquibase - 你可以为关系数据库提供回滚脚本。然而,它们在数据库级别引入了复杂性。

我们决定权衡取舍,将复杂性从数据库级别转移到代码。我们不是在回滚数据库,而是在回滚应用程序。这意味着开发人员需要编写代码来支持向后兼容性。

这意味着应用程序的新版本必须支持数据库的旧模式。此外,开发人员不得在后续的 liquibase 脚本中进行向后不兼容的更改。

我们正在对连接到新版本模式的应用程序的回滚版本运行旧冒烟测试。这样我们就可以确保我们很可能能够在没有更大问题的情况下回滚生产。

部署到生产

如果冒烟测试通过并且我们检查了回滚程序,我们就可以上线了。监控部分在这里发挥作用。我们需要确保我们已将所有 KPI 检查警报放在适当的位置。作为部署过程的一部分,需要对监控和警报进行审查。

正如您在图片中看到的,实时部署的第一个场景不包括 0 停机时间方法。这是我们决定采取的又一个权衡。我们现在不想解决自动数据迁移的问题。同样对于开发人员来说,编写同时支持旧模式和新模式的代码实际上是令人兴奋的。这就是为什么我们想要一步一步地做事——现在我们杀死所有生产环境中的应用程序实例,启动一个并更改模式,然后启动其余的。

回滚程序

如果我们的 KPI 监控在生产中开始变红,那么我们需要尽快回滚。由于我们已经测试了回滚过程,因此终止所有实例、下载应用程序的先前版本并针对新模式运行它在生产环境中应该不是问题。

概括

由于一切都与分布式系统有关——你可以看到微服务部署并不容易。实际上它充满了权衡和复杂性。从基础设施开始,通过测试,以数据库模式更改结束。

所提出的解决方案似乎是时间、努力、确定性和反馈之间可接受的折衷方案。