Skip to content
扫描二维码在移动端阅读本文
扫码开始移动端阅读

重生之测试发版别找我,飞书群里自己发

6267
31.335分钟

最近被催发版催得有点烦。

发版本身不复杂,麻烦的是群里隔一会儿就有人来一句:

“帮忙发一下测试。”

然后我就得停下手里的事情,确认分支,确认 commit,确认发哪个站点,确认有没有人在发,发完还要再去看一下是不是成功。中间哪一步没说清楚,过一会儿还得补一句:

“刚才发的是哪个版本?”

这类事情做一次两次没什么,天天做就很像人工流水线。

后来我懒得再当这个人工中转站了,就做了一个飞书里的发布控制台:@ 一下机器人,选范围、看 commit、确认发布,后面还能查任务和回退版本。

这篇就记一下我是怎么做的,以及中间遇到的几个问题。

先说明一下:文中的服务器地址、GitLab 地址、用户 ID、commit、任务 ID、应用凭据都已经脱敏,截图里的敏感内容也做了遮挡。


先给大家体验一下,这里是一个模拟的发布控制台

前端开发流水线5 位成员
@测试发布机器人 发测试
测试发布机器人 机器人
测试环境

测试环境发布控制台

运行正常

统一管理测试环境发布、任务状态和版本回退。

当前操作人
示例用户
发布分支
dev
最新 commit
a1b2c3d
当前任务
暂无进行中的任务

发布控制

最近任务已完成 · 全部站点 · a1b2c3d

可以直接点击卡片中的按钮。

表面上就是几个按钮,后面还连着权限、任务队列、日志和回退。后面我遇到的问题,基本也都从这几个地方冒出来。


一、群里总有人让我发版

我最开始画出来的流程其实很短:

流程画完,问题也就跟着出来了。

1. 服务器信息不能泄露

发布机器在内网里,仓库地址、FTP 地址、目标目录、账号信息都不能扔到群里。

如果直接让大家登录服务器发版,服务器信息会慢慢扩散。今天是一个人知道,过几天就变成群里所有人都知道。更麻烦的是,大家知道怎么发以后,谁都可能顺手发一下。

测试环境虽然不是生产环境,但它同样是共享环境。一次不受控的发布,也可能把别人正在验证的东西覆盖掉。

所以群里只放判断这次发布是否合理需要的信息,能直接碰到服务器和仓库的东西,都留在小黑盒里。

群内可见用来确认这次要发什么
当前环境当前分支commit提交内容发布范围任务状态
小黑盒保管只在执行发布时使用
服务器地址仓库凭据FTP 账号目标目录

这样大家能看清楚发的是什么、发到哪里、现在跑到哪了,但拿不到可以直接登录服务器的东西。

2. 不能再让我手工接单

“帮忙发一下”还有个麻烦:发完以后很容易只剩一句“应该发过了”。

如果我直接在服务器上执行命令,最后很可能只剩一句“应该发过了”。谁发的、什么时候发的、发了哪个 commit、发了哪些站点,全部靠回忆。

现在每次发布都要生成一条任务记录:

01谁发起的

任务 ID、发起人、创建时间

02发的是什么

分支、commit、提交内容

03发到哪里

发布范围、目标站点

04最后怎么样

当前阶段、完成时间、失败原因

至少以后不用靠回忆来拼这次发布发生了什么。

3. 发布不能谁都能点

群里所有人都能看到卡片,不代表所有人都应该能操作卡片。

我定的规则也不复杂:

谁 @ 机器人打开的发布卡片,谁才能操作这张卡片。

群聊白名单和用户白名单是第一层权限。卡片本身还会绑定:

服务端生成卡片会话
发生在哪里群聊
谁能操作发起人
从哪里触发原始消息
进行到哪里当前阶段
还能不能点过期时间

确认发布、取消任务、重新发布、回退这类会产生副作用的按钮,只能消费一次。即使有人把按钮参数复制出来,也不能绕过服务端校验。

4. 发布完最好能回退

既然已经记录了每次成功发布的版本,就没理由只做“往前发”,不做“往后退”。

所以每个站点成功上传后,会写入版本记录。回退时选择历史成功版本,直接恢复构建产物再上传,不需要重新拉代码和重新构建。

发错了不至于只能干等着下一次发布覆盖回来。


二、原来的群机器人只能发消息

我一开始其实已经有一个飞书群机器人了。它可以通过 Webhook 发消息,发文本、发卡片都没问题。

但它只能解决一件事:服务端往群里发东西。

我想要的流程是反过来的:

这就不是单向 Webhook 能解决的了。机器人得先收到群消息,用户点卡片以后还要再收到一次回调。

所以最后换成了飞书自建应用,用 App ID 和 App Secret 建长连接。小黑盒主动连出去,飞书不用从公网打进我的内网。

这点对内网小主机很关键:

飞书卡片交互文档可以看这里:

这里很容易混:能发消息的机器人,不等于能接收并处理卡片操作。


三、为什么把它放在小黑盒上

飞书负责让人操作,小黑盒负责真正干活,这两个地方分开比较合适。

飞书适合做入口。大家在群里就能看到发布范围、commit 和任务状态,也能留下是谁发起的。小黑盒跟测试环境在一个内网,能拉代码、构建项目、上传 FTP,也不用把服务器暴露到公网。

我最后把它拆成了几块:

发布脚本没有推倒重写,还是原来的脚本。Node.js 服务做的是把飞书里的点击转换成任务,再交给单 Worker 按顺序执行。这样大家不用登录服务器,群里也不会出现一条可以随便执行的命令。

它只提供发布流程需要的几个动作:

01发布

全部站点

江苏站点

安徽站点

02查询

任务状态

最近日志

03恢复

历史成功版本

一键回退

至少比在群里传命令,让服务器照着执行靠谱。


四、真实卡片里放了什么

前面点的是示例,真实卡片还得考虑一个问题:信息不能太少,也不能把服务器细节全摊出来。首页先放环境状态、最新 commit 和最近任务,至少不用点进去以后再猜现在是什么情况。

飞书发布控制台01 · 入口
飞书测试环境发布控制台
01 · 先看控制台。 在群里 @机器人,就会收到自己的发布控制台。

点“发布测试”以后,先选择范围:

飞书发布控制台02 · 选择范围
选择测试环境发布范围
02 · 再选发布范围。 范围和实际站点数量一起展示,尽量不让人靠猜。

这里没有只写“全部、江苏、安徽”三个模糊按钮,而是直接把站点数量写出来。因为“全部”到底影响几个站点,最好不要让人靠猜。

选择范围后,服务端会读取当前 dev 分支的最新 commit,并展示:

本次发布版本
commit
固定到唯一版本
提交内容
这次到底改了什么
commit 时间
版本是什么时候产生的
实际影响范围
发布范围
全部、江苏或安徽
目标站点
即将被覆盖的具体站点
通道状态
仓库和 FTP 是否真的可用

确认卡片大概是这样:

飞书发布控制台03 · 确认发布
核对 commit 和目标站点后确认发布
03 · 真正执行前再核对一次。 commit、提交内容、时间和目标站点都放在这里。

确认按钮点击后,卡片会立即失效,服务端先做一次消费校验,然后再创建任务。

这里真正要看的不是按钮怎么跳页面,而是服务端处理这次操作的顺序:

发布任务进入任务中心以后,可以看到最近一次任务、commit、提交内容、任务阶段和时间:

飞书发布控制台04 · 任务中心
飞书发布任务中心
04 · 发出去以后看任务。 当前状态、历史记录和失败原因都留在这里,出了问题至少知道该查哪一次。

帮助卡片也会说明权限和重复发布规则:

飞书发布控制台05 · 使用帮助
飞书发布机器人使用帮助
05 · 规则也放在入口里。 权限、确认和重复发布规则,不用再单独问一遍。

这类信息看起来有点啰嗦,但比“大家默认知道规则”靠谱。尤其是发布这种有副作用的动作,规则最好直接写在入口里。


五、卡片超时了,但任务还在跑

卡片能点以后,先遇到的是超时。飞书弹了一句:

目标回调服务超时未响应。

我当时也以为发布失败了。结果去看任务,发现任务已经创建,后台还在继续构建和上传。

原因很简单,两个动作根本不是一个耗时:

  • 飞书卡片回调需要尽快返回;
  • 一次完整发布可能要几十秒,甚至几分钟。

如果按钮回调一直等到构建和上传结束,超时基本跑不了。两个动作得拆开:

按钮回调只校验权限、创建任务,然后马上告诉飞书“已提交”。构建和上传交给后台 Worker。

我那次发 5 个站点,前后花了一分多钟。中间只盯着飞书上的红色提示,肯定会以为失败了。

后来我又把构建和上传进度写进任务状态,不能只扔一个“执行中”在那儿:

超时解决了,新的麻烦也来了:飞书已经收到“已提交”,那最后到底算成功还是失败,听谁的?


六、任务说失败,后来又变成功

这个问题很快就出现了。

有一次任务先显示失败,后面又显示成功,日志里还能看到一个错误:

text
column index out of range

这不是业务代码编译失败,而是任务数据处理过程中的参数绑定问题。简单说,就是数据库语句需要的参数和实际传入的参数没有对齐。

这个错误本身不算大,麻烦的是它会把人带偏:

  • 是不是没拉到代码?
  • 是不是 FTP 挂了?
  • 是不是构建成功但状态没写进去?
  • 是不是卡片显示错了?

卡片只是展示,不能拿它当最终结果。现在一条任务至少留三份东西:

  1. 任务表里的状态;
  2. 任务事件记录;
  3. 每个任务独立的 JSONL 执行日志。

成功必须满足:

  • 构建命令退出码为 0;
  • 每个目标站点的上传命令退出码为 0;
  • 日志出现对应站点的上传完成标记;
  • 任务状态最后才更新为 succeeded

任务创建成功,只能说明它进队列了,不能说明发布成功。

状态能说清以后,还得让任务真的跑起来。Worker 要先拿到代码。GitLab CI 里这一步有人替你做了,放到小黑盒上可没人管。


七、CI 能拉代码,不代表小黑盒也能拉

我们原来的自动化测试脚本一直在 GitLab CI 里跑得好好的,所以我一开始也想当然了:脚本能用,发布服务照着调用不就行了?

GitLab CI 里的 Job 通常由 Runner 自动完成仓库 checkout。Job 启动以后,代码已经在工作目录里了,脚本直接运行即可。

GitLab Runner代码已经准备好

自动创建工作目录

自动 checkout 当前项目

自动注入 CI 变量

脚本直接开始执行

小黑盒发布服务必须自己取得代码

独立的 PM2 进程

没有 CI_JOB_TOKEN

没有 CI_PROJECT_DIR

需要单独配置仓库权限

CI 里能跑,只能说明 Runner 有权限,不能证明小黑盒也有。

小黑盒的仓库访问必须独立配置,而且只需要读取权限。当前实际使用的是 HTTPS 凭据。SSH 部署密钥也测试过,但遇到了两个问题:

  • GitLab SSH 端拒绝了现有密钥;
  • 重新添加时又提示指纹已经被使用。

这时候不能凭经验说“理论上应该可以”,必须在小黑盒上实际执行仓库读取测试。

长期更稳定的做法,是给发布服务配置一套独立的只读仓库凭据,权限只给 read_repository,并且不把凭据写进仓库、不放进飞书卡片、不打印到日志。

仓库权限配好以后,任务才算有了跑起来的条件。但还得能分清:是这次发布失败了,还是发布服务自己挂了。

相关文档:


八、PM2 日志不是发布日志

我一开始就看 PM2 输出,里面主要是:

  • 服务启动;
  • 飞书长连接;
  • 错误;
  • 重启。

这些只能说明服务还活着,不能说明某一次发布做了什么。真正的构建、FTP 上传和站点结果,在单独的任务日志里。

这两个日志解决的问题不同:

日志负责什么
PM2 日志服务有没有活着、有没有重启、有没有启动级错误
任务日志某一次发布到底拉了什么、构建了什么、上传了什么

后来一次真实发布的任务日志里,可以看到完整过程:

任务日志已完成
  1. 拉取 dev 分支
  2. 读取当前 commit
  3. 开始构建
  4. 构建 login-web
  5. 上传 login-web
  6. 上传 ah-admin
Static deployment complete.

这才是判断发布是否完成的证据。

所以现在如果卡片和直觉冲突,排查顺序应该是:

  1. 先看任务状态
  2. 再看任务事件
  3. 再看任务 JSONL 日志
  4. 最后看 PM2 服务日志

不要反过来。

前面几个问题解决以后,才轮到另一个问题:它和原来的 GitLab CI 怎么配合?


九、GitLab CI 跑检查,飞书确认发布

GitLab CI 负责跑检查,飞书负责确认什么时候把版本发到共享测试环境。

简单说就是:

  • CI 负责自动验证;
  • 飞书负责人工确认;
  • 小黑盒负责手动 CD;
  • 任务记录负责审计和回退。

多人共用测试环境时,我不太想让每次提交都自动覆盖。代码过了检查,不代表现在就适合发。

现在就是先看 CI 结果,再到飞书确认 CD。以后真要联动,也应该先拿 CI 结果做门禁,别一提交就直接覆盖测试环境。

这样 CI 继续负责检查,测试环境也还在人的控制里。


顺便吐槽一下今天喝的瑞幸

今天点了一杯瑞幸新品,第一口真给我喝懵了,一股油漆味。

瑞幸新品

这味道简直就是路边一条~ 后边的味道是一点抹茶味,但第一口的印象实在太深,已经救不回来了。

兄弟们避雷吧,真不好喝。生椰拿铁YYDS。

顺便祝我生日快乐。大家中秋节快乐~

正在连接 GitHub评论区马上就来...