实战发布平台——原来「回滚」可以这样玩!

2,041 阅读13分钟

声明:文章为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

回滚功能对于发布平台的重要性不言而喻,好比一台车只能往前开而不能倒车,那开起来得有多蛋疼!平时回滚点得嘎嘎爽,却不知道其背后的实现原理?这次就来手把手自己也造一个!

说起回滚功能,其实笔者理解的就是:只要能把版本回退到过去,就算是回滚功能了。好比一句俗语,不管黑猫白猫,抓到老鼠就是好猫~所以回滚的实现方式是很多的,只要找到契合自己业务场景的方式去实现回滚就ok了!

快速看源码

一、回滚实现分析

还是那句,要实现一个功能,首先就需要明确需求点,然后选择一个合适自己的实现方案即可。所以在正式实战回滚功能的一开始,我们先回顾一下一个项目从开发到上线的历程:

回滚逻辑.png

如图所示,我们从开发到线上核心的经历就是提交代码仓库jenkins构建构建产物上传。也就是这三个步骤,都可以协助我们实现回滚的功能。为什么可以这么说?因为代码就是本源,而在源代码到线上的这几个过程里,我们可以理解为这些都是源代码的不同阶段的不同表现。归根结底,只要源代码在手,天下我有

接下来笔者分别讲讲这三个阶段如何协助我们实现回滚功能:

  1. git 仓库:

    直接在代码层面进行回滚,也就是对 commit 做手脚!(下面的命令别跟我说都没用过,我信你个鬼!)这让笔者想起之前团队没有发布平台这种玩意的时候,线上需要回滚就是靠操作 git 的,把有问题的 commit 给取消掉,再重新执行打包流程来实现回滚~low 是有点 low,但:“又不是不能用.png”

    • git reset。回退提交(可以直接理解为 commit 倒退)。
    • git revert。新增一个 commit 来撤销之前 commit 的所有修改(可以理解为自己去把上次的代码删了然后提交)。
  2. jenkins 协助回滚:

    回滚当然可以直接使用 jenkins 实现!如下图所示,可以看到 jenkinsjob 界面中其实是有一个长长的构建历史:

    所以我们完全可以借助 jenkins 来实现回滚,只是需要对其进行一定的配置和对构建脚本的一些改造。这一点笔者没有深入展开研究,粗略看了下网上有很多大神都有相关的 参数构建配置 的讲解来实现 jenkins 端的发布+回滚,笔者也就不在这里班门弄斧啦~感兴趣的伙伴可以自行找来看看。

  3. 构建产物 dist

    这一点就更好理解了,产物是软件的最终运行形态,只要用户访问到不同的产物其实就是不同的版本,这样是不是就已经get到了这个点呢?我们每个 commit 都可以以版本为标签生成不同的产物文件夹以存放 dist 产物。随便找个库的 cdn 用法为例: image.png 这样一来,我们可以通过以“切换”静态资源的方式,让用户访问到不同版本的静态资源的方式来实现回滚。比如说:

    • 直接切换(删除、替换)掉用户访问的静态资源。(其实就把某个版本的 index.html 替换了就行)
    • 在用户访问到真实的静态资源之前加一个中间层(类似网关吧),操控中间层实现静态资源的访问切换。

其实通过“产物切换”这一块来实现“回滚”的功能,有点将 构建产物 两个阶段进行分离的意思。笔者了解到有些团队就是这样操作的,构建步骤只负责构建出产物,并不直接将本次的构建产物部署上线;真正部署上线又是另外一个流程...好像有点走远了,我们接着回到本文接着讲吧~

笔者见识短浅,只能穷举到这里了... anyway,回滚功能只要能够契合自己的业务场景,不管使用什么方式来实现都是可以的。

通过对代码到生产的流程分析后,我们发现做回滚可以在很多阶段入手,所以我们找一个最好切入的点来实现(这里指的是最好在发布平台中实现)。这里其实可以效仿 jenkins 的操作,将回滚的功能最小化嵌入发布平台中。具体怎么搞,我们接着往下看!

二、实战回滚

上面提到效仿 jenkins ,就是想把回滚的功能在发布平台中实现。我们在 jenkins 通过参数化构建配置,可以实现让用户在可视化界面构建前选择是正常构建还是回滚,我们效仿这样的交互模式,借助服务端+数据库,把回滚功能增加到发布平台中! image.png

1. 确定方案

还是那句:只要源代码在手,天下我有,因此回滚可以理解为找到用户期望的源代码重新执行构建。也就是说:让用户选择某个 分支 、 某个 提交 后执行构建就可以实现真正意义上的“回滚”。因而笔者采用的具体的回滚实现方案是:

  1. 记录每次构建的 分支信息提交信息(核心就是记录 commithash);
  2. 前端提供操作界面选择 构建历史
  3. 通过选择构建历史,动态配置 commit hash 信息 到 jenkins 的 job 中实现回滚功能;

可以把上述的文字描述转化成下图: 构建记录保存.png

在这个方案的实现中,我们首先要做的就是保存每一次的构建记录,最重要的就是需要保存 commit hash(不同分支的每一个commit都会有一个独特hash的) 信息,因为有了它们就可以动态配置 freestyle job ,让 jenkins 帮我们构建我们指定的分支和提交的代码(最后其实还是 jenkins 帮我们实现的)。

其实平时我们执行 resetrebasecherry-pick 等操作的时候都会用到 commit hash 的,这里笔者随便找个项目的 git log 来给大伙看一下: image.png

1. 前端界面

前端需要让用户操作回滚,一定需要让用户进行回滚版本选择,所以首先需要对我们之前的前端界面进行一定的改造。现在先回顾一下之前实现过的构建详情页面,并看看我们需要改造一点什么?

image.png

如图所示,这里我们需要:

  1. 新增一个选择器供用户选择回滚版本
  2. 设置一个是否回滚的标志(控制当前构建状态)
  3. 按钮中的文案根据 是否回滚 联动变化

整体来看,回滚功能在前端实现来说比较简单,就是普通的业务逻辑,所以直接上核心代码吧:

  1. 选择回滚版本:

    操作界面中添加选择器,并将操作数据绑定 rollBackHash

    <!-- 添加选择器,选择回滚版本 -->
    <el-select 
      class="ml-[8px]" 
      placeholder="选择回滚版本" 
      v-model="rollBackHash" 
      v-if="isRollBack"
    >
      <el-option 
        v-for="item in historyList" 
        :label="item.label" 
        :value="item.value" 
      />
    </el-select>
    

    获取该配置的构建历史数据:

    historyList.value = xxx后端返回数据
    

2. 后端实现

这里后端最主要做的就两件事:保存构建信息提供查询构建信息列表接口。其中保存的构建信息核心的就是 commit hash(笔者在本次实战demo中将写死这块数据,模拟回滚的版本历史),正常的项目开发的情况下我们可以通过 gitopenapi 去获取到我们想要的数据(应有尽有),因为这个跟本文的实战主题不太相关,所以不会涉及太多 git 那块的说明。

首先先处理数据存储。如果要设计一个新表,这个表的目的就是存放一个 构建配置id 及关联 commit hash 的这么一个数据,也就是每一份构建配置都会有一份自己的构建历史记录。不过我们之前已经实现了一个 jobConfig 的配置表,每份配置都会单独存一条记录(里面是我们期望保存的字段数据),不知道大家是否还记得: image.png

既然已经有现成的了,笔者就顺手拿来用了,直接原表的基础上新增一个 history 的字段来存储构建历史(当然这个根据实际场景吧,需要建表就建表)。笔者就接着在上图的 schema 中增加 history 字段。

const configSchema = new mongoose.Schema({
  ...
  history: {
    type: Array
  }
})

增加完成后通过前端访问已经能看到一个 history 的空数组出现: image.png

由于保存构建配置不属于本文的核心内容,这里笔者直接模拟构建完成后保存构建信息这个步骤(之前有实现过配置的 update 接口,所以这里就不展开了,感兴趣的伙伴可以看这里)。笔者直接通过 postman 模拟构建完毕后请求 update 接口,然后把两次的构建信息保存到配置的 history 字段中: image.png

postman 执行成功后,我们再在前端中访问分页接口,已经可以看到刚才保存的 history 数据了(里面包含了两个提交的 commit hash): image.png

接下来,我们一鼓作气把之前的前端静态部分接入一下接口的 history 字段。由于代码过于简单,笔者就不放出来了,我们直接看看接入后的页面效果吧: image.png

可以看到在构建详情的页面中,我们已经在回滚列表选择器中加入了接口的 history 数据,到这里我们的回滚前准备工作就已经完成了,用户这个时候已经可以在前端界面中,通过选择不同的回滚版本来执行回滚的构建了。

接下来就是实现回滚的最核心环节——jenkins 动态配置 job,我们接着往下看!

3. 借助 jenkins 实现回滚

前文一直铺垫了很久的 commitHash,大家可能到现在也不知道是干嘛的,现在笔者告诉大家:只要我们把这个 commitHash 丢给 jenkins,其就会帮我们在 git 仓库中拉取 对应分支对应commit 的代码下来,然后执行本次构建!这不就刚刚好可以帮助我们实现回滚了吗!

在介绍方案实战之前,我们先来模拟一个案例,案例中笔者就告诉你怎么通过 jenkins 实现回滚的!比如我们现在有个项目有:提交一提交二 两个 commit ,而其中提交二是有问题的提交image.png

然后假装我们把其发上线了!如下图所示,jenkins 构建了 feat: 提交二 的代码(可以发现此时的构建ID是:#102): image.png

这下线上的应用立马就出问题了,产品经理和测试正拿着刀子向你飞奔过来。此时你不慌不忙的捏着 提交一commit hash 粘贴在 jenkins 构建配置中如下: image.png

然后保存好配置后立马重新点击了一次 jenkins 的构建。不一会儿,线上应用就恢复了,产品经理和测试相视一笑,放下了手中的刀...这时候我们再看看 jenkins 的日志输出: image.png

到这里!你是不是已经 get 到了要点了!没错,就是你想的那样。回想一下专栏文章: node + Jenkins 实战自动化部署,笔者讲述了如何挖空 jenkins 的配置 XML 实现动态配置 job,这里我们也是用同样的套路,把用户在前端提交上来的 commit hash 动态替换到对应的 XML 的位置上(其原理就跟我们动态配置构建命令一致):

<branches>
  <hudson.plugins.git.BranchSpec>
    <!-- 就是将 commit hash 替换到这个位置即可 -->
    <name>${xxx}</name>
  </hudson.plugins.git.BranchSpec>
</branches>

配置 XML 之后,调用 jenkins.config 去更新 jenkinsjob 然后再在 node 端发起构建,然后紧接着的就是普通的打包构建、部署的流程了,这些都在之前的文章有介绍过这里就不再赘述了,感兴趣的可以关注笔者的专栏去了解其他篇章哈。

总结一下:在之前的构建中,XML 中的 <name>master</name> 放的是 master,这个时候 jenkins 就会去拉 master分支 最新的一个 commit 来执行本次构建,而在本文中,我们将其替换成了一个 commit hashjenkins 就会去找到这个 commit hash 对应的分支和提交再执行拉取代码、构建。所以本文实现回滚的核心点就是:保存 commit hash通过commit hash 配置 job,剩下的事情就交给 jenkins 吧!

4. 完善后端代码

实战的最后一步,我们还需要完善一下我们的后端代码来完成整个前端发起的回滚构建功能。因为前面提到的后端工作只是准备阶段的,并不完善~

先回顾一下项目构建的触发流程吧,经过上一篇的 webSocket实战,我们是在 socket connent 之后监听前端 emit'build:start' 事件来触发项目构建,与此同时会初始化一个构建实例存在全局保证当前配置的构建状态同步。

回顾构建核心代码:

async build (socket) {
  // 改变构建状态
  this.isBuilding = true
  const jobName = 'test-config-job'
  // 获取当前的项目配置
  const config = await jobConfig.findJobById(this.id)
  // 重新配置 jenkins job,我们本次就要在这里进行一定改造
  await jenkins.configJob(jobName, config)
  // 触发 jenkins 构建
  const { buildNumber, logStream } = await jenkins.build(jobName)
  ...
}

由于目前新增了回滚功能,所以我们需要多接收一个前端传过来的 rollBackHash,并判断如果有 rollBackHash 的时候就用其去覆盖 XML 中的配置。so,我们仅需要在 socket 中接收参数,并在传递给 build 方法即可:

socket.on('build:start', async function () {
  // 传入 rollBackHash
  await builder.build(socket, rollBackHash)
})

接下来的代码实现就是把 rollBackHash 传递到配置 XML 的函数中,按照上文所说的在对应的位置进行 hash 替换 再触发构建就行了。然后呢?没有然后了哈哈哈。还是那句:剩下的事情就交给 jenkins 啦,轻松!

写在最后

站在 jenkins 的肩膀上,回滚其实 so easy 有没有?其实笔者在一直没接触这个领域之前,对 构建、部署、回滚 啊这种字眼总是觉得很陌生,会有种莫名的畏惧感。但是当自己真的去触摸这些知识盲区并自己将其实现后,才会开始有点底气,不再发自内心的抗拒这些新知识。最后的最后后笔者还想说一句:功能的实现方式千千万,找到适合自己的就好~共勉!