TT Lab
はじめる
学ぶ 学習パス コース

CKAD — Kubernetesアプリケーション開発者

バッチは成功したのに荷物が二度発送された

TT Labで続きを見る

一言でいうと

JobがCompleteになるということは、指定した実行の完了条件を満たしたという意味であって、外部システムの業務が正確に1回処理されたことの証明ではありません。

なぜ必要なのか

注文を1件受け取って配送の受付を作るバッチがあります。開発者はcompletionsとparallelismをどちらも1に設定し、restartPolicyもNeverにしました。実行画面には、成功したPodが1つとCompleteが表示されています。ところが、元帳には同じ注文の配送が2件あります。数字の1を3か所に書いたのに、どうしてこんなことが起きたのでしょうか。

プログラムの最後の行と外部システムの保存時点は、同じ出来事ではありません。最初のPodが配送APIにリクエストを送り、サーバーは新しい配送を保存しました。レスポンスを受け取ったあと、プログラムが終了コード0を残す前にエラーで終わります。Jobコントローラーは失敗した実行を見て、新しいPodを作ります。新しいPodは同じ注文をもう一度処理し、今度は正常に終了します。コントローラーの立場では、やるべきことを終えたことになりますが、外部の元帳には2回の保存が残ることがあります。

このラボの配送は、SQLiteに行を追加する擬似サービスです。実際の住所・個人情報・配送業者のAPIは使いません。小さくて制御できる副作用を使うからこそ、失敗の原因を読む練習に集中できます。

どう動くのか

まず、実行の主体を区別します。Jobは、必要な完了数を管理するコントローラーの対象です。Podは、作業プログラムを入れる実行単位です。コンテナ内のプロセスが終わると、終了コードが残ります。外部の業務システムは、自分のストレージにリクエストの結果を残します。どれか1つの層の成功を別の層の成功と読み替えると、原因を見逃します。

parallelismは同時に実行したいPodの数に関係し、completionsは必要な成功の数に関係します。どちらも、外部APIのトランザクションの回数は数えません。restartPolicy: Neverは、同じPod内の失敗したコンテナをkubeletが再実行しない、という選択です。Jobコントローラーが代替Podを作ることまで禁止するスイッチではありません。

観測するときは、名前だけを見ず、UIDと所有関係を確認します。2つのPodが同じJob UIDをownerReferencesで指していて、それぞれのrestartCountが0で、1つはFailed、もう1つはSucceededなら、Pod内部の再起動とJobの再試行を区別する根拠になります。失敗したPodの終了コードとログも保存しておけば、保存前の失敗なのか、保存後の失敗なのかを絞り込めます。

kubectl get jobs,pods -n ckad-job-effects
kubectl get pod -n ckad-job-effects <파드이름> -o json
kubectl logs -n ckad-job-effects <파드이름>

ただし、ログの成功を示す文1つだけで配送を数えることはしません。サーバーで、リクエストの試行の一覧と配送行の一覧を別々に照会します。リクエストの試行には、どのPodがどのビジネスキーを送ったかが残り、配送行には実際の副作用が残ります。リクエスト2回が配送2件を作ったのか、同じ配送番号が返ってきたのかを比較できなければなりません。

現場での姿

再試行は、エラーを隠すための機能ではありません。一時的な失敗に耐えるためには必要ですが、再実行しても安全な作業かどうかを判断する責任はなくなりません。メールの送信、ファイル変換結果の登録、在庫の引き当て、外部の注文受付も、同じ境界の問題に出会います。相手のシステムが保存したかどうかを確認しにくい区間が、特に重要です。

backoffLimitを0に変えれば、再試行の回数は減らせます。だからといって、すでに記録された配送が取り消されるわけではありません。最初のPodが保存後に失敗すると、Jobは失敗なのに配送は1件存在することがあります。運用担当者が失敗したJobを見て新しいJobを手動で作成すれば、同じ副作用をもう一度作ってしまう可能性も残ります。失敗の状態を見て無条件に再実行する前に、ビジネスキーで元帳を照会する理由です。

activeDeadlineSecondsも、実行に許可した時間であって、外部の保存の有効期間ではありません。保存後に待機しているプログラムが時間制限で終了しても、すでに作られた行は自動的には消えません。実行の取り消し、業務の取り消し、補償処理は、それぞれ別の設計です。ラボではこの違いを観測するだけで、任意の補償削除は行いません。

観測のタイミングも重要です。期限超過のあとでコントローラーが作業Podを削除すると、あとからget podsを実行するだけではその実行が見つからないことがあります。実験中に収集したPod UID・所有するJob・コンテナの実行情報と元帳のリクエストを結び付け、現在のJobの失敗の理由は別に確認します。削除前のRunningの観測を終了コードに置き換えて記録したり、Podが見えないからといってリクエストもなかったと判断したりしません。再採点には、収集した時点を区別した保存データが必要です。

次の教材ですること

続く教材では、再実行を止める代わりに、同じ業務を安全に繰り返すための契約を設計します。まず、実行識別子とビジネス識別子を区別してください。そのあとのラボでは、正常なJobを比較群として残し、保存の直後に失敗するJobを実行して、Jobの条件・実際のPod UID・終了コード・リクエストの回数・配送行の数を一緒に記録します。Completeなのに2件になる場合と、Failedなのに1件になる場合を、それぞれ説明できることが目標です。

公式ドキュメントと適用範囲

この教材の元帳の構造と障害の注入は、独立した学習用の例です。すべての外部システムに同じ保存モデルがあるとは仮定せず、試験問題の複製でも、CKADの全範囲の代替でもありません。