部署失败时,平台最容易返回的一句话是:Deployment failed。
这句话没错,但它几乎没有告诉用户下一步该做什么。失败发生在计划生成、资源准备、runtime 启动、健康检查,还是最后的 URL readback?前面的步骤已经完成了吗?现在重试会重复创建资源吗?应该改配置、重新部署,还是回滚到上一个已验证版本?
Appaloft 把部署当成一条可以被 CLI、Web Console、GitHub workflow 和 AI agent 共同调用的控制面路径。这个模型要求成功有明确证据,失败也要留下足够的事实。失败不是流程的终点,也不应该只是日志页面上的一片红色。
这篇接着从 GitHub Actions 到 Appaloft:一次部署应该留下哪些证据、安装进度不该是一条孤立的日志和一个部署 URL,应该说清楚什么会变往下讲:一次失败的部署,至少应该留下哪些恢复信息?
“失败”不是诊断
部署通常不是一个动作。它可能包含项目或环境选择、配置解析、artifact 准备、资源绑定、runtime 执行、健康检查和外部访问验证。
如果所有错误最后都收敛成一个 failed,平台就丢掉了最有用的上下文。用户看到的是同一个红色状态,但处理方式完全不同:
- 配置缺失,需要补充变量后重新部署;
- 目标 server 尚未 ready,需要等待或处理基础设施;
- image 已经启动,但 health check 没有通过,需要查看 runtime;
- deployment 已经健康,但返回的 URL 仍然无法访问,需要检查 route 或 DNS;
- 新版本不可用,但上一个版本仍然健康,可以回滚。
这些状态都可以叫“部署失败”,但它们不应该共享同一个 recovery hint。错误消息应该回答三个问题:失败发生在哪里,已经完成了什么,以及现在允许什么操作。
先保留发生过的事实
失败以后最重要的不是马上清理所有东西,而是留下可以复盘和判断的事实。
至少需要能关联到:
- source revision,例如 commit SHA 或 source snapshot;
- 本次实际使用的 artifact 或 image digest;
- 目标 project、environment、resource 和 server;
- 解析后的配置摘要,而不是 secret 值;
- 已创建、已复用和未创建的依赖资源;
- runtime、health check 和 HTTP readback 的结果;
- 失败阶段、结构化错误和发生时间;
- 上一个 verified deployment 或明确的人工恢复缺口。
这也是为什么 deployment provenance 不能只存在于 GitHub Actions 的 log 里。CI log 适合记录过程,控制面需要保存之后仍然能读懂的部署状态。几天以后,用户不应该重新翻一百行 shell 输出,才能确认线上到底运行过什么。
当然,事实不等于把所有原始响应都保存下来。provider token、环境变量值、数据库连接串和未经处理的 request body 不应该进入 deployment detail。可恢复性需要证据,也需要 redaction 边界。
Retry、redeploy 和 rollback 不是同一个按钮
失败页面经常放一个“重试”按钮。但这三个动作的语义并不相同。
Retry 适合短暂失败或可以安全重复的阶段,例如等待一个 readiness 条件、重新读取状态,或者继续一个有明确 idempotency 边界的执行。它不应该默认重新创建所有资源。
Redeploy 是用一个新的输入重新走部署流程。用户可能修改了环境变量、换了 source revision,或者选择了另一个 target。它产生的是一次新的部署尝试,不应该把原来的失败记录覆盖掉。
Rollback 则是把可变入口重新指向上一个已验证目标。它需要知道回滚到哪个 artifact、哪份配置和哪条 route 状态,也需要知道这个目标仍然可用。没有明确 target 时,平台不应该用 latest 或“上一次看起来成功的东西”猜一个答案。
简单地说:
| 动作 | 解决的问题 | 需要的证据 |
|---|---|---|
| Retry | 同一次尝试能否安全继续 | 当前阶段、幂等边界、剩余状态 |
| Redeploy | 用新输入重新发布 | 新 source、配置摘要、目标环境 |
| Rollback | 恢复上一个已验证目标 | digest、配置 snapshot、route readback |
如果某个动作不满足这些条件,界面应该明确显示“暂不可用”,而不是把危险操作伪装成一个方便的按钮。
AI Agent 更不能猜成功
AI agent 处理失败时,最危险的行为不是报错,而是把部分成功说成完整成功。
例如,image 已经上传,不代表 runtime 已经 ready;container 返回了 200,不代表自定义域名已经完成 TLS;provider API 接受了请求,也不代表最终 route readback 已经通过。
所以 agent 在部署后应该读取结构化状态、health、diagnostics 和 recovery readiness,再决定是继续、重试、请求用户补充信息,还是停止。它可以说:
artifact 已生成,runtime 已启动,但 HTTP readback 仍未通过。当前建议检查 route target;没有证据证明部署已经完成。
它不应该只因为拿到一条 URL,就说“已经上线”。一个部署 URL,应该说清楚什么会变讨论过,URL 本身也是部署契约的一部分。它是 immutable evidence、mutable alias、custom domain,还是一个暂时不可用的 route,含义并不一样。
可恢复性应该出现在产品状态里
用户不一定需要看到所有内部步骤,但他需要看到平台对下一步的判断:
- 继续等待:平台还在做不会产生新副作用的检查;
- 修改后重试:缺少配置或输入不满足约束;
- 重新部署:当前尝试已结束,需要新的 source 或 target;
- 回滚:新版本失败,但上一个 verified target 仍可用;
- 人工处理:系统没有足够证据安全地自动恢复。
这比“请查看日志”更具体,也比自动不停重试更安全。日志仍然重要,但日志应该支持一个已经组织好的状态,而不是要求用户自己从原始输出里推断状态。
部署系统真正成熟的标志,不是它从不失败。那几乎不现实。更实际的标准是:失败以后,用户还能知道发生了什么、哪些东西已经存在、什么动作不会扩大影响,以及怎样回到一个被验证过的状态。
对 Appaloft 来说,部署控制面最终要提供的不是一个永远绿色的按钮,而是一条在成功、失败和恢复之间都保持可读的路径。