Job / CronJob: Kubernetes でバッチ処理を実行する
- 公開日
- カテゴリ:Kubernetes
- タグ:Kubernetes,学習メモ

nginx のような Web サーバーは、起動したらアクセスを待ち続けて終了しない。一方で、データベースのバックアップや集計のような処理は、1 回やって終わればよい。終了するコンテナを Deployment で動かすと、終了するたびに起動し直されてしまう。
Job は、Pod を作ってコンテナが正常に終了するまで面倒を見るデータ。CronJob は、決まった時刻に Job を作るデータ。
この記事では、同じ「時刻と文字を表示して終了するコマンド」を Deployment・Job・CronJob の 3 つで動かして、何が違うかを確認する。
この記事でやること:
- 終了するコンテナを Deployment で動かすと
CrashLoopBackOffになることの確認 - Job で動かすと
Completeになり、Pod がCompletedのまま残ってログが読めることの確認 - 失敗するコンテナの Job が、
backoffLimitの回数までやり直してFailedになることの確認 - CronJob が毎分 Job を作り、古い Job が 3 つを超えると消されることの確認
- 時刻を待たずに手で 1 回動かす方法と、CronJob を消したときに何が消えるかの確認
restartPolicy: OnFailure、completions/parallelism、ttlSecondsAfterFinishedの動きの確認
contents
- 環境
- Deployment・Job・CronJob の違い
- 終了するコンテナを Deployment で動かす
- Job
- 失敗する Job
- Job を消す
- CronJob
- 時刻を待たずに手で動かす
- 古い Job は 3 つまで残る
- CronJob を消す
- restartPolicy を OnFailure にする
- 何回成功させるか、同時にいくつ動かすか
- 終わった Job を自動で消す
- 用語メモ
- 参考 URL
環境
kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)
- 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1
Deployment・Job・CronJob の違い
同じコマンド(date; echo Hello from Kubernetes。時刻と文字を表示して終了する)を 3 つで動かして比べる。
| Deployment | Job | CronJob | |
|---|---|---|---|
| 何のためのデータか | Pod を決めた数だけ動かし続ける | Pod を作り、コンテナが正常に終了するまで面倒を見る | 決まった時刻に Job を作る |
| コンテナが正常終了したら | 起動し直す | そこで完了。Pod は残る | (Job と同じ) |
| コンテナが失敗して終了したら | 起動し直す | 決めた回数までやり直し、超えたら失敗で止まる | (Job と同じ) |
出てくる名前は 2 つ。
| 名前 | 内容 |
|---|---|
| Job | 「このコンテナを動かして、正常に終了したら完了」というデータ。kind: Job。Pod の定義を中に持つ(Deployment と同じ template) |
| CronJob | 「この時刻になったら、この Job を作る」というデータ。kind: CronJob。Job の定義を中に持つ(jobTemplate)。cron は、Unix で昔から使われている「決まった時刻にコマンドを実行する仕組み」の名前 |
Job と CronJob はどこにあるか
Job と CronJob は、Deployment と同じく、動いているプログラムではなく、Control Plane で動いている etcd に保存されるデータ。(「ConfigMap: Kubernetes で Pod に渡す設定値」の「ConfigMap はどこにあるか」を参照)
CronJob を見て時刻どおりに Job を作る、Job を見て Pod を作る、という仕事は、Control Plane で動いている kube-controller-manager というプログラムがやる。実際にコマンドを実行するのは、Worker の Node に置かれた Pod のコンテナ。
- 角が二重の箱(kube-controller-manager、kubelet)は動いているプログラム。それ以外は etcd に保存されたデータと、Node の上の Pod
- 自分で YAML を書くのは CronJob だけ。Job は cronjob-controller が時刻ごとに作り、Pod は job-controller が作る。どちらも kube-controller-manager の中で動いている
- kubelet は、自分の Node に置かれた Pod のコンテナを起動し、終了コードを見届けて報告する。Job の
STATUSがCompleteやFailedになるのは、その報告を見た job-controller が Job のデータを書き換えるため
終了するコンテナを Deployment で動かす
ここまでの記事で使ってきた nginx はサーバーで、起動したらアクセスを待ち続けて終了しない。この記事では、仕事を 1 回やったら終了するプログラムとして、busybox の中のコマンドを使う。
| nginx | sh -c 'date; echo ...' | |
|---|---|---|
| 何か | Web サーバー | 時刻を表示し、文字を表示するだけのコマンド |
| 起動したら | アクセスを待ち続ける。終了しない | 一瞬で終了する(終了コード 0。正常終了) |
正常なときの kubectl get pods | Running、READY 1/1 | Completed、READY 0/1 |
Deployment の形はこれまでと同じで、コンテナの中身だけ違う。(Deployment は「Deployment: Kubernetes で Pod の数と更新を管理する」を参照)
# hello-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deploy
spec:
replicas: 1
selector:
matchLabels:
app: hello-deploy
template:
metadata:
labels:
app: hello-deploy
spec:
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "date; echo Hello from Kubernetes"]
| 部分 | 意味 |
|---|---|
image: busybox:1.36 | 基本的なコマンドをひととおり詰めた小さな image。sh、date、echo が入っている |
command: [...] | コンテナが起動したときに実行するコマンド。nginx の image では省略していた(image に「nginx を起動する」が組み込まれているため) |
-w を付けると、変化があるたびに行が追加されて表示される。
$ kubectl apply -f hello-deployment.yaml
# => deployment.apps/hello-deploy created
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
hello-deploy-7466b65c74-n87st 0/1 Completed 1 (7s ago) 7s
hello-deploy-7466b65c74-n87st 0/1 CrashLoopBackOff 1 (14s ago) 15s
hello-deploy-7466b65c74-n87st 1/1 Running 2 (14s ago) 15s
hello-deploy-7466b65c74-n87st 0/1 Completed 2 (15s ago) 16s
hello-deploy-7466b65c74-n87st 0/1 CrashLoopBackOff 2 (24s ago) 39s
hello-deploy-7466b65c74-n87st 1/1 Running 3 (24s ago) 39s
hello-deploy-7466b65c74-n87st 0/1 Completed 3 (24s ago) 39s
| 表示 | 意味 |
|---|---|
STATUS: Completed | コンテナが正常終了(終了コード 0)した状態 |
READY 0/1 | コンテナが動いていない(終了している) |
RESTARTS が増える | kubelet がコンテナを起動し直した回数 |
STATUS: CrashLoopBackOff | コンテナが終了する → 起動し直す → また終了する、を繰り返している(crash loop)ので、次に起動し直すまで kubelet が間を置いて待っている(back off)状態。待ち時間は 10 秒、20 秒、40 秒…と延びていく(上限 5 分) |
Running は起動し直した直後の 1 秒ほどしか出ない。ほとんどの時間は Completed と CrashLoopBackOff の行き来で、RESTARTS は増え続ける。
コンテナは失敗していない。Last State: を見ると、終了コードは 0。
$ kubectl describe pod -l app=hello-deploy | grep -A 7 'Last State:'
Last State: Terminated
Reason: Completed
Exit Code: 0
Started: Tue, 06 Oct 2026 21:28:34 +0900
Finished: Tue, 06 Oct 2026 21:28:34 +0900
Ready: False
Restart Count: 4
Environment: <none>
それでも起動し直されるのは、Pod の restartPolicy(コンテナが終了したらどうするか)が Always(いつでも起動し直す)だから。Deployment の Pod は Always しか書けず、YAML に書いていなくても Always が入っている。
$ kubectl get deploy hello-deploy -o jsonpath='{.spec.template.spec.restartPolicy}'
# => Always
Deployment は「Pod を決めた数だけ動かし続ける」ためのデータで、コンテナが終了したら、理由が何であれ起動し直す。1 回やって終わる処理には向いていない。
$ kubectl delete -f hello-deployment.yaml
# => deployment.apps "hello-deploy" deleted from default namespace
Job
同じコンテナを Job で動かす。Deployment の YAML との違いは apiVersion、kind、restartPolicy の 3 か所で、コンテナの定義は同じ。
# hello-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "date; echo Hello from Kubernetes"]
| Deployment | Job | |
|---|---|---|
apiVersion | apps/v1 | batch/v1(batch は「一括処理」) |
replicas / selector | 書く | 書かない(Pod の数は Job が決める。ラベルも自動で付く) |
template.spec.restartPolicy | Always(これしか書けない) | Never か OnFailure。Always は書けない(apply でエラーになる) |
restartPolicy: Never は「コンテナが終了しても、同じ Pod の中で起動し直さない」。OnFailure は「失敗したときだけ起動し直す」(後の節で確認する)。
$ kubectl apply -f hello-job.yaml
# => job.batch/hello created
$ kubectl get jobs
NAME STATUS COMPLETIONS DURATION AGE
hello Complete 1/1 3s 5s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-8q7zx 0/1 Completed 0 11s
kubectl get jobs の列 | 意味 |
|---|---|
STATUS | Running は動いている最中、Complete は完了、Failed は失敗 |
COMPLETIONS | 正常終了した Pod の数 / 必要な数。1/1 は「1 回必要で、1 回終わった」 |
DURATION | 始まってから終わるまでの時間 |
- Pod の名前は
Job の名前-英数字 5 文字。Deployment の Pod(名前-ReplicaSet の英数字-英数字)より短い - Pod は
Completed、READY 0/1、RESTARTS 0。正常終了して、起動し直されていない
ログを見る
$ kubectl logs job/hello
Tue Oct 6 12:32:40 UTC 2026
Hello from Kubernetes
kubectl logs job/hello は、Job hello の Pod のログを見る。Pod の名前を書かなくて済む。1 行目が date、2 行目が echo の出力。時刻は UTC で、日本時間より 9 時間前の時刻が出る(コンテナの中のタイムゾーンが UTC のため)。
コンテナはもう終了しているが、Pod が残っているので、ログが読める。Job は「正常終了」を「仕事が終わった」とみなす。Deployment は「止まってしまった」とみなして起動し直す。
| Deployment | Job | |
|---|---|---|
| コンテナが正常終了したら | 起動し直す(RESTARTS が増える) | 完了(Complete、RESTARTS 0) |
| 終わったあとの Pod | 起動し直しを待っている(CrashLoopBackOff) | Completed のまま残る |
| ログ | 起動し直すたびに新しいコンテナになる | 1 回分がそのまま読める |
Job と Pod のつながり
Pod には、Job の名前がラベルで付いている。
$ kubectl get pods -l batch.kubernetes.io/job-name=hello
NAME READY STATUS RESTARTS AGE
hello-8q7zx 0/1 Completed 0 3m41s
Deployment は ReplicaSet を作り、ReplicaSet が Pod を作る(Deployment → ReplicaSet → Pod の 3 段)。Job は間に何もはさまず、Job に合わせて直接 Pod が作られる(Job → Pod の 2 段)。
YAML に書かなかった項目には既定値が入る。kubectl describe job hello で見ると、Completions: 1(正常終了を何回集めたら完了か)、Parallelism: 1(同時に動かす Pod の数)、Backoff Limit: 6(失敗したとき、何回までやり直すか)。
失敗する Job
失敗(終了コード 1)で終わるコンテナの Job。
# fail-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: fail
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
containers:
- name: fail
image: busybox:1.36
command: ["sh", "-c", "echo something went wrong; exit 1"]
| 部分 | 意味 |
|---|---|
exit 1 | シェルを終了コード 1 で終わらせる。0 以外の終了コードは「失敗」 |
backoffLimit: 2 | やり直しの上限。既定の 6 だと待ち時間が長くなるので、2 にしてある |
$ kubectl apply -f fail-job.yaml
# => job.batch/fail created
$ kubectl get pods -l batch.kubernetes.io/job-name=fail -w
NAME READY STATUS RESTARTS AGE
fail-gk5vc 0/1 Error 0 5s
fail-7ttgb 0/1 Pending 0 0s
fail-7ttgb 0/1 ContainerCreating 0 0s
fail-7ttgb 1/1 Running 0 0s
fail-7ttgb 0/1 Error 0 0s
fail-brccm 0/1 Pending 0 0s
fail-brccm 0/1 ContainerCreating 0 0s
fail-brccm 1/1 Running 0 0s
fail-brccm 0/1 Error 0 0s
(同じ行が続く部分は省いた。)
STATUS: Error。コンテナが 0 以外の終了コードで終了した- やり直しのたびに、新しい名前の Pod が作られている。
restartPolicy: Neverなので、同じ Pod の中では起動し直さず、別の Pod が作られる - 失敗した Pod は消えずに残る
$ kubectl get jobs
NAME STATUS COMPLETIONS DURATION AGE
fail Failed 0/1 84s 84s
hello Complete 1/1 3s 6m19s
$ kubectl describe job fail | grep -A 8 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 97s job-controller Created pod: fail-gk5vc
Normal SuccessfulCreate 86s job-controller Created pod: fail-7ttgb
Normal SuccessfulCreate 66s job-controller Created pod: fail-brccm
Warning BackoffLimitExceeded 63s job-controller Job has reached the specified backoff limit
- Job
failのSTATUSはFailed、COMPLETIONSは0/1。一度も正常終了していない Created pod:が 3 回。最初の 1 回 + やり直し 2 回(backoffLimit: 2)。2 つ目は 1 つ目の約 10 秒後、3 つ目は 2 つ目の約 20 秒後に作られているBackoffLimitExceeded/Job has reached the specified backoff limitは「やり直しの上限に達した」。ここで Job は止まり、自分では二度と動き出さない。直すには、原因を直して Job を作り直すFromがjob-controller。Pod を作っているのは kube-controller-manager の中の job-controller
Pod が 3 つあるので、kubectl logs job/fail は 1 つを選んで表示する。
$ kubectl logs job/fail
Found 3 pods, using pod/fail-gk5vc
something went wrong
Pod の STATUS と Job の STATUS は別物なので、分けて覚える。
Pod(kubectl get pods) | Job(kubectl get jobs) | |
|---|---|---|
| 正常終了 | Completed | Complete |
| 失敗 | Error | Failed(backoffLimit を超えたあと) |
Deployment も「終了したら起動し直す」だったが、終了の理由を見ていなかった。Job は、正常終了なら完了、失敗ならやり直し、と理由で分ける。やり直しても直らない失敗(設定ミスなど)のために、backoffLimit で上限を決める。
Job を消す
Job も Pod も、自分で消すまで残る。
$ kubectl delete -f hello-job.yaml -f fail-job.yaml
job.batch "hello" deleted from default namespace
job.batch "fail" deleted from default namespace
$ kubectl get jobs,pods
# => No resources found in default namespace.
Pod は個別に消していないが、Job と一緒に消えた。Job に合わせて作られた Pod は Job に属していて、Job を消すと一緒に消される(Deployment を消すと Pod が消えるのと同じ)。
CronJob
cron の書式
schedule フィールドに、いつ動かすかを cron の書式で書く。空白で区切った 5 つの欄で、左から「分、時、日、月、曜日」。* は「毎回」。
┌───────────── 分(0〜59)
│ ┌───────────── 時(0〜23)
│ │ ┌───────────── 日(1〜31)
│ │ │ ┌───────────── 月(1〜12)
│ │ │ │ ┌───────────── 曜日(0〜6。0 が日曜)
│ │ │ │ │
* * * * *
| 書き方 | 意味 |
|---|---|
*/1 * * * * | 毎分(*/1 は「1 ごと」。* * * * * と同じ意味。この記事ではこれ) |
0 3 * * * | 毎日 3 時 0 分 |
0 3 * * 1 | 毎週月曜の 3 時 0 分 |
*/5 * * * * | 5 分ごと |
CronJob を作る
jobTemplate.spec の中身は、hello-job.yaml の spec と同じ。
# hello-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cron
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "date; echo Hello from Kubernetes"]
| フィールド | 意味 |
|---|---|
schedule | いつ Job を作るか。cron の書式。* が YAML の記号と誤読されないように、引用符で囲む |
jobTemplate | 作る Job の定義。Job の YAML の spec をそのまま入れる |
jobTemplate.spec.template | その Job に合わせて作られる Pod の定義。Deployment や Job の template と同じ |
CronJob の中に Job の定義、Job の定義の中に Pod の定義、という 3 段の入れ子になっている。
$ kubectl apply -f hello-cronjob.yaml
# => cronjob.batch/hello-cron created
$ kubectl get cronjobs
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-cron */1 * * * * <none> False 0 <none> 4s
| 列 | 意味 |
|---|---|
SCHEDULE | YAML の schedule |
TIMEZONE | 時刻をどのタイムゾーンで読むか。<none> は書いていない |
SUSPEND | 一時停止しているか。False は動いている |
ACTIVE | 今動いている Job の数 |
LAST SCHEDULE | 最後に Job を作ってからの経過時間。<none> は、まだ 1 回も作っていない |
Job が作られるのを待つ
次の分の変わり目(秒が 00 になるとき)に Job が作られる。
$ kubectl get jobs -w
NAME STATUS COMPLETIONS DURATION AGE
hello-cron-29854843 Complete 1/1 2s 12s
hello-cron-29854844 Running 0/1 0s
hello-cron-29854844 Running 0/1 0s 0s
hello-cron-29854844 SuccessCriteriaMet 0/1 3s 3s
hello-cron-29854844 Complete 1/1 3s 3s
- 自分では Job を作っていないのに、Job ができている。1 分たつと次の Job が作られる
- 名前は
CronJob の名前-数字。数字は、予定の時刻を 1970 年 1 月 1 日 0 時(UTC)からの経過分数で表したもの。同じ時刻の Job が二重に作られないように、時刻から名前を決めている Completeの直前にSuccessCriteriaMet(成功の条件を満たした)が一瞬出る。Pod の終了処理が終わるとCompleteになる
$ kubectl get cronjobs,jobs,pods
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/hello-cron */1 * * * * <none> False 0 29s 107s
NAME STATUS COMPLETIONS DURATION AGE
job.batch/hello-cron-29854843 Complete 1/1 2s 89s
job.batch/hello-cron-29854844 Complete 1/1 3s 29s
NAME READY STATUS RESTARTS AGE
pod/hello-cron-29854843-j8rnv 0/1 Completed 0 89s
pod/hello-cron-29854844-s4svw 0/1 Completed 0 29s
$ kubectl logs job/hello-cron-29854844
Tue Oct 6 12:44:00 UTC 2026
Hello from Kubernetes
- CronJob の
LAST SCHEDULEに値が入った - Job 1 つにつき Pod が 1 つ。Pod の名前は
Job の名前-英数字 5 文字 dateの時刻の秒が00。分の変わり目に動いている
| Deployment の場合 | CronJob の場合 | |
|---|---|---|
| 自分が書くデータ | Deployment | CronJob |
| 自動で作られる中間のデータ | ReplicaSet | Job |
| 自動で作られる Pod | Pod | Pod |
| 中間のデータが作られるとき | Pod の定義を変えたとき | schedule の時刻になるたび |
CronJob の仕事は「時刻になったら Job を作る」だけ。Pod を作って終わりまで面倒を見るのは Job の役目で、ここまでの節と同じ。だから、CronJob の jobTemplate には、Job の restartPolicy や backoffLimit がそのまま書ける。
時刻を待たずに手で動かす
「毎日 3 時」の CronJob を試すのに 3 時まで待たなくてよいように、kubectl create job <Job の名前> --from=cronjob/<CronJob の名前> で、CronJob の jobTemplate から Job を手で 1 つ作れる。CronJob 自身は時刻が来るまで何もしないので、「今すぐ 1 回分の Job を作れ」と自分で命令する形。
$ kubectl create job hello-manual --from=cronjob/hello-cron
# => job.batch/hello-manual created
$ kubectl get jobs
NAME STATUS COMPLETIONS DURATION AGE
hello-cron-29854846 Complete 1/1 3s 68s
hello-cron-29854847 Complete 1/1 2s 8s
hello-manual Complete 1/1 3s 7s
| 部分 | 意味 |
|---|---|
kubectl create job hello-manual | hello-manual という名前の Job を作る |
--from=cronjob/hello-cron | 中身は CronJob hello-cron の jobTemplate を使う |
hello-manual も CronJob hello-cron に属する Job として扱われ、次の節の「残す数」にも数えられる。毎分の Job も、手で動かしている間、時刻どおりに作られ続けている。
古い Job は 3 つまで残る
毎分 Job が増えていくが、完了した Job は新しいものから 3 つまでしか残らず、古いものから消される。上の kubectl get jobs でも、最初に作られた hello-cron-29854843 と hello-cron-29854844 はすでにない。
$ kubectl describe cronjob hello-cron | grep -A 12 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 5m19s cronjob-controller Created job hello-cron-29854843
Normal SawCompletedJob 5m17s cronjob-controller Saw completed job: hello-cron-29854843, condition: Complete
Normal SuccessfulCreate 4m19s cronjob-controller Created job hello-cron-29854844
Normal SawCompletedJob 4m16s cronjob-controller Saw completed job: hello-cron-29854844, condition: Complete
Normal SuccessfulCreate 3m19s cronjob-controller Created job hello-cron-29854845
Normal SawCompletedJob 3m16s cronjob-controller Saw completed job: hello-cron-29854845, condition: Complete
Normal SuccessfulCreate 2m19s cronjob-controller Created job hello-cron-29854846
Normal SawCompletedJob 2m16s cronjob-controller Saw completed job: hello-cron-29854846, condition: Complete
Normal SuccessfulDelete 2m16s cronjob-controller Deleted job hello-cron-29854843
Normal SuccessfulCreate 79s cronjob-controller Created job hello-cron-29854847
Reason | 起きたこと |
|---|---|
SuccessfulCreate / Created job ... | 時刻になったので Job を作った |
SawCompletedJob / Saw completed job: ..., condition: Complete | その Job が完了したのを見届けた |
SuccessfulDelete / Deleted job ... | 4 つ目の Job が完了したので、いちばん古い Job を消した |
From は cronjob-controller。Job の Events: の job-controller とは別の係で、どちらも kube-controller-manager の中で動いている。
残す数は CronJob の successfulJobsHistoryLimit(既定 3)で決まる。失敗した Job は failedJobsHistoryLimit(既定 1)。YAML に書いていなくても、kubectl describe cronjob の Successful Job History Limit: 3 / Failed Job History Limit: 1 に入っている。Job が消えると、その Pod も消える。
タイムゾーン
*/1 * * * * では関係ないが、0 3 * * *(3 時)のように時刻を書くときは、何時の 3 時かが問題になる。timeZone を書かない CronJob の時刻は、kube-controller-manager が動いているマシンのタイムゾーンで読まれる。このクラスタの Control Plane の Node は UTC なので、0 3 * * * は日本時間の 12 時になる。
日本時間で読ませるには、CronJob の YAML の spec に、schedule と並べて timeZone を書く(この記事では試していない)。
spec:
schedule: "0 3 * * *"
timeZone: "Asia/Tokyo"
jobTemplate:
...
CronJob を消す
$ kubectl delete -f hello-cronjob.yaml
# => cronjob.batch "hello-cron" deleted from default namespace
$ kubectl get cronjobs,jobs,pods
# => No resources found in default namespace.
CronJob を消しただけで、Job も Pod もなくなった。CronJob → Job → Pod とつながっているので、上を消すと下も消える。
restartPolicy を OnFailure にする
失敗する Job の restartPolicy を Never から OnFailure に変える。
# fail-job-onfailure.yaml(restartPolicy 以外は fail-job.yaml と同じ)
restartPolicy: OnFailure
$ kubectl apply -f fail-job-onfailure.yaml
# => job.batch/fail-onfailure created
$ kubectl get pods -l batch.kubernetes.io/job-name=fail-onfailure -w
NAME READY STATUS RESTARTS AGE
fail-onfailure-88mb5 0/1 Error 1 (6s ago) 6s
fail-onfailure-88mb5 0/1 CrashLoopBackOff 1 (14s ago) 15s
fail-onfailure-88mb5 1/1 Running 2 (14s ago) 15s
fail-onfailure-88mb5 0/1 Error 2 (14s ago) 15s
fail-onfailure-88mb5 0/1 Terminating 2 (15s ago) 16s
fail-onfailure-88mb5 0/1 Error 2 (16s ago) 17s
$ kubectl get jobs
NAME STATUS COMPLETIONS DURATION AGE
fail-onfailure Failed 0/1 56s 56s
$ kubectl get pods
# => No resources found in default namespace.
(同じ行が続く部分は省いた。)
- Pod は 1 つのまま、
RESTARTSが増えていく。kubelet が同じ Pod の中でコンテナを起動し直している(Deployment のときと同じCrashLoopBackOffも出る) RESTARTSが2(backoffLimit)に達すると Job はFailedになり、動いていた Pod はTerminatingを経て消える。kubectl get podsには何も残らない
Never | OnFailure | |
|---|---|---|
| 失敗したら | 新しい Pod が作られる | kubelet が同じ Pod の中でコンテナを起動し直す(RESTARTS が増える) |
backoffLimit の数え方 | 失敗した Pod の数 | コンテナの起動し直しの回数 |
| 上限に達したら | Job が Failed。Pod は残る | Job が Failed。動いていた Pod は消される |
| 失敗のログ | 残る | 消える |
公式ドキュメントは、Job の動きを調べているときは Never を勧めている(失敗したログが消えないため)。
$ kubectl delete -f fail-job-onfailure.yaml
何回成功させるか、同時にいくつ動かすか
completions(何回成功したら完了か)と parallelism(同時に動かす Pod の数)。既定はどちらも 1。
# hello-job-parallel.yaml(spec の部分)
spec:
completions: 3
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "date; echo Hello from Kubernetes; sleep 5"]
sleep 5 を足してあるのは、同時に動いているところを見るため。
$ kubectl apply -f hello-job-parallel.yaml
# => job.batch/hello-parallel created
$ kubectl get jobs -w
NAME STATUS COMPLETIONS DURATION AGE
hello-parallel Running 0/3 0s 0s
hello-parallel Running 0/3 7s 7s
hello-parallel Running 2/3 8s 8s
hello-parallel Running 2/3 15s 15s
hello-parallel SuccessCriteriaMet 2/3 16s 16s
hello-parallel Complete 3/3 16s 16s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-parallel-htqv5 0/1 Completed 0 29s
hello-parallel-nwpp7 0/1 Completed 0 29s
hello-parallel-zbscf 0/1 Completed 0 21s
(同じ行が続く部分は省いた。)
COMPLETIONSの分母が3- 最初の 2 つ(
htqv5、nwpp7)は同時に始まり、8 秒で2/3になった。3 つ目(zbscf)はそのあとに始まっている(AGEが 8 秒違う)。parallelism: 2の効き方 - 大量のデータを分けて処理するときに使う形。この記事のコマンドは 3 回とも同じことをしているだけ
$ kubectl delete -f hello-job-parallel.yaml
終わった Job を自動で消す
Job を大量に作る運用では、終わった Job がたまっていく。ttlSecondsAfterFinished を書くと、終わってからその秒数がたったときに、Job と Pod が自動で消える。
# hello-job-ttl.yaml(hello-job.yaml に 1 行足したもの)
spec:
ttlSecondsAfterFinished: 30
$ kubectl apply -f hello-job-ttl.yaml
# => job.batch/hello-ttl created
$ kubectl get jobs,pods
NAME STATUS COMPLETIONS DURATION AGE
job.batch/hello-ttl Complete 1/1 3s 4s
NAME READY STATUS RESTARTS AGE
pod/hello-ttl-h74x8 0/1 Completed 0 4s
40 秒ほど待って、もう一度。
$ kubectl get jobs,pods
# => No resources found in default namespace.
自分で消していないのに、Job も Pod も消えた。消しているのは kube-controller-manager の中の ttl-after-finished-controller。TTL は Time To Live(生きていられる時間)。CronJob が作る Job は successfulJobsHistoryLimit で消されるので、ttlSecondsAfterFinished は主に自分で直接作る Job に使う。
用語メモ
| 用語 | 読み方 | 意味 |
|---|---|---|
| Job | ジョブ | Pod を作り、コンテナが正常終了するまで面倒を見るデータ。kind: Job、apiVersion: batch/v1 |
| CronJob | クロンジョブ | 決まった時刻に Job を作るデータ。kind: CronJob |
| cron | クロン | Unix で昔から使われている、決まった時刻にコマンドを実行する仕組み。その書式(分 時 日 月 曜日)が CronJob の schedule に使われている |
| restartPolicy | リスタート ポリシー | コンテナが終了したらどうするか。Always(いつでも起動し直す。Deployment はこれだけ)、OnFailure(失敗したときだけ)、Never(起動し直さない)。Job は Never か OnFailure |
| Completed / Error | コンプリーテッド / エラー | Pod の STATUS。コンテナが終了コード 0 で終了した / 0 以外で終了した |
| CrashLoopBackOff | クラッシュ ループ バックオフ | Pod の STATUS。コンテナが何度も終了するので、次に起動し直すまで kubelet が待っている。間隔は 10 秒、20 秒…と延びる |
| Complete / Failed | コンプリート / フェイルド | Job の STATUS。完了 / 失敗(backoffLimit を超えた) |
| backoffLimit | バックオフ リミット | 失敗したとき、何回までやり直すか。既定 6 |
| completions / parallelism | コンプリーションズ / パラレリズム | 何回成功したら完了か / 同時に動かす Pod の数。既定はどちらも 1 |
| schedule / jobTemplate | スケジュール / ジョブ テンプレート | CronJob のフィールド。いつ Job を作るか / 作る Job の定義 |
| successfulJobsHistoryLimit | サクセスフル ジョブズ ヒストリー リミット | 完了した Job をいくつ残すか。既定 3 |
| ttlSecondsAfterFinished | ティーティーエル セカンズ アフター フィニッシュド | Job のフィールド。終わってから何秒後に自動で消すか |
| kube-controller-manager | キューブ コントローラー マネージャー | Control Plane で動いているプログラム。etcd のデータを見て別のデータを作る係(controller)がたくさん入っている。job-controller と cronjob-controller はその中の 2 つ |
まとめ
- Deployment は Pod を動かし続けるためのデータ。コンテナが正常終了しても起動し直すので(
restartPolicy: Always)、1 回で終わる処理には向かない。CrashLoopBackOffになる - Job は、Pod を作ってコンテナが正常終了するまで面倒を見るデータ。
restartPolicyはNeverかOnFailure - 正常終了すると Job は
Complete。Pod はCompletedのまま残り、kubectl logs job/<名前>でログが読める - 失敗すると、新しい Pod でやり直す。
backoffLimit(既定 6)を超えるとFailedになり、それ以上は動かない - Job を消すと、その Pod も消える。
ttlSecondsAfterFinishedを書くと、終わってから自動で消える - CronJob は、
schedule(cron の書式)の時刻ごとに Job を作るデータ。CronJob → Job → Pod の 3 段で、CronJob を消すと Job と Pod も消える - 完了した Job は新しい 3 つまで残り(
successfulJobsHistoryLimit)、古いものから消える。kubectl create job <名前> --from=cronjob/<名前>で、時刻を待たずに 1 回動かせる - Job を見て Pod を作る、CronJob を見て Job を作るのは、kube-controller-manager の中の job-controller と cronjob-controller。コンテナを起動して終了を見届けるのは kubelet
timeZoneを書かない CronJob の時刻は、kube-controller-manager のマシンのタイムゾーン(このクラスタでは UTC)で読まれる
参考 URL
- [official] Job | Kubernetes
- [official] CronJob | Kubernetes
- [official] CronJobを使用して自動化タスクを実行する | Kubernetes
- [official] Podのライフサイクル | Kubernetes
- [official] コントローラー | Kubernetes

