LabHub
배우기 러닝패스 코스

CKAD — 쿠버네티스 애플리케이션 개발자 · 배치는 성공했는데 택배는 두 번 나갔다 · 실습

배송 중복 사건 — Job 성공과 업무 성공은 다르다

LabHub 에서 이어서 보기

목표

한 주문을 처리하는 Job이 성공했는데 배송은 두 번 만들어지는 사건을 재현하고, 재시도·멱등 키·실행 기한을 근거로 설명합니다.

왜 중요한가

completions와 parallelism이 1이어도 외부 업무가 한 번만 처리된다는 보장은 아닙니다. 실패한 프로세스가 이미 저장을 끝냈을 수 있기 때문입니다. Kubernetes가 관리하는 실행 수명과 원장이 관리하는 업무 수명을 분리해서 관측해야 재실행 여부를 판단할 수 있습니다.

개인 VM의 실제 k3s와 HTTP·SQLite 합성 원장을 사용합니다. 실제 배송·결제·개인정보는 없습니다. 원장의 safe 모드는 서버의 쓰기 트랜잭션과 업무 키 유일성으로 중복 행을 합칩니다. 이 실습은 그 계약의 적용과 검증을 다루며 분산 시스템 전체의 정확히 한 번 처리를 보장하지 않습니다. 원장 Pod를 지우면 데이터도 사라집니다.

처음 준비에 최대 6분이 허용됩니다. 모든 파일은 /root/ckad-job-effects 아래에 둡니다. 세션 종료 시 작업은 사라지므로 필요한 기록은 미리 내려받으세요. 실습은 55분 기준이며 더 필요하면 만료 전에 시간을 연장하세요.

사용하는 틀과 실행 도우미

job-template.json은 실제 worker 명령과 고정 이미지, 권한 제한이 포함된 Job JSON입니다. 단계마다 복사하여 metadata.name, spec.backoffLimit, 컨테이너 env의 CASE_ID·MODE·FAULT 값을 바꿉니다. Pod 틀의 spec.template.metadata.labels에 있는 labhub.io/case도 해당 CASE_ID와 같은 값으로 바꿉니다. WORK_ID는 order-001, POD_UID는 Downward API로 유지합니다. parallelism·completions는 1, restartPolicy는 Never를 유지합니다. 다른 보안·앱 설정도 변경하지 않습니다. 기한이 필요한 단계에만 spec.activeDeadlineSeconds를 추가합니다.

직접 kubectl create로 실행하지 말고 파일 작성 후 python3 /opt/fixtures/ckad_job_effects_lab.py run <단계>를 실행합니다. 도우미는 학생 파일을 그대로 API로 제출하고 실행 중 Pod 관측을 보존합니다. 이미 완료된 같은 단계를 다시 실행하면 새 Job을 만들지 않고 채점만 합니다. 관측 대기가 중단되면 같은 명령으로 이어가며 Job을 삭제·재생성하지 않습니다. 생성 응답 자체를 잃은 경우에는 중복 생성 대신 새 실습을 요구합니다.

단계

1. baseline.json: name=baseline, CASE_ID=baseline, MODE=unsafe, FAULT=none, backoffLimit=0. run 1로 정상 비교군을 관측합니다.
2. unsafe-retry.json: name=unsafe-retry, CASE_ID=unsafe-retry, MODE=unsafe, FAULT=after-commit, backoffLimit=1. run 2로 저장 후 실패와 대체 Pod를 관측합니다.
3. observation-2.json을 읽어 retry-analysis.json에 job_uid, failed_pod_uid, succeeded_pod_uid, request_count, shipment_count, retry_layer를 적습니다. retry_layer는 job-controller 또는 kubelet-container 중 관측에 맞는 값입니다. run 3으로 분석을 기록합니다.
4. safe-retry.json: name=safe-retry, CASE_ID=safe-retry, MODE=safe, FAULT=after-commit, backoffLimit=1. run 4로 같은 실패에서 멱등 결과를 확인합니다.
5. safe-replay.json: name=safe-replay, CASE_ID=safe-retry, MODE=safe, FAULT=none, backoffLimit=0. run 5로 새 Job에서도 같은 업무 키가 이어지는지 봅니다.
6. no-retry.json: name=no-retry, CASE_ID=no-retry, MODE=unsafe, FAULT=after-commit, backoffLimit=0. run 6으로 실패해도 배송이 남는지 봅니다.
7. deadline.json: name=deadline, CASE_ID=deadline, MODE=safe, FAULT=deadline, backoffLimit=0, activeDeadlineSeconds=20. run 7로 기한 초과와 원장을 관측합니다. 현재 Pod 목록이 비어도 observed_pods의 과거 관측을 지우지 않습니다.
8. 여섯 실행을 비교해 final-analysis.json을 씁니다. request_count·shipment_count는 원장 전체 합계, safe_replay_shipment_id는 재실행에서 유지된 배송 번호입니다. timeout_cancels_effect와 ledger_survives_ledger_pod_replacement는 각각 기한 초과가 업무를 취소하는지, 원장 Pod 교체에도 데이터가 보존되는지의 불리언입니다. idempotency_scope는 실제 중복 판정에 쓰는 필드 이름을 +로 이어 적습니다. outcomes에는 여섯 Job 이름을 키로 두고 condition(Complete/Failed), shipments(각 실행 직후 해당 업무의 배송 수)를 적습니다. run 8로 종합 분석을 기록합니다.

참고

단계 8개

  1. 정상 배송 비교군
  2. 성공했는데 두 건이다
  3. 범인은 어느 재시도인가
  4. 같은 실패, 한 건의 배송
  5. 다른 Job으로 다시 실행
  6. 재시도를 꺼도 남는 것
  7. 시간이 끝나도 남는 것
  8. 배송 사고 종합 보고서