回滚成功后,为什么新 Pod 又故障了?
目标
在使用同一镜像的真实应用中,对比配置变更、发布、回滚和 Pod 替换。观察引用可变配置的回滚如何再次出现问题,并确认引用按版本划分的不可变配置进行恢复后,新 Pod 也能保持正确状态。
为什么重要
不能仅凭 rollback 命令成功和服务响应正常,就断定外部配置也已恢复。旧 Pod 正常响应时,新 Pod 仍可能读到错误配置。本实验使用真正运行 k3s、kubelet 和容器的个人 VM,不是 KWOK 或伪造的 Ready 状态。安装可能需要数分钟。实验时长为 55 分钟;若需要更多时间,请在到期前延长。会话结束时 VM 和文件都会被回收,请先下载需要保留的记录。这不是替换生产集群 Pod 的操作流程。
已准备的环境与工具
- 自己的应用:cnpa-rollback/checkout,初始有两个 Pod 和可变 ConfigMap settings。初始值为 CONFIG_REVISION=v1、HEALTHY=true。
- 必须保留:cnpa-rollback-other/sentinel。必须一直保留其他团队的 UID 和真实 HTTP。
- 应用使用固定镜像 digest、non-root、移除 capability、禁用服务账户令牌自动挂载,并使用只读根文件系统。
- 错误配置下 readiness /readyz 返回 503,而 liveness /livez 正常。不要把错误配置误认为进程死亡。
- 对于无状态、仅处理短 GET 的应用,SIGTERM 终止宽限期为 5 秒。不要把这个值套用到数据库或长请求。
- 辅助工具:在 python3 /opt/fixtures/cnpa_config_lab.py 后附加 act、capture、observe 或 grade 以及步骤编号。
act 只执行明确指定的变更。capture 最多观察 75 秒后保存 JSON,observe 查询一次。grade 不会修改学员文件或资源。已完成的历史观察不会被后续步骤的当前状态覆盖。record JSON 不是手工美化的答案,而是观察成功后保存的材料。只需按指定值编写输入 JSON。第 3、6、7 步的 release JSON 是 strategic merge patch,不是完整 Deployment,并会保留 name=app 容器的其余安全配置。
步骤
- 使用 capture 1 保存 /root/cnpa-release/baseline.json。确认 cnpa-rollback/checkout Deployment 的两个真实 Pod 都读取配置 v1 且处于就绪状态,并确认服务响应来自该 Deployment 所拥有的 Pod。
- 在 /root/cnpa-release/mutable-config.json 中编写 v1 ConfigMap settings(命名空间 cnpa-rollback)。data 为 CONFIG_REVISION="v2"、HEALTHY="false",immutable=false。用 act 2 应用,再用 capture 2 保存 env-unchanged.json。配置已改变,但最初两个 Pod 的 UID 和实际 v1 环境变量必须保持不变。
- 在 /root/cnpa-release/mutable-release.json 中编写 JSON 补丁,只把 spec.template.metadata.annotations 的 labhub.io/release 设为 mutable-v2。运行 act 3 和 capture 3,保存 rollout-incomplete.json。一个新 Pod 应为 v2、就绪检查返回 503;原有两个 Pod 应为 v1 且正常,服务仍由旧 Pod 响应。
- 使用 act 4 回滚到安装时观察到的上一修订版本,并用 capture 4 保存 apparent-rollback.json。同时确认 rollout status 成功、最初两个 Pod 再次正常,以及 settings 内容仍是错误值。
- 使用 act 5 替换自己实验中的一个正常 Pod,并用 capture 5 保存 replacement-failed.json。确认被删除的 UID 已消失,新 UID 的 Pod 读取了错误的 v2 配置。另一个旧 Pod 仍须正常响应。
- 在 /root/cnpa-release/versioned-configs.json 中用 v1 List 编写两个 ConfigMap。cnpa-rollback 命名空间中的 settings-v1 为 CONFIG_REVISION="v1"、HEALTHY="true";settings-v2 为 CONFIG_REVISION="v2"、HEALTHY="false";两者都设 immutable=true。在 versioned-release.json 中编写补丁,将模板 annotation labhub.io/release=versioned-v1,并把 name=app 容器的 envFrom.configMapRef.name 指向 settings-v1。用 act 6 应用、测试不可变修改被拒绝,再用 capture 6 保存 versioned-ready.json。
- 在 /root/cnpa-release/bad-release.json 中编写补丁,将模板 annotation labhub.io/release=versioned-v2,并把 name=app 容器的 envFrom.configMapRef.name 指向 settings-v2。运行 act 7 和 capture 7,保存 versioned-incomplete.json。原有不可变 v1 的两个 Pod 应正常,一个新的 v2 Pod 应就绪失败。
- 使用 act 8 恢复到第 6 步的实际修订版本,然后替换一个正常 Pod。用 capture 8 保存 replacement-ready.json。确认替换后的新 UID 也使用不可变 v1 配置且正常,settings-v1 的 UID 以及另一团队 cnpa-rollback-other/sentinel 的 UID 和 HTTP 均保持不变。
参考
Deployment 修订版本记录的是 Pod 模板历史,而不是外部配置内容的副本。不要猜编号,要使用观察到的修订版本。请关联服务响应、各 Pod 响应、当前 observed generation,以及 Pod→ReplicaSet→Deployment 的 UID。正在终止的 Pod 可能会短暂额外显示。若达到观察时限,请调查同一次运行的条件与日志。不要仅凭超时就判定失败或重启安装。不可变 ConfigMap 也能被删除并重建,因此要确认原 UID 和内容得到保留。不要把秘密值放入 ConfigMap。观察记录是学习证据,并不是阻止 root 用户的远程证明或防作弊机制。前置步骤只会补齐缺失的旧输入和观察,不会生成当前答案,也不会覆盖已经进行的步骤或学员未完成的文件。当前步骤评分还会查看实际状态;若已继续向后操作,历史步骤则查看保存的记录。最终步骤之后仍会继续确认恢复状态和另一团队的健康状态。若学员文件丢失,不要重新伪造已经消失的历史状态作为成功记录,应留下该次运行缺少历史证据的事实。官方文档:ConfigMap · Deployment 回滚。
关联基线声明与响应
使用 capture 1 保存 /root/cnpa-release/baseline.json。确认 cnpa-rollback/checkout Deployment 的两个真实 Pod 都读取配置 v1 且处于就绪状态,并确认服务响应来自该 Deployment 所拥有的 Pod。
不要只看一次服务响应;请关联 Pod→ReplicaSet→Deployment 的所有权关系和各 Pod 的就绪响应。
区分配置变更与现有环境变量
在 /root/cnpa-release/mutable-config.json 中编写 v1 ConfigMap settings(命名空间 cnpa-rollback)。data 为 CONFIG_REVISION="v2"、HEALTHY="false",immutable=false。用 act 2 应用,再用 capture 2 保存 env-unchanged.json。配置已改变,但最初两个 Pod 的 UID 和实际 v1 环境变量必须保持不变。
环境变量在容器启动时注入。请分别查看配置对象的当前内容和现有进程实际读取的值。
区分服务正常与新发布失败
在 /root/cnpa-release/mutable-release.json 中编写 JSON 补丁,只把 spec.template.metadata.annotations 的 labhub.io/release 设为 mutable-v2。运行 act 3 和 capture 3,保存 rollout-incomplete.json。一个新 Pod 应为 v2、就绪检查返回 503;原有两个 Pod 应为 v1 且正常,服务仍由旧 Pod 响应。
修改模板 annotation 会触发新发布。maxUnavailable=0 时,不会因未就绪的新 Pod 而先减少现有正常容量。
第一次看似成功的回滚
使用 act 4 回滚到安装时观察到的上一修订版本,并用 capture 4 保存 apparent-rollback.json。同时确认 rollout status 成功、最初两个 Pod 再次正常,以及 settings 内容仍是错误值。
回滚的是 Pod 模板。不要写死修订版本号,要使用最初观察到的值。
确认问题在替换 Pod 上重现
使用 act 5 替换自己实验中的一个正常 Pod,并用 capture 5 保存 replacement-failed.json。确认被删除的 UID 已消失,新 UID 的 Pod 读取了错误的 v2 配置。另一个旧 Pod 仍须正常响应。
对比初始 Pod 的 UID 与新创建 Pod 的 UID。即使仍有一个正常 Pod,也不代表恢复结果能够持续。
按发布单元绑定不可变配置
在 /root/cnpa-release/versioned-configs.json 中用 v1 List 编写两个 ConfigMap。cnpa-rollback 命名空间中的 settings-v1 为 CONFIG_REVISION="v1"、HEALTHY="true";settings-v2 为 CONFIG_REVISION="v2"、HEALTHY="false";两者都设 immutable=true。在 versioned-release.json 中编写补丁,将模板 annotation labhub.io/release=versioned-v1,并把 name=app 容器的 envFrom.configMapRef.name 指向 settings-v1。用 act 6 应用、测试不可变修改被拒绝,再用 capture 6 保存 versioned-ready.json。
ConfigMap 需要 apiVersion、kind、metadata、data 和 immutable。发布补丁是 strategic merge patch,不是完整的 Deployment 文档。
部署对错误版本的引用
在 /root/cnpa-release/bad-release.json 中编写补丁,将模板 annotation labhub.io/release=versioned-v2,并把 name=app 容器的 envFrom.configMapRef.name 指向 settings-v2。运行 act 7 和 capture 7,保存 versioned-incomplete.json。原有不可变 v1 的两个 Pod 应正常,一个新的 v2 Pod 应就绪失败。
不可变配置不修改内容,而是引用新名称。本步骤有意选择错误版本来做实验。
让恢复持续到下一个 Pod
使用 act 8 恢复到第 6 步的实际修订版本,然后替换一个正常 Pod。用 capture 8 保存 replacement-ready.json。确认替换后的新 UID 也使用不可变 v1 配置且正常,settings-v1 的 UID 以及另一团队 cnpa-rollback-other/sentinel 的 UID 和 HTTP 均保持不变。
恢复后也不要用同名的新配置对象替换原对象。请同时检查保留的 UID 和替换 Pod 的实际响应。