LabHub
배우기 러닝패스 코스

CKAD — Kubernetes Application Developer

Duplicate Shipments: Job Success Is Not Business Success

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로 종합 분석을 기록합니다.

참고

정상 배송 비교군

/root/ckad-job-effects/baseline.json을 틀에서 작성하고 1단계 안내의 이름·업무 키·모드·실패 조건·재시도 예산으로 run 1을 실행하세요.

정상 실행에서도 Job과 원장의 관측을 따로 남깁니다.

성공했는데 두 건이다

/root/ckad-job-effects/unsafe-retry.json에 저장 후 실패와 재시도 1회를 선언하고 run 2를 실행하세요.

Never는 같은 Pod의 컨테이너 재시작 정책입니다. 대체 Pod의 UID를 보세요.

범인은 어느 재시도인가

observation-2.json에서 Job·실패 Pod·성공 Pod UID와 요청·배송 수를 읽어 retry-analysis.json에 기록하고 run 3을 실행하세요.

같은 ownerReferences와 서로 다른 Pod UID, restartCount를 함께 비교합니다.

같은 실패, 한 건의 배송

/root/ckad-job-effects/safe-retry.json에 safe 모드와 안정적인 업무 키를 선언하고 run 4를 실행하세요.

요청은 반복돼도 같은 배송 번호로 연결되는지 보세요.

다른 Job으로 다시 실행

/root/ckad-job-effects/safe-replay.json을 별도 Job 이름으로 작성하되 CASE_ID=safe-retry를 유지하고 run 5를 실행하세요.

실행 ID는 달라져야 하고 업무 ID는 유지돼야 합니다.

재시도를 꺼도 남는 것

/root/ckad-job-effects/no-retry.json에 backoffLimit=0과 저장 후 실패를 선언하고 run 6을 실행하세요.

Failed 조건이 저장 롤백을 뜻하는 것은 아닙니다.

시간이 끝나도 남는 것

/root/ckad-job-effects/deadline.json에 activeDeadlineSeconds=20과 저장 후 대기를 선언하고 run 7을 실행하세요.

실행 종료와 업무 취소를 구분하고 삭제 전 Pod 관측을 보존하세요.

배송 사고 종합 보고서

1·2·4·5·6·7단계 관측으로 final-analysis.json의 합계·재사용 배송 번호·취소 및 보존 판단·업무 키 범위·여섯 outcomes를 작성하고 run 8을 실행하세요.

전체 요청 수, 업무별 배송 수, 실행 성공 조건은 다른 지표입니다.