跳到正文

Blog

只有运行中的工作负载真的变了,部署才算成功

Appaloft Deployment Proof 如何对照部署意图与 runtime readback,并识别“任务绿色、HTTP 200、实际仍是旧版本”的假成功。

AppaloftDeployment controlRuntime verificationDockerAI

我们做 Deployment Proof,是因为原来的成功条件太容易满足了。

部署命令可以正常结束,deployment record 也可以写着 succeeded,健康检查还能返回 HTTP 200。可机器上跑的,仍然可能是旧容器。对 AI agent 来说,这种故障尤其麻烦:常见信号全是绿色,它很容易把一个并未发生的结果说成事实。

Deployment Proof 是 Appaloft 的只读查询。它把部署时记录的意图和 runtime readback 放在一起核对,最后给出 verifiedpartially-verifiedunverifiedstalefailed。只有 verified 才能被称为部署成功。

健康检查通过,版本却没换

设想一次普通的 redeploy。v1 原本就是健康的。我们把 APP_VERSION 改成 v2,重新部署,又拿到了健康响应。如果旧容器根本没有被替换,只看 health 依然会通过。

Proof 得回答更具体的问题:

  • runtime 解析到的 artifact 是不是目标 artifact;
  • workload identity 或 generation 有没有变化;
  • Resource 配置改变后,runtime configuration fingerprint 是否也变了;
  • access route 是否仍指向当前 workload;
  • recovery candidate 是否可用。

这些检查不需要返回 secret 原文。配置只留下 SHA-256 fingerprint。Proof DTO 会给出 expected effects、observed evidence、mismatch 和后续动作,但不会包含 secret value。

一个查询,五种结论

Deployment Proof 和其他 Appaloft 能力共用 operation catalog:

appaloft deployments proof <deploymentId>

HTTP 入口是 GET /api/deployments/{deploymentId}/proof。Generated SDK 和 MCP tool 暴露同一个 deployments.proof operation,Web Console 的 deployment detail 页面也渲染同一份 DTO。

这五个 verdict 刻意把“缺证据”和“已验证”分开:

  • verified:artifact、workload、配置、health、access 和 recovery 等必要证据与部署意图一致;
  • partially-verified:已有证据能对上,但某项必要 readback 暂时拿不到;
  • unverified:runtime 证据不足,无法作出判断;
  • stale:runtime 可能仍然健康,但已经不是这次 deployment 对应的 workload;
  • failed:观测结果和部署目标直接冲突,例如 health 失败,或 access route 指向了错误 workload。

“读不到 artifact digest”和“读到了另一个 digest”不是同一种故障,处理方式也不该一样。

真实 Docker smoke 跑了什么

我们用本地真实 Docker daemon 验证了这条链路,没有 mock 一个成功响应。测试 fixture 很小,health endpoint 会返回当前 APP_VERSION

整个过程是:

  1. 通过 Appaloft 部署 APP_VERSION=v1 的 fixture;
  2. 走正式 Resource 配置 operation,把变量改成 v2
  3. 通过 Appaloft redeploy,再查询 Deployment Proof;
  4. 从外部把当前容器替换成一个仍然健康、但服务 v1 且带着旧 deployment/config label 的容器;
  5. 再查同一个 deployment 的 proof。

正式 redeploy 之后,verdict 是 verified。Workload identity 变了,configuration fingerprint 也变了,health 和 access 均通过,runtime 还返回了它实际解析到的 immutable image digest。

接着我们故意制造了旧版本替换。新容器依旧返回正常 JSON:

{
  "healthStatus": "ok",
  "servedVersion": "v1",
  "proofVerdict": "stale",
  "mismatches": [
    "workload_generation_mismatch",
    "configuration_fingerprint_mismatch",
    "public_access_failed",
    "access_route_workload_mismatch"
  ]
}

HTTP 200 没有把结果变成成功。这个 workload 是健康的,但它不是正在核验的 deployment 创建出来的那个 workload。

证据从哪里来

Local Docker 和 Docker Compose adapter 会按 Resource label 找到容器,然后读取 image identity、workload label、启动时间、health、route ownership 和脱敏后的 configuration fingerprint。Docker Swarm 则读取对应 service identity 与 update generation。

Application layer 不知道 Docker command。它只向 runtime evidence port 请求标准化后的观测数据,再把这些数据与 deployment state 对照。Verdict 逻辑因此可以独立测试,其他 runtime adapter 以后也能提供同等证据,不必改 query contract。

Generic SSH 暂时没有足够 readback 能力时,会明确返回 evidence gap,对应非 verified 结果。我们宁愿把缺口写出来,也不愿从 execution record 猜一个成功。

AI 和 GitHub check 也得遵守这条规则

Appaloft skill 现在要求 agent 在观察完 deployment progress 后读取 deployments.proof。Agent 可以描述 terminal state,但只有 verdict 为 verified 时,才能说部署成功。

Cloud 的 GitHub check summary 也用同一映射。绿色 workflow 是一份输入证据,不是最终结论。Control plane 仍需核对“请求部署什么”和“runtime 正在跑什么”。

CLI、Web Console、MCP client 和自动化 check 因此得到的是同一个答案,不再各自解释一遍状态。

命令和 verdict 的完整说明见部署生命周期文档。如果想继续了解 provenance,可以读一次 GitHub Actions 部署应该留下哪些证据

后续 runtime adapter 想拿到 verified,也得交出自己的 readback 证据。暂不支持的部分会继续显示为缺口,不会藏在一个绿色状态后面。