Trace missing files behind a green pipeline
한국어 원문으로 표시합니다.
목표
업무 성공과 산출물 보존·정리 성공을 구분하고, 충돌 없는 키와 최소 권한으로 검증합니다.
왜 중요한가
업무가 성공해도 최종 파일이 사라지거나 중간 파일이 계속 쌓일 수 있습니다. 이 실습은 VM 안의 실제 Argo Workflows 4.1.3과 SeaweedFS 4.46을 사용합니다. 화면의 성공 표시가 아니라 실행 UID와 실제 파일 바이트, GC의 오류를 함께 확인합니다. 기본 클러스터와 저장소는 자동 준비되고, 학생은 정책을 수정하거나 비교 시나리오를 직접 실행합니다.
단계
- argo 네임스페이스의 artifact-repositories와 Service seaweed를 읽으세요. /root/capa-artifacts/inventory.json에 namespace=argo, endpoint=seaweed:8333, writer_bucket=artifacts, anonymous_allowed=false를 기록합니다. 채점은 실제 허용 계정의 파일 읽기, 익명 요청과 다른 팀 버킷 접근 거절도 대조합니다.
- 준비된 /root/capa-artifacts/isolated.yaml의 produce 출력 두 개에서 s3.key만 고칩니다. temporary는 runs/{{workflow.uid}}/temp.txt, final은 runs/{{workflow.uid}}/final.json입니다. 나머지 파이프라인과 최종 Never는 유지하고 실험 도우미로 isolated를 실행하세요. 서로 다른 두 Workflow의 UID와 최종 파일 내용을 비교합니다.
- isolated 두 실행의 완료 후 저장소를 확인합니다. /root/capa-artifacts/retention.json에 business=Succeeded, temporary=deleted, final=retained, final_policy=Never를 기록합니다. 두 최종 파일의 생산 UID가 다르고 A의 정리 시점에 B의 입력은 살아 있었는지 실험 기록도 확인하세요.
- 준비된 missing-retain.yaml은 최종 artifact의 Never를 일부러 뺐습니다. 도우미로 missing-retain을 실행한 뒤 /root/capa-artifacts/missing.json에 business=Succeeded, final_present=false, fix=Never를 기록하세요. 이 비교 실행의 YAML을 정상 정책으로 바꾸지 않습니다. 오류 원인과 고칠 필드를 설명하는 단계입니다.
- 준비된 gc-denied.yaml은 Role 없는 GC 계정 gc-denied를 사용합니다. 도우미로 gc-denied를 실행하고 /root/capa-artifacts/gc-error.json에 business=Succeeded, condition=ArtifactGCError, cause=RBAC, force_finalizer=false를 적습니다. 실제 forbidden 로그와 남은 두 파일을 읽고, finalizer를 지우지 않습니다.
- /root/capa-artifacts/gc-binding.yaml에 RoleBinding capa-gc-repair를 작성·적용하세요. 네임스페이스는 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}} 키를 유지하고 도우미로 collision을 실행하세요. A의 입력이 B의 UID로 덮이고 A가 실패한 뒤, A의 GC가 B의 입력을 지워 B도 실패하는지 확인합니다. 두 Workflow의 Failed/Error는 이 비교의 기대 결과입니다. 단순히 파일을 지워 실패를 흉내 내지 않습니다.
- on-deletion.yaml은 OnWorkflowDeletion과 최종 Never를 함께 사용합니다. 도우미로 on-deletion을 실행하세요. 도우미는 성공 직후 파일을 관측하고, 정확한 Workflow UID를 확인해 그 Workflow만 삭제한 뒤 다시 관측합니다. 업무 완료 때는 두 파일이 있고 삭제 뒤에는 최종 파일만 남아야 합니다.
- /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 다른 시나리오도 같은 형식이며 이름은 해당 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의 상태입니다. 학생 답안이 아니라 도우미의 관측 기록이므로 직접 편집하지 않습니다. - 실제 실행 조회:
kubectl -n argo get workflow WORKFLOW_NAME -o yaml실제 파드 조회:kubectl -n argo get pods -l workflows.argoproj.io/workflow=WORKFLOW_NAME로그 조회:kubectl -n argo logs POD_NAME --all-containers=trueWORKFLOW_NAME과 POD_NAME은 앞 명령으로 찾은 실제 이름으로 바꿉니다. - 저장소 목록:
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 안의 관측용 관리 도구입니다. 업무 업로드와 GC는 별도의 artifact-writer 계정을 사용하며, 1단계는 그 계정의 버킷 제한을 검사합니다. - 관측 시간이 끝나도 실행은 계속될 수 있습니다. 같은 작업을 wait isolated로 확인하고, 살아 있는 작업을 중복 생성하지 마세요. 같은 YAML의 완료 기록은 재사용합니다. 종료된 실험을 의도적으로 새로 비교할 때만 run에 --new-attempt를 붙입니다.
- 도우미는 업로드 후 일시정지·관측·소비 재개를 수행합니다. 실행 도우미의 완료는 정답 통과가 아닙니다. 잘못된 YAML로 끝난 실행은 채점에서 거절됩니다.
- kubectl에는 KUBECONFIG=/etc/rancher/k3s/k3s.yaml을 사용합니다. argo CLI에는 --kubeconfig=/etc/rancher/k3s/k3s.yaml을 명시하면 홈 디렉터리에 의존하지 않습니다.
- 저장소 HTTP와 emptyDir는 폐기 가능한 실습용입니다. 세션이 끝나면 파일과 VM이 모두 회수됩니다. Never도 세션 종료를 넘어 보존하거나 백업하는 기능은 아닙니다.
정상 읽기와 거절할 읽기를 구분
argo 네임스페이스의 artifact-repositories와 Service seaweed를 읽으세요. /root/capa-artifacts/inventory.json에 namespace=argo, endpoint=seaweed:8333, writer_bucket=artifacts, anonymous_allowed=false를 기록합니다. 채점은 실제 허용 계정의 파일 읽기, 익명 요청과 다른 팀 버킷 접근 거절도 대조합니다.
저장소 참조의 endpoint와 bucket은 다른 필드입니다. 정상 요청이 성공하는지도 함께 확인합니다.
두 실행의 산출물 키를 분리
준비된 /root/capa-artifacts/isolated.yaml의 produce 출력 두 개에서 s3.key만 고칩니다. temporary는 runs/{{workflow.uid}}/temp.txt, final은 runs/{{workflow.uid}}/final.json입니다. 나머지 파이프라인과 최종 Never는 유지하고 실험 도우미로 isolated를 실행하세요. 서로 다른 두 Workflow의 UID와 최종 파일 내용을 비교합니다.
Kubernetes 이름이 달라도 저장소 key가 같으면 충돌합니다. UID를 로컬 파일명이 아니라 S3 key에 넣습니다.
중간 파일을 지우고 최종 결과는 보존
isolated 두 실행의 완료 후 저장소를 확인합니다. /root/capa-artifacts/retention.json에 business=Succeeded, temporary=deleted, final=retained, final_policy=Never를 기록합니다. 두 최종 파일의 생산 UID가 다르고 A의 정리 시점에 B의 입력은 살아 있었는지 실험 기록도 확인하세요.
OnWorkflowCompletion 기본 정책과 개별 final의 Never를 함께 봅니다. 관측 기록은 도우미 status에서 실행 이름을 찾은 뒤 확인할 수 있습니다.
최종 보존을 빠뜨린 성공 분석
준비된 missing-retain.yaml은 최종 artifact의 Never를 일부러 뺐습니다. 도우미로 missing-retain을 실행한 뒤 /root/capa-artifacts/missing.json에 business=Succeeded, final_present=false, fix=Never를 기록하세요. 이 비교 실행의 YAML을 정상 정책으로 바꾸지 않습니다. 오류 원인과 고칠 필드를 설명하는 단계입니다.
잘못된 설정이 업무 실패를 만들지 않을 수도 있습니다. 정상 isolated 결과와 이 실행의 최종 파일을 비교하세요.
업무 성공과 GC 권한 실패 분리
준비된 gc-denied.yaml은 Role 없는 GC 계정 gc-denied를 사용합니다. 도우미로 gc-denied를 실행하고 /root/capa-artifacts/gc-error.json에 business=Succeeded, condition=ArtifactGCError, cause=RBAC, force_finalizer=false를 적습니다. 실제 forbidden 로그와 남은 두 파일을 읽고, finalizer를 지우지 않습니다.
이 단계에서 실패해야 하는 것은 업무 컨테이너가 아니라 GC입니다. 권한을 붙이는 일은 다음 단계에서 합니다.
최소 GC 권한으로 새 실행 검증
/root/capa-artifacts/gc-binding.yaml에 RoleBinding capa-gc-repair를 작성·적용하세요. 네임스페이스는 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만 필요합니다. status 권한은 --subresource=status로 확인합니다.
공유 키가 만드는 두 번의 실패
collision.yaml의 shared/{{workflow.parameters.batch}} 키를 유지하고 도우미로 collision을 실행하세요. A의 입력이 B의 UID로 덮이고 A가 실패한 뒤, A의 GC가 B의 입력을 지워 B도 실패하는지 확인합니다. 두 Workflow의 Failed/Error는 이 비교의 기대 결과입니다. 단순히 파일을 지워 실패를 흉내 내지 않습니다.
도우미는 같은 실험 시도의 두 실행에만 같은 batch를 줍니다. status에 나온 서로 다른 UID와 로그를 비교하세요.
완료 시점과 삭제 시점 비교
on-deletion.yaml은 OnWorkflowDeletion과 최종 Never를 함께 사용합니다. 도우미로 on-deletion을 실행하세요. 도우미는 성공 직후 파일을 관측하고, 정확한 Workflow UID를 확인해 그 Workflow만 삭제한 뒤 다시 관측합니다. 업무 완료 때는 두 파일이 있고 삭제 뒤에는 최종 파일만 남아야 합니다.
Workflow 삭제와 파드 삭제는 같은 사건이 아닙니다. finalizer 강제 제거 없이 삭제가 끝난 증거를 읽습니다.
같은 성공에 숨은 다른 저장 결과 보고
/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가 왜 다른 조치를 요구하는지 실제 파일 상태와 오류 계층을 근거로 설명합니다.
도우미 status의 UID를 읽되 이름과 혼동하지 마세요. 최종 결과 유실과 임시 파일 정리 실패는 같은 장애가 아닙니다.