发一个坏版本不是问题,谁都会发。问题在于你发现它的这段时间里用户看到了什么,以及回退要花多久。有人故意往一个小型演示应用上推了一个坏版本,全程盯着请求日志,然后用一条命令回滚。

打开网易新闻 查看精彩图片

这是「部署与运维 Levelrail」系列四篇中的第二篇。整个过程中,一个每200毫秒发一次请求的循环一直在跑,用来记录用户实际会看到什么。

前置条件有三样:一个正在运行的 Levelrail 实例;装好 Docker 并已登录实例的 levelrail-cli;一个带健康检查路由的 Web 应用镜像。演示用的是十行 Node 服务,打了 v1、v2 和坏掉的 v3 三个标签。

第一步:用健康检查描述应用

就绪检查是后面一切成立的前提。只有当这个路由返回成功状态,平台才会把流量发给容器。它写在应用清单 app.yaml 里,创建应用时一并生效:

  • 镜像指向 lrdemo4/shop:v1,端口 3000
  • 就绪检查路径 /healthz,间隔 2 秒,超时 2 秒
  • 就绪超时 20 秒

创建命令是 levelrail-cli apps create --file app.yaml。仅靠命令行参数无法在创建时设置健康检查,必须走清单文件。

第二步:先发一个健康的 v2

部署 v2 后执行 apps deploys wait,输出显示部署 dep_AvQuhqaXo6oX 状态为 healthy,12 秒后以退出码 0 返回。同时在第二个终端里启动请求循环,每 200 毫秒打一次应用,把状态码打出来。

第三步:发一个坏掉的 v3

v3 镜像在 /healthz 上返回 500。部署后等待,结果是 failed,失败原因 health_check_failed,不可重试。提示信息写得很直白:容器启动了,但从未通过就绪健康检查。修复建议是确认健康检查路径能快速返回成功状态,或者如果应用只是启动慢,就调高就绪超时。日志摘录显示,就绪探测始终未就绪,最后一次尝试返回 500,期望的是 200 到 299。

命令在 22 秒后以退出码 7 结束。关键的部分在这里:整个失败尝试期间,请求循环共发出 2597 次请求,零错误,零个响应来自 v3。每个用户拿到的都是 v2 的响应。

第四步:回滚

回滚就是一条命令:levelrail-cli apps rollback shop,再跟一次 deploys wait。输出显示正在回滚到上一次成功的部署,即 v2 镜像,部署 dep_AKgDMUUOHY73 状态 healthy,整个过程 23 秒完成。

状态查询里有一行值得注意:上一个版本会保留到某个时间点,作为即时回滚目标。也就是说,你回滚掉的那个版本在短窗口内仍然可用,再改一次主意也是瞬间的事。

第五步:确认它一直健康

回滚几分钟后才出问题,比不回滚更糟。所以观察持续了 8 分钟,超过了部署后 5 分钟的观察窗口,每 30 秒采样一次实际服务的版本。14 次采样全部返回 v2,没有容器重启,应用保持健康。最终部署列表里,v3 那条标记为 failed,原因是 ReadinessFailed。

依赖它之前要知道的三件事

录制过程中发现了三个粗糙的地方:

  • 回滚命令执行约 21 秒后,v1 在 1.2 秒内响应了 6 次请求,状态码都是 200,当时失败 v3 部署的一次重试正在收尾。下一次协调把流量指回了 v2。
  • 仪表盘会把不止一行部署标记为 Current,而且回滚会为 v2 新增一行,而不是复用旧行。历史记录是对的,标签不对。
  • 健康检查要在第一次部署之前设好。没有健康检查的应用在补上之前,没有任何东西把关。

这三项被记为待解决问题,修复后会更新指南。

回顾

  • 在 app.yaml 里描述健康路由,然后创建应用。
  • 用 apps deploy 部署,脚本以 apps deploys wait 的退出码作为判断依据。
  • 没通过就绪检查的版本永远拿不到流量,用户留在能用的版本上。
  • apps rollback 大约 20 秒回到上一个好版本,另一个版本保持待命。

脚本判断部署结果时,deploys wait 在健康时退出码为 0,失败、被取消、被取代或被阻塞时为 7,等待超时为 6。