删了却还在:GitOps删除保护实验
目标
在真实的 Argo CD 和 k3s 中区分 Git 删除意图、对象级保护、审批和空目标拒绝。
为什么这很重要
OutOfSync 可能是保护机制生效的结果,Succeeded 也可能并非当前提交的结果。 不要触碰生产 LabHub、外部仓库、用户数据或 PVC。只使用个人 VM 中的 cgoa-prune-safety Namespace 和 两个专用 Application。实际删除对象是包含学习用字符串的 ConfigMap。 不要删除 Namespace、Application 或共享控制器,也不要更改全局安全设置或权限。 本实验时长为 55 分钟。如有需要,请在会话到期前延长时间并保存观测结果。结束时 VM 和文件会被回收。
已准备的环境与辅助工具
/srv/cgoa-prune-safety 是工作 Git,/srv/bare/cgoa-prune-safety.git 是 VM 内部远程仓库。 Application cgoa-prune-safety 读取 app 目录,cgoa-prune-safety-empty 读取 empty 目录。 请对照实际 SHA,而不是 main 名称。观测文件位于 /root/cgoa-prune。 题目中的 key=value 是说明写法。JSON 文件应像格式示例一样为键和字符串加引号,布尔值写成 true/false。 python3 /opt/fixtures/cgoa_prune_lab.py observe 会读取当前 Git、Application 和 ConfigMap。 同一辅助工具的 complete N 会检查输入,并在执行限定变更后保存实际观测结果。 第 2、3、4、5 步会修改 Git,第 6 步执行删除审批,第 7 步在检查空目标后修改策略,第 8 步恢复默认策略和 Git。 第 7 步的 preview-empty 会从 Git 中移除最后一个目标,先观测保护效果;允许删除则需要另写答案并运行 complete 7。 请使用 git show 直接检查辅助工具创建的变更。若存在尚未完成的 Git 变更或部分答案,工具不会覆盖,而会停止。 solve N 与查看答案相同,只会写入当前尚不存在的答案。prepare N 只准备尚未完成的前置步骤,不会创建当前答案。 grade N 只执行读取。即使重新运行已完成步骤,也不会重复删除或生成观测结果。
步骤
- 读取 git -C /srv/cgoa-prune-safety rev-parse HEAD 和 baseline.json。在 identity.json 中写入字符串 revision=实际提交 SHA、namespace=cgoa-prune-safety,然后运行 complete 1。在 observation-1.json 中确认两个 Application 的 source.path,以及五个 ConfigMap 的所有权和 UID。
- 检查 baseline.json 中的 configmaps.normal.uid。在 normal.json 中写入 remove=normal、uid=已确认的 UID 字符串,然后运行 complete 2。辅助工具只会从 Git 的 app 目录中移除 normal。通过 git show 和 observation-2.json 确认只有 normal 消失且状态为 Synced/Succeeded,同时其他 UID 保持不变。
- 在 retention.json 中写入字符串 remove=retained、option=Prune=false、expected_sync=OutOfSync,然后运行 complete 3。即使 retained 已从 Git 中消失,实际 UID 仍会保留。请在 observation-3.json 的 operationState.syncResult.resources 中找到 PruneSkipped,并解释 Succeeded 与 OutOfSync 为何会同时出现。
- 在 restore.json 中写入字符串 fix_source=git 和布尔值 same_object=true,然后运行 complete 4。Git 定义恢复后,确认 retained 仍是同一个 UID,且状态为 Synced。删除或重新创建 live 对象不属于本题要求的保留方式。
- 在 pending.json 中写入字符串 remove=confirmed、expected_operation=Running 和布尔值 approved=false,然后运行 complete 5。在 observation-5.json 中确认等待审批消息,以及 requiresPruning 的对象是否只有 confirmed。不要断定 Running 就意味着控制器故障。
- 从 observation-5.json 读取 apps.app.uid 和 configmaps.confirmed.uid。在 approval.json 中写入布尔值 approve=true,并将 app_uid 和 target_uid 写为对应 UID 字符串,然后运行 complete 6。辅助工具会再次核对当前对象和范围是否仍是此前检查过的对象和范围,然后在 Application 中记录审批时间。确认 Git 提交没有变化,但 confirmed 已删除,状态变为 Synced/Succeeded。
- 先运行 preview-empty。它会将 empty 目录的目标改为有效的空 List,但不允许删除。检查 guard-7.json 中的 snapshot.revision 与 apps.empty.status.operationState.syncResult.revision 是否不同、状态是否为 SyncError,以及 disposable UID 是否得到保留。在 empty.json 中写入布尔值 allow_empty=true、previous_success_is_current=false,字符串 revision=已检查的当前 SHA、target_uid=disposable UID,然后运行 complete 7。确认在同一个 Git 状态下仅策略发生变化,并且只删除 disposable。
- 在 decision.json 中写入布尔值 restore_git=true、allow_empty=false、same_object=false、backup_verified=false,然后运行 complete 8。将默认保护恢复为 false,并恢复 Git 定义。在 observation-8.json 中确认 disposable 使用新 UID,而 retained 和 anchor 使用基准 UID。报告时要区分声明恢复与数据备份验证。
注意与解释
retained 和 anchor 必须始终保持基准 UID 与数据。disposable 在删除并恢复后应使用新的 UID。 如果待审批目标 UID 与当前范围发生变化,请停止操作。不要忘记,审批注解的作用域是整个 Application。 空目标不是读取失败,而是有效 List 中的 items=[]。除了拒绝消息,还要同时检查当前 revision、策略和保留对象。 完成记录中的哈希可以检测文件被意外覆盖,但无法作为安全保证来阻止同一 VM root 的所有伪造。 若残留 pending 日志,请不要猜测变更是否完成并再次执行删除。请保存资料,并在新的实验中重新开始。 评分预算为 60 秒,准备前置步骤的预算为 90 秒。只在 complete 中等待收敛。 Argo CD 同步选项 · 自动同步与空目标。
读取 Git 和对象生命周期的基准
读取 git -C /srv/cgoa-prune-safety rev-parse HEAD 和 baseline.json。在 identity.json 中写入字符串 revision=实际提交 SHA、namespace=cgoa-prune-safety,然后运行 complete 1。在 observation-1.json 中确认两个 Application 的 source.path,以及五个 ConfigMap 的所有权和 UID。
请读取提交 SHA,而不是分支名称;读取对象名称时还要同时读取 UID。
确认未受保护对象确实被删除
检查 baseline.json 中的 configmaps.normal.uid。在 normal.json 中写入 remove=normal、uid=已确认的 UID 字符串,然后运行 complete 2。辅助工具只会从 Git 的 app 目录中移除 normal。通过 git show 和 observation-2.json 确认只有 normal 消失且状态为 Synced/Succeeded,同时其他 UID 保持不变。
请同时比较 Git diff 和当前 ConfigMap 列表。
区分跳过删除后的操作成功与状态差异
在 retention.json 中写入字符串 remove=retained、option=Prune=false、expected_sync=OutOfSync,然后运行 complete 3。即使 retained 已从 Git 中消失,实际 UID 仍会保留。请在 observation-3.json 的 operationState.syncResult.resources 中找到 PruneSkipped,并解释 Succeeded 与 OutOfSync 为何会同时出现。
操作是否成功与是否符合期望状态是两个不同的问题。
通过恢复 Git 保留同一个对象
在 restore.json 中写入字符串 fix_source=git 和布尔值 same_object=true,然后运行 complete 4。Git 定义恢复后,确认 retained 仍是同一个 UID,且状态为 Synced。删除或重新创建 live 对象不属于本题要求的保留方式。
即使名称相同,只要 UID 发生变化,就不算保留了同一个对象。
确认等待审批状态和删除对象
在 pending.json 中写入字符串 remove=confirmed、expected_operation=Running 和布尔值 approved=false,然后运行 complete 5。在 observation-5.json 中确认等待审批消息,以及 requiresPruning 的对象是否只有 confirmed。不要断定 Running 就意味着控制器故障。
请读取 operationState.message 和 requiresPruning 列表。
将审批限定于已检查的 UID
从 observation-5.json 读取 apps.app.uid 和 configmaps.confirmed.uid。在 approval.json 中写入布尔值 approve=true,并将 app_uid 和 target_uid 写为对应 UID 字符串,然后运行 complete 6。辅助工具会再次核对当前对象和范围是否仍是此前检查过的对象和范围,然后在 Application 中记录审批时间。确认 Git 提交没有变化,但 confirmed 已删除,状态变为 Synced/Succeeded。
审批注解附加在 Application 上,因此要检查完整的待删除范围。
观测空目标拒绝后再明确允许
先运行 preview-empty。它会将 empty 目录的目标改为有效的空 List,但不允许删除。检查 guard-7.json 中的 snapshot.revision 与 apps.empty.status.operationState.syncResult.revision 是否不同、状态是否为 SyncError,以及 disposable UID 是否得到保留。在 empty.json 中写入布尔值 allow_empty=true、previous_success_is_current=false,字符串 revision=已检查的当前 SHA、target_uid=disposable UID,然后运行 complete 7。确认在同一个 Git 状态下仅策略发生变化,并且只删除 disposable。
请确认最后一次成功操作属于哪个 Git 提交。
恢复默认保护与 Git,并报告限制
在 decision.json 中写入布尔值 restore_git=true、allow_empty=false、same_object=false、backup_verified=false,然后运行 complete 8。将默认保护恢复为 false,并恢复 Git 定义。在 observation-8.json 中确认 disposable 使用新 UID,而 retained 和 anchor 使用基准 UID。报告时要区分声明恢复与数据备份验证。
重新创建 ConfigMap 并不等于完成了卷或数据库恢复测试。