Jobs

Jobは一つ以上のPodを作成し、指定された数のPodが正常に終了するまで、Podの実行を再試行し続けます。Podが正常に終了すると、Jobは成功したPodの数を追跡します。指定された完了数に達すると、そのタスク(つまりJob)は完了したとみなされます。Jobを削除すると、作成されたPodも一緒に削除されます。Jobを一時停止すると、再開されるまで、稼働しているPodは全部削除されます。

単純なケースを言うと、確実に一つのPodが正常に完了するまで実行されるよう、一つのJobオブジェクトを作成します。 一つ目のPodに障害が発生したり、(例えばノードのハードウェア障害またノードの再起動が原因で)削除されたりすると、Jobオブジェクトは新しいPodを作成します。

Jobで複数のPodを並列で実行することもできます。

スケジュールに沿ってJob(単一のタスクか複数タスク並列のいずれか)を実行したい場合は CronJobを参照してください。

実行例

下記にJobの定義例を記載しています。πを2000桁まで計算して出力するJobで、完了するまで約10秒かかります。

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

このコマンドで実行できます:

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

実行結果はこのようになります:

job.batch/pi created

kubectlでJobの状態を確認できます:


Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl:5.34.0
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7


apiVersion: batch/v1
kind: Job
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":{"spec":{"containers":[{"command":["perl","-Mbignum=bpi","-wle","print bpi(2000)"],"image":"perl","name":"pi"}],"restartPolicy":"Never"}}}}
  creationTimestamp: "2022-06-15T08:40:15Z"
  generation: 1
  labels:
    controller-uid: 863452e6-270d-420e-9b94-53a54146c223
    job-name: pi
  name: pi
  namespace: default
  resourceVersion: "987"
  uid: 863452e6-270d-420e-9b94-53a54146c223
spec:
  backoffLimit: 4
  completionMode: NonIndexed
  completions: 1
  parallelism: 1
  selector:
    matchLabels:
      controller-uid: 863452e6-270d-420e-9b94-53a54146c223
  suspend: false
  template:
    metadata:
      creationTimestamp: null
      labels:
        controller-uid: 863452e6-270d-420e-9b94-53a54146c223
        job-name: pi
    spec:
      containers:
      - command:
        - perl
        - -Mbignum=bpi
        - -wle
        - print bpi(2000)
        image: perl:5.34.0
        imagePullPolicy: Always
        name: pi
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Never
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  active: 1
  ready: 1
  startTime: "2022-06-15T08:40:15Z"

Jobの完了したPodを確認するには、kubectl get podsを使います。

Jobに属するPodの一覧を機械可読形式で出力するには、下記のコマンドを使います:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods

出力結果はこのようになります:

pi-5rwd7

ここのセレクターはJobのセレクターと同じです。--output=jsonpathオプションは、返されたリストからPodのnameフィールドを指定するための表現です。

その中の一つのPodの標準出力を確認するには:

kubectl logs $pods

出力結果はこのようになります:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

Job spec(仕様)の書き方

他のKubernetesオブジェクト設定ファイルと同様に、JobにもapiVersionkindまたはmetadataフィールドが必要です。 Jobの名前は有効なDNSサブドメイン名である必要があります。

Jobには.specセクションも必要です。

Podテンプレート

.spec.template.specの唯一の必須フィールドです。

.spec.templatepodテンプレートです。ネストされていることとapiVersionkindフィールドが不要になったことを除いて、仕様の定義がPodと全く同じです。

Podの必須フィールドに加えて、Job定義ファイルにあるPodテンプレートでは、適切なラベル(podセレクターを参照)と適切な再起動ポリシーを指定する必要があります。

RestartPolicyNeverOnFailureのみ設定可能です。

Podセレクター

.spec.selectorフィールドはオプションです。ほとんどの場合はむしろ指定しないほうがよいです。 独自のPodセレクターを指定セクションを参照してください。

Jobの並列実行

Jobで実行するのに適したタスクは主に3種類あります:

  1. 非並列Job
    • 通常、Podに障害が発生しない限り、一つのPodのみが起動されます。
    • Podが正常に終了すると、Jobはすぐに完了します。
  2. 固定の完了数を持つ並列Job:
    • .spec.completionsに0以外の正の値を指定します。
    • Jobは全体的なタスクを表し、.spec.completions個のPodが成功すると、Jobの完了となります。
    • .spec.completionMode="Indexed"を利用する場合、各Podは0から.spec.completions-1までの範囲内のインデックスがアサインされます。
  3. ワークキューを利用した並列Job:
    • .spec.completionsの指定をしない場合、デフォルトは.spec.parallelismとなります。
    • Pod間で調整する、または外部サービスを使う方法で、それぞれ何のタスクに着手するかを決めます。例えば、一つのPodはワークキューから最大N個のタスクを一括で取得できます。
    • 各Podは他のPodがすべて終了したかどうか、つまりJobが完了したかどうかを単独で判断できます。
    • Jobに属する 任意 のPodが正常に終了すると、新しいPodは作成されません。
    • 一つ以上のPodが正常に終了し、すべてのPodが終了すると、Jobは正常に完了します。
    • 一つのPodが正常に終了すると、他のPodは同じタスクの作業を行ったり、出力を書き込んだりすることはできません。すべてのPodが終了プロセスに進む必要があります。

非並列 Jobの場合、.spec.completions.spec.parallelismの両方を未設定のままにしておくことも可能です。未設定の場合、両方がデフォルトで1になります。

完了数固定 Jobの場合、.spec.completionsを必要完了数に設定する必要があります。 .spec.parallelismを設定してもいいですし、未設定の場合、デフォルトで1になります。

ワークキュー 並列Jobの場合、.spec.completionsを未設定のままにし、.spec.parallelismを非負の整数に設定する必要があります。

各種類のJobの使用方法の詳細については、Jobパターンセクションを参照してください。

並列処理の制御

必要並列数(.spec.parallelism)は任意の非負の値に設定できます。 未設定の場合は、デフォルトで1になります。 0に設定した際には、増加するまでJobは一時停止されます。

実際の並列数(任意の瞬間に実行されているPod数)は、さまざまな理由により、必要並列数と異なる可能性があります:

  • 完了数固定 Jobの場合、実際に並列して実行されるPodの数は、残りの完了数を超えることはありません。 .spec.parallelismの値が高い場合は無視されます。
  • ワークキュー Jobの場合、任意のPodが成功すると、新しいPodは作成されません。ただし、残りのPodは終了まで実行し続けられます。
  • Jobコントローラーの応答する時間がなかった場合。
  • Jobコントローラーが何らかの理由で(ResourceQuotaの不足、権限の不足など)、Podを作成できない場合、 実際の並列数は必要並列数より少なくなる可能性があります。
  • 同じJobで過去に発生した過度のPod障害が原因で、Jobコントローラーは新しいPodの作成を抑制することがあります。
  • Podがグレースフルシャットダウンされた場合、停止するのに時間がかかります。

完了モード

FEATURE STATE: Kubernetes v1.24 [stable]

完了数固定 Job、つまり.spec.completionsの値がnullではないJobは.spec.completionModeで完了モードを指定できます:

  • NonIndexed(デフォルト): .spec.completions個のPodが成功した場合、Jobの完了となります。言い換えれば、各Podの完了状態は同質です。ここで要注意なのは、.spec.completionsの値がnullの場合、暗黙的にNonIndexedとして指定されることです。

  • Indexed: Jobに属するPodはそれぞれ、0から.spec.completions-1の範囲内の完了インデックスを取得できます。インデックスは下記の三つの方法で取得できます。

    • Podアノテーションbatch.kubernetes.io/job-completion-index
    • Podホスト名の一部として、$(job-name)-$(index)の形式になっています。 インデックス付きJob(Indexed Job)とServiceを一緒に使用すると、Jobに属するPodはお互いにDNSを介して確定的ホスト名で通信できます。
    • コンテナ化されたタスクの環境変数JOB_COMPLETION_INDEX

    インデックスごとに、成功したPodが一つ存在すると、Jobの完了となります。完了モードの使用方法の詳細については、 静的な処理の割り当てを使用した並列処理のためのインデックス付きJobを参照してください。めったに発生しませんが、同じインデックスを取得して稼働し始めるPodも存在する可能性があります。ただし、完了数にカウントされるのはそのうちの一つだけです。

Podとコンテナの障害対策

Pod内のコンテナは、その中のプロセスが0以外の終了コードで終了した、またはメモリ制限を超えたためにコンテナが強制終了されたなど、様々な理由で失敗することがあります。この場合、もし.spec.template.spec.restartPolicy = "OnFailure"と設定すると、Podはノード上に残りますが、コンテナは再実行されます。そのため、プログラムがローカルで再起動した場合の処理を行うか、.spec.template.spec.restartPolicy = "Never"と指定する必要があります。 restartPolicyの詳細についてはPodのライフサイクルを参照してください。

Podがノードからキックされた(ノードがアップグレード、再起動、削除されたなど)、または.spec.template.spec.restartPolicy = "Never"と設定されたときにPodに属するコンテナが失敗したなど、様々な理由でPod全体が故障することもあります。Podに障害が発生すると、Jobコントローラーは新しいPodを起動します。つまりアプリケーションは新しいPodで再起動された場合の処理を行う必要があります。特に、過去に実行した際に生じた一時ファイル、ロック、不完全な出力などを処理する必要があります。

.spec.parallelism = 1.spec.completions = 1.spec.template.spec.restartPolicy = "Never"を指定しても、同じプログラムが2回起動されることもありますので注意してください。

.spec.parallelism.spec.completionsを両方とも2以上指定した場合、複数のPodが同時に実行される可能性があります。そのため、Podは並行処理を行えるようにする必要があります。

Pod失敗のバックオフポリシー

設定の論理エラーなどにより、Jobが数回再試行した後に失敗状態にしたい場合があります。.spec.backoffLimitを設定すると、失敗したと判断するまでの再試行回数を指定できます。バックオフ制限はデフォルトで6に設定されています。Jobに属していて失敗したPodはJobコントローラーにより再作成され、バックオフ遅延は指数関数的に増加し(10秒、20秒、40秒…)、最大6分まで増加します。

再実行回数の算出方法は以下の2通りです:

  • .status.phase = "Failed"で設定されたPod数を計算します。
  • restartPolicy = "OnFailure"と設定された場合、.status.phasePendingまたはRunningであるPodに属するすべてのコンテナで再試行する回数を計算します。

どちらかの計算が.spec.backoffLimitに達した場合、Jobは失敗とみなされます。

JobTrackingWithFinalizers機能が無効な場合、 失敗したPodの数は、API内にまだ存在するPodのみに基づいています。

Jobの終了とクリーンアップ

Jobが完了すると、それ以上Podは作成されませんが、通常Podが削除されることもありません。 これらを残しておくと、完了したPodのログを確認でき、エラーや警告などの診断出力を確認できます。 またJobオブジェクトはJob完了後も残っているため、状態を確認することができます。古いJobの状態を把握した上で、削除するかどうかはユーザー次第です。Jobを削除するにはkubectl (例:kubectl delete jobs/piまたはkubectl delete -f ./job.yaml)を使います。kubectlでJobを削除する場合、Jobが作成したPodも全部削除されます。

デフォルトでは、Jobは中断されることなく実行できますが、Podが失敗した場合(restartPolicy=Never)、またはコンテナがエラーで終了した場合(restartPolicy=OnFailure)のみ、前述の.spec.backoffLimitで決まった回数まで再試行します。.spec.backoffLimitに達すると、Jobが失敗とマークされ、実行中のPodもすべて終了されます。

Jobを終了させるもう一つの方法は、活動期間を設定することです。 Jobの.spec.activeDeadlineSecondsフィールドに秒数を設定することで、活動期間を設定できます。 Podがいくつ作成されても、activeDeadlineSecondsはJobの存続する時間に適用されます。 JobがactiveDeadlineSecondsに達すると、実行中のすべてのPodは終了され、Jobの状態はtype: Failedになり、理由はreason: DeadlineExceededになります。

ここで要注意なのは、Jobの.spec.activeDeadlineSeconds.spec.backoffLimitよりも優先されます。したがって、失敗して再試行しているPodが一つ以上持っているJobは、backoffLimitに達していなくても、activeDeadlineSecondsで指定された設定時間に達すると、追加のPodをデプロイしなくなります。

例えば:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job仕様と、Jobに属するPodテンプレートの仕様は両方ともactiveDeadlineSecondsフィールドを持っているので注意してください。適切なレベルで設定していることを確認してください。

またrestartPolicyはJob自体ではなく、Podに適用されることも注意してください: Jobの状態はtype: Failedになると、自動的に再起動されることはありません。 つまり、.spec.activeDeadlineSeconds.spec.backoffLimitによって引き起こされるJob終了メカニズムは、永久的なJob失敗につながり、手動で介入して解決する必要があります。

終了したJobの自動クリーンアップ

終了したJobは通常システムに残す必要はありません。残ったままにしておくとAPIサーバーに負担をかけることになります。Jobが上位コントローラーにより直接管理されている場合、例えばCronJobsの場合、Jobは指定された容量ベースのクリーンアップポリシーに基づき、CronJobによりクリーンアップされます。

終了したJobのTTLメカニズム

FEATURE STATE: Kubernetes v1.23 [stable]

終了したJob(状態がCompleteFailedになったJob)を自動的にクリーンアップするもう一つの方法は TTLコントローラーより提供されたTTLメカニズムです。.spec.ttlSecondsAfterFinishedフィールドを指定することで、終了したリソースをクリーンアップすることができます。

TTLコントローラーでJobをクリーンアップする場合、Jobはカスケード的に削除されます。つまりJobを削除する際に、Jobに属しているオブジェクト、例えばPodなども一緒に削除されます。Jobが削除される場合、Finalizerなどの、Jobのライフサイクル保証は守られることに注意してください。

例えば:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttlは終了してからの100秒後に自動的に削除されるようになっています。

このフィールドに0を設定すると、Jobは終了後すぐに自動削除の対象になります。このフィールドに何も設定しないと、Jobが終了してもTTLコントローラーによるクリーンアップはされません。

Jobパターン

Jobオブジェクトは、Podの確実な並列実行をサポートするために使用されます。科学技術計算でよく見られるような、密接に通信を行う並列処理をサポートするようには設計されていません。独立だが関連性のある一連の作業項目の並列処理をサポートします。例えば送信すべき電子メール、レンダリングすべきフレーム、トランスコードすべきファイル、スキャンすべきNoSQLデータベースのキーの範囲、などです。

複雑なシステムでは、異なる作業項目のセットが複数存在する場合があります。ここでは、ユーザーが一斉に管理したい作業項目のセットが一つだけの場合 — つまりバッチJobだけを考えます。

並列計算にはいくつかのパターンがあり、それぞれに長所と短所があります。 トレードオフの関係にあるのは:

  • 各作業項目に1つのJobオブジェクト vs. すべての作業項目に1つのJobオブジェクト。
     後者は大量の作業項目を処理する場合に適しています。
     前者は大量のJobオブジェクトを管理するため、ユーザーとシステムにオーバーヘッドをかけることになります。
  • 作成されるPod数が作業項目数と等しい、 vs. 各Podが複数の作業項目を処理する。  前者は通常、既存のコードやコンテナへの変更が少なくて済みます。 後者は上記と同じ理由で、大量の作業項目を処理する場合に適しています。
  • ワークキューを利用するアプローチもいくつかあります。それを使うためには、キューサービスを実行し、既存のプログラムやコンテナにワークキューを利用させるための改造を行う必要があります。 他のアプローチは既存のコンテナ型アプリケーションに適用しやすいです。

ここでは、上記のトレードオフをまとめてあり、それぞれ2~4列目に対応しています。 またパターン名のところは、例やより詳しい説明が書いてあるページへのリンクになっています。

パターン 単一Jobオブジェクト Podが作業項目より少ない? アプリを修正せずに使用できる?
作業項目ごとにPodを持つキュー 時々
Pod数可変のキュー
静的な処理の割り当てを使用したインデックス付きJob
Jobテンプレート拡張

.spec.completionsで完了数を指定する場合、Jobコントローラーより作成された各Podは同一のspecを持ちます。これは、このタスクのすべてのPodが同じコマンドライン、同じイメージ、同じボリューム、そして(ほぼ)同じ環境変数を持つことを意味します。これらのパターンは、Podが異なる作業をするためのさまざまな配置方法になります。

この表は、各パターンで必要な.spec.parallelism.spec.completionsの設定を示しています。 ここで、Wは作業項目の数を表しています。

パターン .spec.completions .spec.parallelism
作業項目ごとにPodを持つキュー W 任意
Pod数可変のキュー null 任意
静的な処理の割り当てを使用したインデックス付きJob W 任意
Jobテンプレート拡張 1 1であるべき

高度な使い方

Jobの一時停止

FEATURE STATE: Kubernetes v1.24 [stable]

Jobが作成されると、JobコントローラーはJobの要件を満たすために直ちにPodの作成を開始し、Jobが完了するまで作成し続けます。しかし、Jobの実行を一時的に中断して後で再開したい場合、または一時停止状態のJobを再開し、再開時間は後でカスタムコントローラーに判断させたい場合はあると思います。

Jobを一時停止するには、Jobの.spec.suspendフィールドをtrueに修正し、後でまた再開したい場合にはfalseに修正すればよいです。 .spec.suspendをtrueに設定してJobを作成すると、一時停止状態のままで作成されます。

一時停止状態のJobを再開すると、.status.startTimeフィールドの値は現在時刻にリセットされます。これはつまり、Jobが一時停止して再開すると、.spec.activeDeadlineSecondsタイマーは停止してリセットされることになります。

Jobを中断すると、稼働中のPodは全部削除されることを忘れないでください。Jobが中断されると、PodはSIGTERMシグナルを受信して終了されます。Podのグレースフル終了の猶予期間がカウントダウンされ、この期間内に、Podはこのシグナルを処理しなければなりません。場合により、その後のために処理状況を保存したり、変更を元に戻したりする処理が含まれます。この方法で終了したPodはcompletions数にカウントされません。

下記は一時停止状態のままで作成されたJobの定義例になります:

kubectl get job myjob -o yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  suspend: true
  parallelism: 1
  completions: 5
  template:
    spec:
      ...

Jobのstatusセクションで、Jobが停止中なのか、過去に停止したことがあるかを判断できます:

kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
  conditions:
  - lastProbeTime: "2021-02-05T13:14:33Z"
    lastTransitionTime: "2021-02-05T13:14:33Z"
    status: "True"
    type: Suspended
  startTime: "2021-02-05T13:13:48Z"

Jobのcondition.typeが"Suspended"で、statusが"True"になった場合、Jobは一時停止中になります。lastTransitionTimeフィールドで、どのぐらい中断されたかを判断できます。statusが"False"になった場合、Jobは一時停止状態でしたが、今は実行されていることになります。conditionが書いていない場合、Jobは一度も停止していないことになります。

Jobが一時停止して再開した場合、Eventsも作成されます:

kubectl describe jobs/myjob
Name:           myjob
...
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
  Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
  Normal  Suspended         11m   job-controller  Job suspended
  Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
  Normal  Resumed           3s    job-controller  Job resumed

最後の4つのイベント、特に"Suspended"と"Resumed"のイベントは、.spec.suspendフィールドの値を切り替えた直接の結果です。この2つのイベントの間に、Podは作成されていないことがわかりますが、Jobが再開されるとすぐにPodの作成も再開されました。

可変スケジューリング命令

FEATURE STATE: Kubernetes v1.23 [beta]

ほとんどの場合、並列Jobは、すべてのPodが同じゾーン、またはすべてのGPUモデルxかyのいずれかであるが、両方の混在ではない、などの制約付きで実行することが望ましいです。

suspendフィールドは、これらの機能を実現するための第一歩です。Suspendは、カスタムキューコントローラーがJobをいつ開始すべきかを決定することができます。しかし、Jobの一時停止が解除されると、カスタムキューコントローラーは、Job内のPodの実際の配置場所には影響を与えません。

この機能により、Jobが開始される前にスケジューリング命令を更新でき、カスタムキューコントローラーがPodの配置に影響を与えることができると同時に、実際のPodとノードの割り当てをkube-schedulerにオフロードすることができます。これは一時停止されたJobの中で、一度も一時停止解除されたことのないJobに対してのみ許可されます。

JobのPodテンプレートで更新可能なフィールドはnodeAffinity、nodeSelector、tolerations、labelsとannotationsです。

独自のPodセレクターを指定

Jobオブジェクトを作成する際には通常、.spec.selectorを指定しません。Jobが作成された際に、システムのデフォルトロジックは、他のJobと重ならないようなセレクターの値を選択し、このフィールドに追加します。

しかし、場合によっては、この自動設定されたセレクターをオーバーライドする必要があります。そのためには、Jobの.spec.selectorを指定します。

その際には十分な注意が必要です。そのJobの他のPodと重なったラベルセレクターを指定し、無関係のPodにマッチした場合、無関係のJobのPodが削除されたり、無関係のPodが完了されてもこのJobの完了数とカウントしたり、片方または両方のJobがPodの作成または完了までの実行を拒否する可能性があります。 一意でないセレクターを選択した場合、他のコントローラー(例えばReplicationController)や属しているPodが予測できない挙動をする可能性があります。Kubernetesは.spec.selectorを間違って設定しても止めることはしません。

下記はこの機能の使用例を紹介しています。

oldと名付けたJobがすでに実行されていると仮定します。既存のPodをそのまま実行し続けてほしい一方で、作成する残りのPodには別のテンプレートを使用し、そのJobには新しい名前を付けたいとしましょう。これらのフィールドは更新できないため、Jobを直接更新できません。そのため、kubectl delete jobs/old --cascade=orphanで、属しているPodが実行されたままoldJobを削除します。削除する前に、どのセレクターを使用しているかをメモしておきます:

kubectl get job old -o yaml

出力結果はこのようになります:

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

次に、newという名前で新しくJobを作成し、同じセレクターを明示的に指定します。既存のPodもcontroller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002ラベルが付いているので、同じくnewJobによってコントロールされます。

通常システムが自動的に生成するセレクターを使用しないため、新しいJobで manualSelector: trueを指定する必要があります。

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

新しいJobはa8f3d00d-c6d2-11e5-9f87-42010af00002ではなく、別のuidを持つことになります。manualSelector: trueを設定することで、自分は何をしているかを知っていて、またこのミスマッチを許容することをシステムに伝えます。

FinalizerによるJob追跡

FEATURE STATE: Kubernetes v1.23 [beta]

この機能が有効でない場合、Job Controllerはクラスター内に存在するPodを数えてJobステータスを追跡します。つまりsucceededPodとfailedPodのカウンターを保持します。 しかし、Podは以下のような理由で削除されることもあります:

  • Nodeがダウンしたときに、孤立した(Orphan)Podを削除するガベージコレクター。
  • 閾値に達すると、(SucceededまたはFailedフェーズで)終了したPodを削除するガベージコレクター。
  • Jobに属するPodの人為的な削除。
  • 外部コントローラー(Kubernetesの一部として提供されていない)によるPodの削除や置き換え。

クラスターでJobTrackingWithFinalizers機能を有効にすると、コントロールプレーンは任意のJobに属するPodを追跡し、そのようなPodがAPIサーバーから削除された場合に通知します。そのために、Jobコントローラーはbatch.kubernetes.io/job-trackingFinalizerを持つPodを作成します。コントローラーはPodがJobステータスに計上された後にのみFinalizerを削除し、他のコントローラーやユーザーによるPodの削除を可能にします。

Jobコントローラーは、新しいJobに対してのみ新しいアルゴリズムを使用します。この機能が有効になる前に作成されたJobは影響を受けません。JobコントローラーがPod FinalizerでJob追跡しているかどうかは、Jobがbatch.kubernetes.io/job-trackingというアノテーションを持っているかどうかで判断できます。 このアノテーションを手動で追加または削除してはいけません

代替案

単なるPod

Podが動作しているノードが再起動または故障した場合、Podは終了し、再起動されません。しかし、終了したPodを置き換えるため、Jobが新しいPodを作成します。このため、たとえアプリケーションが1つのPodしか必要としない場合でも、単なるPodではなくJobを使用することをお勧めします。

Replication Controller

JobはReplication Controllersを補完するものです。 Replication Controllerは、終了することが想定されていないPod(Webサーバーなど)を管理し、Jobは終了することが想定されているPod(バッチタスクなど)を管理します。

Podのライフサイクルで説明したように、JobRestartPolicyOnFailureNeverと設定されているPodにのみ適用されます。(注意:RestartPolicyが設定されていない場合、デフォルト値はAlwaysになります)

シングルJobによるコントローラーPodの起動

もう一つのパターンは、一つのJobが一つPodを作り、そのPodがカスタムコントローラーのような役割を果たし、他のPodを作ります。これは最も柔軟性がありますが、使い始めるにはやや複雑で、Kubernetesとの統合もあまりできません。

このパターンの一例としては、Sparkマスターコントローラーを起動し、sparkドライバーを実行してクリーンアップするスクリプトを実行するPodをJobで起動する(sparkの例を参照)が挙げられます。

この方法のメリットは、全処理過程でJobオブジェクトが完了する保証がありながらも、どのPodを作成し、どのように作業を割り当てるかを完全に制御できることです。

次の項目

最終更新 November 17, 2022 at 1:21 AM PST: [ja] Removing beta label, as indexed job is GA (8d97cb086b)