33 的制品身份和28 的就绪状态属于交付的两个不同条件。单机 Compose 和 Kubernetes 可以消费同一镜像 digest,但不会因此拥有相同的网络、卷、策略或回退语义。应以实际 digest 为连接点,再记录各自的声明配置与请求结果。

回退最容易被误写成一句命令:Compose 将服务重新指向旧镜像,Kubernetes 对 Deployment 执行 rollout undo,于是“服务已经恢复”。但真正的前后状态至少有制品 digest、命令/探针/环境变量、流量入口、卷数据和应用协议五部分。Kubernetes 可能报告上一次 ReplicaSet 已恢复,而 PVC 里仍是坏版本改写过的数据;Compose 也可能健康检查变绿,却让客户端继续访问另一端口。本篇让同一份 probe-app 镜像同时进入两种声明体系,制造配置故障并观察回退,不假装有第二份已构建/签名的坏镜像,也不假装合成文本具备数据库迁移语义。

固定制品,不固定假设

附件中的 `compose.yaml` 只检查 `PROBE_IMAGE` 非空;复跑前还需核验它是实际可拉取的 `repo@sha256:...`。声明把仅属于实验的 volume 挂到探针 state-dir,限制发布端口为本机 loopback;Kubernetes 则需在专用集群部署同 digest 的 Deployment/Service,并明确 storage/NetworkPolicy 和探针版本。运行两侧 `/identity` 与 `/state`,可追踪到真正服务该请求的 Pod UID/容器 ID/PID,而不只是 YAML 的 image 字段。

同一 digest 也有两个生效时间

repo@sha256:... 固定的是镜像引用的不可变目标,前提是仓库里的实际 manifest 可取、平台索引指向期望架构、构建输入已留记录。Compose docker compose config 能检查变量插值和最终声明,却不会证明宿主已下载该 digest;docker compose ps 能看进程/健康状态,却不能替代 docker inspect 的 image ID 和应用真实 HTTP。Kubernetes Deployment spec 的 image 是控制面愿望,Pod status 的 imageID、containerID 与节点运行时状态才表示该次启动真正采用的制品。两边都应保存最初 digest 和运行后的 imageID,避免把同名 tag 的不同字节当成“同一制品跨平台交付”。若一个 digest 指的是多平台 index,还须记录实际节点平台及选中的 manifest,不能只用 digest 字符串相同推断两个 Linux 机器执行的字节逐层相同。

Compose Specification 把服务、端口和卷定义为应用模型,但并不定义 Kubernetes Deployment 或 PVC 的调度/绑定流程。此处单机 Compose 用自有项目 c36-own,从 loopback 的指定端口请求 /identity;其 named volume 绑定到 probe-app 的 /tmp/containers-state,要按共用 Dockerfile.single 的目录所有权验证非 root UID 65532 的写入。Kubernetes 则在自建 namespace 通过 Service 到 Pod 18080,并显式创建 PVC,依赖有实际安装的 CSI 驱动与可写文件系统组能力。两者访问入口与后端卷不是同一个对象;即使应用返回相同的合成字节,也只证明各自写到了各自的卷,绝不表示自动跨系统复制数据。

把配置、进程和请求串成基线

两侧只用自己的测试数据:以固定的合成字符串 POST /state,保存 HTTP 状态与 GET 的 JSON;/identity 保存 PID、UID 和 namespace inode。Compose 还需保存 docker compose config、项目标签、容器 ID、镜像 ID、卷名称/所有权及 published port;Kubernetes 保存 Pod UID、containerID、PVC UID/PV handle、Deployment revision、EndpointSlice 条件和 Service spec。对客户侧的真实请求,应区分直接访问 Pod 与访问 Service:只有从专用客户端通过 Service 成功得到对应身份,才说 Kubernetes 入口可用。kubectl exec 从服务容器内打 127.0.0.1 只验证应用在本机回环,不代表 Service、CNI 或 NetworkPolicy 已通过。

这条基线还应记录时间先后:声明被 API 接受、Pod Ready、EndpointSlice 反映就绪、应用 HTTP 返回及新容器开始接收请求,这几个时刻可能不同。Compose 健康检查被标记 healthy 后,宿主绑定的 loopback 端口仍可能因本机其他进程占用而不可达;up -d 返回 0 也未等待业务状态写入成功。两端测试如果有某一步失败,不应该修改下一条命令掩盖失败,而应保留原始配置与退出码;“成功交付”的定义至少需要目标入口的一次读写、真实镜像 ID 和自有存储位置三种证据相互对上。

制品引用到实际执行还有一步

发布前需要先决定固定的是单平台 manifest 还是多平台 index。PROBE_IMAGE 填写 repo@sha256:... 时,Compose 和 Kubernetes 消费的是同一个引用字符串,却不必得到同一套平台层:节点架构不同就可能选择不同 manifest。部署者应先记录仓库返回的 index/manifest 摘要、平台清单与本次成功推送的凭据和时间,再在两侧记录实际节点架构、运行时的 imageID 和应用进程中的解释器版本。若某端还在拉取或命中了旧缓存,只能说明声明已经提交,不能将预期 digest 当成观察结果。15 的平台选择解决内容分叉,运行时身份仍须按节点另查。

固定 digest 也不能代替授权。私有仓库的 Compose 主机与 Kubernetes 节点可能使用不同凭据;前者能拉取不保证后者 imagePullSecrets 可用,后者 Pod 被创建也不保证节点已拉到镜像。若部署卡在 image pull,检查拉取事件、证书、目标 digest 与平台支持,再谈就绪,不应在本机替集群偷换成可访问的 tag。反过来,仓库允许下载不是镜像签名通过,更不是该进程已经按 08 的权限约束运行:签名验证、实际容器安全上下文和应用请求是三段独立证明。把每段输入和错误码并列保存,交付失败才能定界到分发、节点启动还是业务层。

两种声明中的网络地址也有不同有效期。Compose 项目名决定本次服务、网络和卷的对象名;从 VM loopback 访问发布端口证明这台主机的入口,不代表另一主机可达。Kubernetes Service 名称由本集群 DNS 解析到入口,EndpointSlice 描述可选后端,而客户端 Pod 所在 namespace 与 NetworkPolicy 插件决定这条请求是否获准。/identity 的响应必须与本轮 Compose 容器 ID 或 Pod UID 对齐:如果只看到 200,旧容器、别的服务或先前发布的端口都可能回答同一路径。基线记录先从服务端取身份,再从目标客户端请求,并比较请求时间窗内的对象;不能用 YAML 的名字充当活体身份。

为什么这次故障只改变探针配置

要证明配置回退而不是“重新拉取镜像自动修好了服务”,先保持两侧同一个已验证 digest。Compose 在本篇附加 c36-bad.compose.yaml 覆盖健康检查 URL 为不存在的 /__c36_not_ready__;容器主进程仍可提供 /identity,但健康状态应变成 unhealthy。只有实际观察到此分歧,才能说负例走的是健康检查层,而非镜像或网络。恢复时去掉覆盖文件,以原项目/同一个 named volume 再次执行 up,并核对容器镜像 ID、健康与合成数据。Compose 没有 Kubernetes 的自动 ReplicaSet 历史;所谓“Compose rollback”是操作员明确恢复前一份 compose 配置/镜像 digest,然后用 HTTP 核查,并非运行 rollout undo。

Kubernetes 附带战略合并补丁 c36-bad-readiness.yaml,只把 Deployment 模板里 probe 容器的 readinessProbe URL 改错,触发新 revision。要记录新的 ReplicaSet、Pod UID、探针失败、EndpointSlice 与 Service 请求结果;若新 Pod 从未 Ready,rollout status 按有界 90 秒退出非零是预期的一种观测,却仍需取得真实事件才允许写“复现成功”。把 Deployment rollout undo 回到先前模板之后,等 Ready 与 Service 请求再次成功,再验证前后 imageID、PVC handle 和合成内容。实验采用 Recreate 单副本策略避免两个 RWO Pod 同时挂卷;这意味着更新会有停机窗口,不能同时宣传零停机滚动发布。

即使负例 Pod 已 Running、在本 Pod 回环可取 /state,Service 也可能因 Ready=false 无后端;该负例刚好能验证29 的后端条件,但不能证明其具体 kube-proxy 转发或策略路径。若 EndpointSlice 仍残留旧 UID,要先区分控制面传播与节点数据面延迟,不能立刻说“健康检查不起作用”。恢复后的 /state 字节必须来自同一 PVC 才能解释为数据保留;如果 Pod 调度到另一节点而 CSI 不支持,状态卡住反而提供了 30 中节点挂载能力未满足的证据,不可把失败全归给 rollback 命令。

健康检查失败不一定等于服务不可达

Compose 对容器内 /ready 的 healthcheck 和宿主对已发布端口发出的真实请求不共用判定入口:错误 URL 使 health 状态变为 unhealthy,但应用仍可能在 18080 监听并正确读写 /state。因此坏配置下需要同时抓 health 历史和从宿主来的请求。若探针报错而宿主请求仍返回 200,说明这次配置故障主要影响健康判定;不能把“unhealthy”写成进程死亡。如果宿主请求也失败,先查是否容器被替换、端口被占用或进程已退出,再决定故障是否仍仅由错误 URL 解释。执行恢复时再次检查最终生效的 Compose 配置:仅重新运行 up -d 不够,若覆盖文件仍参与配置合并,坏 healthcheck 仍会留在最终声明里。

Kubernetes 的 readinessProbe 在 kubelet 侧报告条件,再由控制面将 Pod 端点状况反映到 EndpointSlice;这条路径和 Compose 把 health 状态展示在 docker inspect 中不同。本篇只把 probe URL 改错,服务端容器内的回环 /state 可以仍然成功,客户端经 Service 的请求却可能无可用 endpoint。判定时应先抓坏 Pod 的 UID、Ready 条件与 404 事件,再抓 EndpointSlice 中目标 UID 的 ready 字段,最后从独立客户端 Pod 发起 HTTP。若 Service 请求仍成功,可能刚好遇到旧端点尚未收敛;这时记录时刻和旧、新 Pod UID,延后复测,不能删去不符合预期的首轮请求。若 NetworkPolicy 插件不执行该策略,直连也许不同,须把网络策略实验留给 29,别拿本轮状态变动反推出特定转发实现。

rollout undo --to-revision 依赖 Deployment 的历史 ReplicaSet;revisionHistoryLimit、手动改动与历史被清理都会改变可回退的目标。执行前保存旧 revision 及旧模板哈希,执行后同时查看 Deployment 当前模板、ReplicaSet、Pod UID 与镜像 ID。对于本例 Recreate,老 Pod 停止、新 Pod 起来之间存在明确的服务空窗;验证失败轮 Service 没有可用后端,不等于发布过程设计了零停机。即使回退命令返回成功,新的 Pod 仍可能因卷挂载、镜像拉取或 readiness 问题无法就绪;此时下一步是检查事件与请求,而不是循环执行 undo 直到表面数字变化。

镜像回退和数据回退是两条操作链

本篇坏输入只改探针 URL,因而同一镜像 digest 的恢复可以严格归因于配置恢复。要展示镜像回退,需额外构建并推送可复现的坏版本,把 Compose 的 PROBE_IMAGE 或 Deployment 镜像指向该新 digest,记录是否拉取成功和新 Pod 状态,再指回旧 digest。只有发布过程中的两个 digest 都实际存在、签名/来源验证策略也同步检查后,才能声称做过“坏镜像回退”;在云端无构建器/仓库时此分支保持 NOT_RUN。kubectl rollout undo 回退的是 Deployment 模板历史,如果模板中的镜像没变,绝不能在文章里称其回退了镜像层。

数据回退更要单独制定边界。probe-app 的 /state 是短文本文件,不包含数据库 schema、事务或向后兼容的升级脚本;本篇最多验证同一个卷在探针配置变坏又恢复期间是否仍能读到同样内容。如果真正的新版本修改了不可逆的数据库列、文件格式或加密方式,旧镜像再能运行也可能无法解释新数据。发布前应由业务定义“读旧写新是否安全”“坏版本已写数据怎么办”“回滚后的数据从快照恢复还是按兼容迁移修复”,并在生产发布窗口保存备份和恢复演练证据;本篇不在没有 CSI Snapshot/真实数据库的 VM 上编造这样的恢复结果。

有时操作者为了“立刻恢复请求”会删除 PVC 或 Compose volume 再创建:探针 /ready 可能重回 200,但旧数据随卷一起消失。这个动作不属于成功的数据回退,还破坏了与原故障的对照。在本篇独占命名空间里,清理脚本应等原始请求与 PVC UID/PV handle 都保存后,才询问操作者是否确认合成数据可丢弃;默认先只停服务、保留卷。对 Retain 策略的 PV 还要单独由管理员确认回收,不能把删除 namespace 当作后端卷已经清零。Compose 的 named volume 也是如此:docker compose down 不带 -v 与显式删除该自有 volume 是两种不同动作。

旧数据还能读到需要哪些约束

Compose 的新空 named volume 可能从镜像内的目录复制初始内容,目录在镜像构建阶段必须已授予 UID/GID 65532 写权限;16 的卷分类还指出已有卷和只读卷是两条不同路径。已有卷的目录所有者未必与镜像一致,bind mount 甚至直接暴露宿主现有目录;不能在发布脚本里递归 chown 一个未知来源的目录,只为让一次测试变绿。本篇仅让专用 Compose 项目使用独占新卷,真实验收要用镜像的非 root 用户写入再在重建后读回,并另以只读挂载拒绝写入做反证。若健康检查成功但 POST 失败,应先查挂载的来源、权限和文件系统只读标记,不能把旧数据为空解释为“回退清理了卷”。

Kubernetes 的 PVC 是声明,PV/CSI volumeHandle 对应后端实际介质,二者的 UID/handle 用于确认读的是哪份数据。fsGroup: 65532 表达意图,却不保证任意 CSI 插件会以同样方式处理卷所有权;实际驱动是否支持、在哪个节点挂载、容器内目录的 uid/gid 和最终写入结果都要核查。若 PVC 是新卷,客户端第一次写的合成字节是基线;若是已有卷,旧数据和属主可能改变这次实验的输入,不能与新空卷的结果混在一张表。实验默认用单副本 Recreate,避免同一 RWO 卷被两个服务 Pod 同时挂载;这降低并发冲突,却牺牲更新期间的连续可用性。还需区分 PVC 在集群里保持 Bound 与容器此刻真正可读写;单靠 API 状态不能证明数据路径成功。

本篇故意不把 Compose 的 volume 和 Kubernetes 的 PVC 共享。两个环境分别写入同一个文本值,只是提供可比较的预期,不是一次跨平台迁移。恢复时对每一侧单独确认:回退前后 state JSON 的内容是否一致、实际卷标识是否相同、访问路径是否经过目标服务,记录响应时间与出错时是否写入了部分字节。探针只保存单个短文件,既没有数据库事务也没有备份目录;因此“两个端点都读出合成文本”只支持各自的基础持久化断言,无法外推到数据库格式兼容、双写一致或故障后的数据恢复。若业务不能容忍空窗与数据升级,发布流程必须另外设计向前兼容与真实恢复演练,不能把本篇的健康检查回退当成替代方案。

回退失败也要写入验收结论

三组输出应能相互解释,而不是只截取最后的绿色状态。基线的请求、坏配置的健康/就绪和服务访问、恢复后的请求,各有独立的原始响应、退出码、UID/PID 与时间窗。若新 Pod 因 PVC 无法挂载未进入 Running,坏 readiness URL 甚至没有机会执行,归因应改写为“存储前置失败”;若 readiness 事件出现但客户端请求已被网络策略拒绝,也不能仅凭服务失败判定探针路由正确。Compose 一侧若坏健康状态出现而旧容器未换,则观察重点是同一 imageID/volume 的配置效果;若容器替换了,必须重新关联新 ID 和旧卷,不能沿用旧 PID 去证明恢复。每个未发生的阶段单独记 NOT_RUN,保存中断处而不是把后续预期结果当作已观察事实。

成功的配置回退应同时满足:生效声明恢复到基线版本、应用从声明对应的真实镜像运行、两侧各自的入口重新能处理请求、自有卷中原有合成状态仍可读。四项的证据来源不同,任何一项没取到都只能把该项标未知。失败后的清理也要遵守同样的身份约束:只停止自建服务,保留有合成数据的卷供复核;只有操作者明确确认数据可丢弃,才单独核对卷和存储类回收策略。这样 37 才能把一次请求继续追到网络和进程,38 才能研究真正的节点故障,而不是拿本篇配置切换冒充基础设施自愈。

对“恢复耗时”也需写清起点。若从提交坏配置开始计时,结果包含部署、故障检测和人工决策;若从发出 undo 开始计时,则只覆盖回退执行及就绪。两者都不能从一条最后成功的 HTTP 响应推算出来。专用环境应在连续有界的客户端请求中记录开始、结束、失败数量与首个恢复响应,同时标注坏 Pod 被移出后端和旧版本重新进入后端的时刻。若采样中断,报告观测窗口和未知的请求,而不是把缺失样本算作零失败。这个时序将交给 38 检验节点故障后的恢复,但两种故障成因和计时口径不混用。

状态矩阵必须在两侧分别填写

同名独立 Compose 声明、故意错误的健康检查覆盖文件、专用命名空间下的 PVC/Deployment/Service和 Kubernetes 就绪错误补丁分别为正常与失败入口;专项附件字节校验只证实随文文件内容,两侧按 UID/digest/请求回退的命令要求操作者从同名目录独立运行,仍可下载 规范探针包构建镜像。同篇 Compose YAML 与共用包里的原声明字节一致;Kubernetes 部分不会神奇得到 Compose 的数据或 Secret,只有手工部署了适用 CSI、存储类和专用 registry 后才能测试。
阶段 Compose 要保存 Kubernetes 要保存 不可提前下结论
原始配置 docker compose config 与 imageID/卷 ID Deployment/PVC/Pod UID、imageID 与 EndpointSlice 同 digest 就是同数据
故意故障 覆盖文件、healthy 状态与直连 HTTP revision、Ready 及 Service/直连 HTTP 探针失败就是镜像损坏
配置恢复 去除覆盖文件、状态文件内容 undo revision、PVC UID 与请求内容 rollout 完成就已恢复数据
额外坏镜像 两个真实 digest 的原始拉取与验证 两代 Pod imageID、签名/准入结果 未构建镜像也完成了镜像回退

迁移判断法是先核对两边同一 digest 的实际镜像 ID,再核对每边配置版本/流量入口、卷身份和业务请求。证据支持哪一环就只报告那一环:配置恢复、镜像恢复和数据兼容各需要独立证据。当前云端无 Docker/kind/CSI 或自有镜像仓库,Compose/Kubernetes 两端交付、故障和回退全 NOT_RUN;页面构建或 YAML 可解析只能证明材料齐备,不能宣称服务上线。

正例先部署已验证版本,记录 digest、配置和合成状态文本,再将应用或配置换成已知坏版本,在专用环境观测失败并按明确回退动作恢复旧 digest 与请求;负例是回退了容器镜像但数据 schema 已被前一版不可逆地改变,服务仍可能失败。因此回退验收必须同时看配置、卷数据兼容性和真实请求,而不是只看“rollout undo 成功”。复跑入口和数据边界要求只清理自己创建的 Compose project/namespace 与卷;本机无 Docker/kind/实际 digest,交付与回退均 NOT_RUN,Compose 静态结构不是服务上线证明。

现象 核查 不能推出
YAML 的 image 是旧 digest 实际容器镜像 digest 数据已回滚
rollout 成功 Pod Ready 和真实请求 业务成功
两端 digest 一样 配置、卷和网络路径 行为必然相同

练习

  1. 先预测容器镜像回退、但测试卷内容被坏版本修改后 /state 结果,专用环境留合成数据对照及备份恢复步骤。
  2. 预测 Compose 与 Kubernetes 同 digest 的 Pod/容器是否拥有同一端口入口,实际读取监听、路由、endpoint 与 HTTP 返回。

上一篇:35:性能对照;下一篇:37:全链路。

系列总目录:从进程隔离到运行时与编排。

参考资料