1.5.0 显示已受理,仓库里却没有订单
目标
制作一个脚本:用 releases/<版本> 和 current 符号链接进行部署,经过健康检查和冒烟测试,失败时回滚到上一个版本并留下记录。清理保留时,要保护 current 和上一个版本。
为什么重要
健康检查问的是进程是否活着,冒烟测试问的是一笔业务能否走完。客户公司上次的部署只问了前一个问题就结束了,结果一整夜都在丢订单。 把版本拆成目录,用一个符号链接切换,回滚也就成了同一个动作。但如果切换的瞬间 current 消失了,或者旧进程替新版本亮起绿灯,或者清理脚本把要回去的版本删掉了,那么即使有这套结构也还是会出事故。 评分器每次都会用不同的版本号,构建出正常的、只在冒烟测试中失败的、一启动就崩溃的、启动很慢的构建包,来运行你的脚本,并重新测量实际响应的版本和符号链接,而不是日志措辞。
预计 60 分钟。请在默认会话结束前用“+时间”延长(最长 180 分钟)。会话结束后 /root 中的文件会消失,所以脚本要另外保存。
步骤
- 把构建包 orderapp-1.4.0.tar.gz 解压到 /root/site/releases/1.4.0,并让 /root/site/current 符号链接指向该目录。
- 制作 /root/release/switch.sh ROOT 版本。拒绝不存在的版本,并且一次性切换,不出现 current 消失的瞬间。
- 制作 /root/release/deploy.sh ROOT 构建包 PORT。让正常的构建包通过解压、切换、替换旧进程、健康检查(确认 version)、冒烟订单、写入 deploy.log 的全部流程。
- 当 deploy.sh 的冒烟测试失败时,把 current 回滚到上一个版本并重新启动该版本,记录 rolled_back、reason smoke,并以退出码 2 结束。
- 对 deploy.sh 的健康检查:一启动就崩溃的版本,以 health 为原因回滚;延迟 2.5 秒才启动的正常版本,要等待后完成部署。等待上限为 10 秒。
- 制作 /root/release/prune.sh ROOT KEEP。保留 mtime 最新的 KEEP 个,但把 current 和上一个版本从待删除列表中排除。
- 确认连续进行四次部署(最后一次冒烟测试失败)和保留 1 的清理后,记录、剩余版本和响应的版本依然正确。
- 用 deploy.sh 把客户的 1.5.0 部署到 /root/site(端口 8480),查看结果,并把事件记录到 /root/release/incident.json。
参考
- 材料:执行契约 /opt/lab/p1a-release/CONTRACT.md,客户备忘录 /opt/lab/p1a-release/README.md,构建包 /opt/lab/p1a-release/builds/
- 查看构建包内部:tar -tzf 构建包文件,tar -xzOf 构建包文件 VERSION
- 确认符号链接:readlink /root/site/current, ls -la /root/site
- 直接测试:bash /root/release/deploy.sh /tmp/try /opt/lab/p1a-release/builds/orderapp-1.4.0.tar.gz 18480; cat /tmp/try/deploy.log
- 常见错误:ln -sf 漏掉 -n,启动应用时不把输出重定向到文件(脚本不会结束),健康检查不确认版本,只按最新顺序清理。
- 为测试启动的应用,结束后按该部署根目录的 shared/app.pid 找出并终止。
手动解压第一个版本并建立 current
把构建包 1.4.0 解压到 /root/site/releases/1.4.0,并让 /root/site/current 符号链接指向那里。
用 tar 的 -C 决定解压位置。current 必须是用 ln -s 建立的链接,而不是目录拷贝。相对路径链接(releases/版本)即使把整个部署根目录搬走也不会失效。
一次性切换 current 的切换脚本
制作 /root/release/switch.sh ROOT 版本。对不存在的版本要以非 0 的代码拒绝,并且 current 的切换不能出现消失的瞬间。
rename(2) 会原子地替换已有名称。在同一目录创建一个临时链接,再把它移到 current 之上。mv 有一个选项,即使目标指向目录,也不会把它放进该目录里面。如果使用 ln,别忘了 -n。
解压、切换、启动,直到冒烟测试
制作 /root/release/deploy.sh ROOT 构建包 PORT,部署正常的构建包,并在 deploy.log 中留下一行 deployed。
版本从 tar 包内的 VERSION 读取。旧进程用 shared/app.pid 停掉,新应用用 nohup 和输出重定向在后台启动。要检查健康检查响应中的 version 是否为新版本,以及冒烟订单(SMOKE- 前缀)能否用 GET 读回。用 jq -cn 生成记录,就不会出引号方面的错误。
冒烟测试失败就回滚到上一个版本
让 /root/release/deploy.sh 在冒烟测试失败时切换回上一个版本并重新启动,记录 rolled_back(reason smoke),然后以退出码 2 结束。
切换之前要把 current 指向的版本记成 previous,才知道该回到哪里。回滚也用 switch.sh 来做。失败的 releases/版本 要留作调查用。
崩溃的版本要快,启动慢的版本要等
让 /root/release/deploy.sh 的健康检查最多等待 10 秒,但进程一旦死掉就立刻以 health 为原因回滚。
只失败一次就放弃的话,会把启动较慢的正常版本回滚掉。反过来,已经死掉的进程也没有必要再等 10 秒。kill -0 PID 不会发送信号,只确认进程是否存在。
清理时不要删掉要回去的版本
制作 /root/release/prune.sh ROOT KEEP。除 mtime 最新的 KEEP 个之外全部删除,但保留 current 和上一个版本。
回滚之后,current 不是最新的。上一个版本是 deploy.log 中 result 为 deployed 且 version 等于 current 的最后一行的 previous。ls -t 按修改时间从新到旧列出。
部署四次再清理,记录也要对得上
让 /root/release/deploy.sh 和 /root/release/prune.sh 即使连续进行四次部署(最后一次冒烟测试失败)和保留 1 的清理,记录与实际状态也能对得上。
被回滚的那次部署的 previous,是回到的那个版本。之后再清理,应当剩下最新的 1 个(失败的版本)以及 current 和上一个版本。请在临时部署根目录中亲自连续运行试一试。
部署客户的 1.5.0 并留下事件记录
用 deploy.sh 把 1.5.0 部署到 /root/site(端口 8480),并把结果用 failed_version、restored_version、reason、deploy_log_line、health_passed 写入 /root/release/incident.json。
deploy_log_line 是回滚记录在 /root/site/deploy.log 中的第几行(从 1 开始)。health_passed 写的是这个版本是否通过了健康检查——请根据 app.log 和记录的 reason 来判断。评分器会把 incident.json 与 deploy.log、current 和构建包原件核对。