追踪绿色流水线背后消失的文件
目标
区分业务执行成功与 artifact 保留、清理成功,并通过无冲突的 key 和最小权限进行验证。
为什么重要
即使业务成功,最终文件也可能消失,中间文件也可能持续堆积。本实验使用 VM 中真实运行的 Argo Workflows 4.1.3 和 SeaweedFS 4.46。我们不会只看界面上的成功标记,而会同时确认执行 UID、真实文件字节以及 GC 错误。基础 cluster 与 storage 会自动准备,学生需要修改 policy 或亲自运行对照 scenario。
步骤
- 读取 argo namespace 中的 artifact-repositories 和 Service seaweed。在 /root/capa-artifacts/inventory.json 中记录 namespace=argo、endpoint=seaweed:8333、writer_bucket=artifacts、anonymous_allowed=false。评分还会对照真实授权账户能否读取文件,以及匿名请求和访问其他 team bucket 是否被拒绝。
- 在准备好的 /root/capa-artifacts/isolated.yaml 中,只修改 produce 的两个 output 的 s3.key。temporary 为 runs/{{workflow.uid}}/temp.txt,final 为 runs/{{workflow.uid}}/final.json。保留其余 pipeline 和 final 的 Never,使用实验 helper 运行 isolated。比较两个不同 Workflow 的 UID 与最终文件内容。
- isolated 的两次执行结束后检查 storage。在 /root/capa-artifacts/retention.json 中记录 business=Succeeded、temporary=deleted、final=retained、final_policy=Never。还要检查实验记录,确认两个最终文件的生产 UID 不同,并确认 A 清理时 B 的 input 仍然存在。
- 准备好的 missing-retain.yaml 故意省略 final artifact 的 Never。使用 helper 运行 missing-retain 后,在 /root/capa-artifacts/missing.json 中记录 business=Succeeded、final_present=false、fix=Never。不要把这个对照执行的 YAML 改成正确 policy;本步骤要解释错误原因和应修复的 field。
- 准备好的 gc-denied.yaml 使用没有 Role 的 GC 账户 gc-denied。使用 helper 运行 gc-denied,并在 /root/capa-artifacts/gc-error.json 中写入 business=Succeeded、condition=ArtifactGCError、cause=RBAC、force_finalizer=false。读取真实 forbidden log 和剩下的两个文件,不要删除 finalizer。
- 在 /root/capa-artifacts/gc-binding.yaml 中编写并应用 RoleBinding capa-gc-repair。namespace 为 argo,roleRef 为 apiGroup=rbac.authorization.k8s.io、kind=Role、name=artifact-gc;subjects 只包含 argo 的一个 ServiceAccount gc-denied。不要扩大现有 Role,使用 gc-repaired.yaml 验证新执行。保留原失败 Workflow 的记录与 finalizer。
- 保持 collision.yaml 中的 shared/{{workflow.parameters.batch}} key 不变,使用 helper 运行 collision。确认 A 的 input 被 B 的 UID 覆盖,A 失败后 A 的 GC 又删除 B 的 input,导致 B 也失败。两个 Workflow 的 Failed/Error 是该对照实验的预期结果。不要仅通过删除文件来伪造失败。
- on-deletion.yaml 同时使用 OnWorkflowDeletion 和 final 的 Never。使用 helper 运行 on-deletion。helper 会在成功后立即观测文件,确认准确的 Workflow UID,只删除该 Workflow,再次观测。业务完成时应存在两个文件,删除后只保留 final 文件。
- 在 /root/capa-artifacts/report.json 中,把 lost_result_uid 写为 missing-retain 的真实 UID,把 gc_failure_uid 写为 gc-denied 的真实 UID。记录 same_business_status=true、same_storage_result=false、status_is_retention_proof=false。根据真实文件状态与错误层级,说明两个 Succeeded 为什么需要不同处置。
参考
- 准备好的 YAML 位于 /root/capa-artifacts/。除第 2 步的两个 key 外,不要删除对照所需的错误配置。每张步骤卡片都列出了准确的修改和观测项。
- 运行:python3 /opt/fixtures/capa-lifecycle/runtime.py run isolated /root/capa-artifacts/isolated.yaml 其他 scenario 格式相同,名称是相应 YAML 文件名去掉 .yaml 后的部分。
- 状态:python3 /opt/fixtures/capa-lifecycle/runtime.py status isolated
- status 显示的 attempt,其前后观测原文位于
/var/lib/labhub/capa-lifecycle/attempts/<attempt>.json。before 是生产后,after 是消费、清理后,while_a_finished 是 A 结束时 B 的状态。它是 helper 的观测记录而非学生答案,请勿直接编辑。 - 查询真实执行:
kubectl -n argo get workflow WORKFLOW_NAME -o yaml查询真实 Pod:kubectl -n argo get pods -l workflows.argoproj.io/workflow=WORKFLOW_NAME查询 log:kubectl -n argo logs POD_NAME --all-containers=true请把 WORKFLOW_NAME 和 POD_NAME 替换为前述命令找到的真实名称。 - 列出 storage:
kubectl -n argo exec store-admin -- mc --config-dir /tmp/mc ls --recursive store/artifacts/读取文件:kubectl -n argo exec store-admin -- mc --config-dir /tmp/mc cat store/artifacts/runs/<UID>/final.jsonstore-admin 是该 VM 内用于观测的管理工具。业务 upload 和 GC 使用独立的 artifact-writer 账户,第 1 步会检查该账户的 bucket 限制。 - 即使观测时间结束,执行仍可能继续。使用同一任务的 wait isolated 确认,不要重复创建仍在运行的任务。相同 YAML 的完成记录会被复用。只有刻意重新执行已结束的对照实验时,才为 run 添加 --new-attempt。
- helper 会在 upload 后暂停、观测并恢复消费。helper 执行完成并不代表答案通过。由错误 YAML 完成的执行会在评分中被拒绝。
- kubectl 使用 KUBECONFIG=/etc/rancher/k3s/k3s.yaml。argo CLI 应显式指定 --kubeconfig=/etc/rancher/k3s/k3s.yaml,避免依赖 home directory。
- storage HTTP 与 emptyDir 仅供可丢弃的实验使用。session 结束后文件和 VM 都会被回收。Never 也无法跨 session 保留或备份数据。
区分允许的读取与应拒绝的读取
读取 argo namespace 中的 artifact-repositories 和 Service seaweed。在 /root/capa-artifacts/inventory.json 中记录 namespace=argo、endpoint=seaweed:8333、writer_bucket=artifacts、anonymous_allowed=false。评分还会对照真实授权账户能否读取文件,以及匿名请求和访问其他 team bucket 是否被拒绝。
storage reference 的 endpoint 与 bucket 是不同 field,同时也要确认正常请求能够成功。
隔离两次执行的 artifact key
在准备好的 /root/capa-artifacts/isolated.yaml 中,只修改 produce 的两个 output 的 s3.key。temporary 为 runs/{{workflow.uid}}/temp.txt,final 为 runs/{{workflow.uid}}/final.json。保留其余 pipeline 和 final 的 Never,使用实验 helper 运行 isolated。比较两个不同 Workflow 的 UID 与最终文件内容。
即使 Kubernetes name 不同,只要 storage key 相同就会冲突。应把 UID 放入 S3 key,而不是 local filename。
删除中间文件并保留最终结果
isolated 的两次执行结束后检查 storage。在 /root/capa-artifacts/retention.json 中记录 business=Succeeded、temporary=deleted、final=retained、final_policy=Never。还要检查实验记录,确认两个最终文件的生产 UID 不同,并确认 A 清理时 B 的 input 仍然存在。
同时查看 OnWorkflowCompletion default policy 与单个 final 的 Never。可先从 helper status 找到执行名称,再检查观测记录。
分析遗漏最终保留的成功执行
准备好的 missing-retain.yaml 故意省略 final artifact 的 Never。使用 helper 运行 missing-retain 后,在 /root/capa-artifacts/missing.json 中记录 business=Succeeded、final_present=false、fix=Never。不要把这个对照执行的 YAML 改成正确 policy;本步骤要解释错误原因和应修复的 field。
错误配置未必导致业务执行失败。请比较正常 isolated 结果与本次执行的最终文件。
分离业务成功与 GC 权限失败
准备好的 gc-denied.yaml 使用没有 Role 的 GC 账户 gc-denied。使用 helper 运行 gc-denied,并在 /root/capa-artifacts/gc-error.json 中写入 business=Succeeded、condition=ArtifactGCError、cause=RBAC、force_finalizer=false。读取真实 forbidden log 和剩下的两个文件,不要删除 finalizer。
本步骤应该失败的是 GC,而不是业务 container。授权将在下一步骤完成。
用最小 GC 权限验证新执行
在 /root/capa-artifacts/gc-binding.yaml 中编写并应用 RoleBinding capa-gc-repair。namespace 为 argo,roleRef 为 apiGroup=rbac.authorization.k8s.io、kind=Role、name=artifact-gc;subjects 只包含 argo 的一个 ServiceAccount gc-denied。不要扩大现有 Role,使用 gc-repaired.yaml 验证新执行。保留原失败 Workflow 的记录与 finalizer。
GC 只需要 task list/watch 与 task status patch。使用 --subresource=status 检查 status 权限。
观察 shared key 引发的两次失败
保持 collision.yaml 中的 shared/{{workflow.parameters.batch}} key 不变,使用 helper 运行 collision。确认 A 的 input 被 B 的 UID 覆盖,A 失败后 A 的 GC 又删除 B 的 input,导致 B 也失败。两个 Workflow 的 Failed/Error 是该对照实验的预期结果。不要仅通过删除文件来伪造失败。
helper 只为同一次实验 attempt 中的两个执行提供相同 batch。比较 status 中不同的 UID 与 log。
比较完成时刻与删除时刻
on-deletion.yaml 同时使用 OnWorkflowDeletion 和 final 的 Never。使用 helper 运行 on-deletion。helper 会在成功后立即观测文件,确认准确的 Workflow UID,只删除该 Workflow,再次观测。业务完成时应存在两个文件,删除后只保留 final 文件。
删除 Workflow 与删除 Pod 不是同一事件。请读取没有强制移除 finalizer 就完成删除的证据。
报告相同成功状态下不同的 storage 结果
在 /root/capa-artifacts/report.json 中,把 lost_result_uid 写为 missing-retain 的真实 UID,把 gc_failure_uid 写为 gc-denied 的真实 UID。记录 same_business_status=true、same_storage_result=false、status_is_retention_proof=false。根据真实文件状态与错误层级,说明两个 Succeeded 为什么需要不同处置。
从 helper status 读取 UID,不要与 name 混淆。最终结果丢失与临时文件清理失败不是同一种故障。