はじめに
Deployment はサービスを継続的に稼働させます。一方、バッチ処理の成功条件は異なります。処理を完了まで実行し、結果を記録して停止することが求められます。Kubernetes では、一度限りの処理を Job、繰り返し実行するスケジュール処理を CronJob として扱います。
この実験では、Job の実行、Pod とログの確認、制御された並列実行の追加、CronJob のスケジューリングを行い、定期処理を運用しやすくする一時停止機能と履歴保持数について学びます。
この実験では、成功条件を比較しながら理解していきます。Deployment はレプリカを利用可能な状態に保つことで成功します。Job は、必要な数の Pod が終了コード 0 で完了すると成功します。CronJob は、意図した時刻に Job を作成すると成功です。この 3 つのレイヤーを分けて考えると、YAML の内容が格段に読みやすくなります。
一度限りの Job を実行する
このステップでは、Pod がレポートを出力して正常終了する Job を作成します。ヒアドキュメントを使ってマニフェストを作成します。引用符付きの EOF により、YAML 内の文字がシェルによって展開されるのを防げます。
YAML を入力する前に、全体の構造を確認しましょう。kind: Job は使用するコントローラーを指定し、spec.template は作成される Pod を定義します。その Pod には、短時間で終了するコンテナが 1 つ含まれています。Deployment のテンプレートとは異なり、Job の Pod では処理の完了後に停止することが前提となるため、restartPolicy: Never または OnFailure を指定する必要があります。
cd /home/labex/project/batch-workloads
cat <<'EOF' > report-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: report-job
namespace: practice-batch
spec:
backoffLimit: 2
template:
metadata:
labels:
app: report-job
spec:
restartPolicy: Never
containers:
- name: reporter
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo report-complete"]
EOF
まず、ファイルをローカルで検証するよう Kubernetes に指示します。--dry-run=client はオブジェクトの構造だけを確認し、実際には作成しません。新しいマニフェストを作成するときに便利な習慣です。
kubectl apply --dry-run=client -f report-job.yaml
結果が job.batch/report-job created (dry run) であれば、ファイルの構造は正しい状態です。次に、実際に適用し、必要な時間を推測するのではなく、Job の Complete 条件を待ちます。
kubectl apply -f report-job.yaml
kubectl wait --for=condition=Complete job/report-job -n practice-batch --timeout=60s
restartPolicy: Never を指定すると、別の Pod が必要かどうかを Job コントローラーが判断します。backoffLimit: 2 は、失敗時の再試行回数を制限します。完了した Pod の出力をコントローラー経由で確認します。
kubectl logs job/report-job -n practice-batch
report-complete と表示されるはずです。job/report-job を指定すると、kubectl が Job に対応する Pod を自動的に探してくれるため、生成された Pod 名をコピーする必要はありません。
この 3 つのコマンドにより、短く分かりやすいライフサイクルを確認できます。
job.batch/report-job created
job.batch/report-job condition met
report-complete
condition met は、Job コントローラーが完了を記録したことを示します。その次のログ行がアプリケーションの出力です。起動直後のローカルクラスターでは、ネットワークの準備に時間がかかり、このような小さな Job でも完了まで数秒かかる場合があります。条件を待つ方法なら、待機時間を推測せずに安定した処理を実現できます。
Job の所有関係と完了状態を確認する
このステップでは、完了した Job と継続的に実行される Deployment を比較します。完了した Job は Pod を保持するため、ログや終了状態を後から確認できます。
まず、通常の一覧表示を確認します。Job には 1 回の成功した完了が表示され、Pod には、表示している列によって Completed または Succeeded のフェーズが表示されます。
kubectl get jobs,pods -n practice-batch
kubectl get pods -n practice-batch -l job-name=report-job -o custom-columns='POD:.metadata.name,PHASE:.status.phase,OWNER:.metadata.ownerReferences[0].name,EXIT_CODE:.status.containerStatuses[0].state.terminated.exitCode'
kubectl describe job report-job -n practice-batch
Pod のフェーズは Succeeded、所有者は report-job、終了コードは 0 になるはずです。Job は必要な数の Pod が成功すると完了します。成功したコンテナを、稼働し続けさせるためだけに再起動することはありません。
カスタム列のクエリを使うと、階層の深い 4 つのフィールドを小さな確認用テーブルにまとめられます。「この Pod の所有者は誰で、プロセスは成功したのか」という問いに対して、Pod 全体の YAML を読むよりも効率的です。
検証時の結果は次のようになりました。
POD PHASE OWNER EXIT_CODE
report-job-cd54c Succeeded report-job 0
生成された接尾辞は環境によって異なります。行を横に読み取ると、OWNER=report-job が Pod とコントローラーの関係を示し、PHASE=Succeeded と EXIT_CODE=0 によってプロセスが正常終了したことを確認できます。
複数の完了処理を並列実行する
このステップでは、4 つの Pod が正常に完了することを要求し、そのうち最大 2 つを同時に実行できる Job を作成します。
マニフェストを作成する前に、次の 2 つの独立した数値を理解しておきましょう。
completions: 4は、合計 4 単位の処理を正常に完了する必要があることを意味します。parallelism: 2は、そのうち最大 2 単位までを同時に実行できることを意味します。
completionMode: Indexed を指定すると、処理単位に 0 から 3 までのインデックスが割り当てられます。各 Pod は JOB_COMPLETION_INDEX 環境変数を通じて自身のインデックスを受け取ります。これにより、実際のバッチプログラムでは、すべての Pod が同じデータを処理するのではなく、データセットを分割して処理できます。
cat <<'EOF' > indexed-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: indexed-job
namespace: practice-batch
spec:
completionMode: Indexed
completions: 4
parallelism: 2
backoffLimitPerIndex: 1
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo worker-index=${JOB_COMPLETION_INDEX}; sleep 2"]
EOF
マニフェストを適用し、4 つすべてのインデックスが完了するまで待ちます。
最後のコマンドでは -L を使い、batch.kubernetes.io/job-completion-index ラベルの値を追加の列として表示します。これにより、各 Pod に割り当てられた処理インデックスを、完全な YAML を開かずに確認できます。
kubectl apply -f indexed-job.yaml
kubectl wait --for=condition=Complete job/indexed-job -n practice-batch --timeout=90s
kubectl get pods -n practice-batch -l job-name=indexed-job -L batch.kubernetes.io/job-completion-index
completions は正常完了が必要な処理の総数で、parallelism は同時に実行できる Pod の最大数です。インデックス付きの完了方式では、JOB_COMPLETION_INDEX を通じて各 Pod に安定したインデックスが割り当てられます。
最終的な一覧には、異なるインデックスラベルを持つ 4 つの完了済み Pod が表示されます。処理は並列に進み、完了する順序は一定ではないため、数値順に表示されるとは限りません。
例:
NAME STATUS JOB-COMPLETION-INDEX
indexed-job-0-gw5wq Completed 0
indexed-job-1-vw4qk Completed 1
indexed-job-2-mpvnr Completed 2
indexed-job-3-8z5j8 Completed 3
4 行表示されたからといって、4 つの Pod が同時に実行されたという意味ではありません。4 つのインデックスは処理の総数を示し、parallelism: 2 によって Kubernetes が一度に実行できる数が 2 つに制限されていました。
CronJob をスケジュールする
このステップでは、1 分ごとに Job を開始する CronJob を作成します。Cron の構文は、分、時、日、月、曜日の 5 つのフィールドで構成されます。
*/1 * * * * は「毎分」を意味します。短時間の実験で動作を確認できるよう、意図的に頻繁な設定にしています。実運用では、通常これほど高頻度には設定しません。このマニフェストには入れ子になった jobTemplate も含まれています。CronJob が Job を作成し、その Job が Pod を作成するためです。
cat <<'EOF' > heartbeat-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: heartbeat
namespace: practice-batch
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: heartbeat
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "date -u; echo heartbeat-complete"]
EOF
スケジュールを適用し、概要を確認します。
kubectl apply -f heartbeat-cronjob.yaml
kubectl get cronjob heartbeat -n practice-batch
concurrencyPolicy: Forbid により、前回の処理がまだ終わっていない場合、新しい実行が重複して開始されるのを防げます。次の実行時刻まで待たずに確認できるよう、CronJob のテンプレートから Job を 1 つ手動で直ちに作成します。
kubectl create job --from=cronjob/heartbeat heartbeat-manual -n practice-batch
kubectl wait --for=condition=Complete job/heartbeat-manual -n practice-batch --timeout=60s
kubectl logs job/heartbeat-manual -n practice-batch
ログには UTC のタイムスタンプと、その後に heartbeat-complete が表示されます。手動で作成した Job は同じテンプレートを使用しますが、CronJob の今後のスケジュールには影響しません。
一般的な出力例は次のとおりです。
job.batch/heartbeat-manual condition met
Thu Aug 13 07:48:29 UTC 2026
heartbeat-complete
タイムスタンプは環境によって異なります。コンテナでは date -u を使用しているため、UTC が表示されます。これにより、クラスターのスケジュール時刻と端末のローカルタイムゾーンを混同せずに済みます。
スケジュール処理を一時停止・再開する
このステップでは、定義を削除せずに CronJob を一時停止します。一時停止は、メンテナンス中やインシデント対応時に役立ちます。
kubectl patch は、JSON ペイロードで指定したフィールドだけを変更します。ここでは spec.suspend を true に設定します。CronJob オブジェクト、そのスケジュール、すでに存在する Job はそのまま維持されます。
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":true}}'
kubectl get cronjob heartbeat -n practice-batch -o custom-columns='NAME:.metadata.name,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,LAST_SCHEDULE:.status.lastScheduleTime'
既存の Job は処理を継続します。一時停止によって防止されるのは、新しいスケジュール済み Job の作成だけです。確認が終わったら再開します。
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":false}}'
kubectl get jobs -n practice-batch --sort-by=.metadata.creationTimestamp
これで、次の 3 つのレイヤーを区別できるようになりました。CronJob はスケジュールを定義し、スケジュールされた各実行が Job を作成し、各 Job が 1 つ以上の Pod を作成します。
実験全体が次の分の境界より前に終わる場合、LAST_SCHEDULE が依然として <none> のことがあります。これはエラーではありません。heartbeat-manual は CronJob のスケジューラーによって作成されたのではなく、--from を使って手動で作成されたためです。最終的に重要なのは SUSPEND=false になっていることです。これにより、今後のスケジュール実行が再び許可されます。
まとめ
有限の処理には Job、並列タスクにはインデックス付きの完了処理、繰り返し実行するスケジュールには CronJob を使用しました。また、コントローラーとの所有関係、ログ、完了状態、同時実行ポリシー、履歴保持数、一時停止機能を確認しました。これらのライフサイクルの違いを理解することは、適切な Kubernetes ワークロードコントローラーを選択するうえで重要です。


