Ritolabo
  1. Home
  2. Kubernetes
  3. Job / CronJob: Kubernetes でバッチ処理を実行する

Job / CronJob: Kubernetes でバッチ処理を実行する

  • 公開日
  • カテゴリ:Kubernetes
  • タグ:Kubernetes,学習メモ
Job / CronJob: 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

  1. 環境
  2. Deployment・Job・CronJob の違い
    1. Job と CronJob はどこにあるか
  3. 終了するコンテナを Deployment で動かす
  4. Job
    1. ログを見る
    2. Job と Pod のつながり
  5. 失敗する Job
  6. Job を消す
  7. CronJob
    1. cron の書式
    2. CronJob を作る
    3. Job が作られるのを待つ
  8. 時刻を待たずに手で動かす
  9. 古い Job は 3 つまで残る
    1. タイムゾーン
  10. CronJob を消す
  11. restartPolicy を OnFailure にする
  12. 何回成功させるか、同時にいくつ動かすか
  13. 終わった Job を自動で消す
  14. 用語メモ
  15. 参考 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 つで動かして比べる。

DeploymentJobCronJob
何のためのデータか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 の中のコマンドを使う。

nginxsh -c 'date; echo ...'
何かWeb サーバー時刻を表示し、文字を表示するだけのコマンド
起動したらアクセスを待ち続ける。終了しない一瞬で終了する(終了コード 0。正常終了)
正常なときの kubectl get podsRunning、READY 1/1Completed、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"]
DeploymentJob
apiVersionapps/v1batch/v1(batch は「一括処理」)
replicas / selector書く書かない(Pod の数は Job が決める。ラベルも自動で付く)
template.spec.restartPolicyAlways(これしか書けない)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 の列意味
STATUSRunning は動いている最中、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 は「止まってしまった」とみなして起動し直す。

DeploymentJob
コンテナが正常終了したら起動し直す(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)
正常終了CompletedComplete
失敗ErrorFailed(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
列意味
SCHEDULEYAML の 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 の場合
自分が書くデータDeploymentCronJob
自動で作られる中間のデータReplicaSetJob
自動で作られる PodPodPod
中間のデータが作られるとき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-manualhello-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 には何も残らない
NeverOnFailure
失敗したら新しい 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


[Prev] Volume: Kubernetes でコンテナのデータを永続化する

Author

rito

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級