1 - CronJobを使用して自動化タスクを実行する

CronJobは、Kubernetes v1.21で一般利用(GA)に昇格しました。古いバージョンのKubernetesを使用している場合、正確な情報を参照できるように、使用しているバージョンのKubernetesのドキュメントを参照してください。古いKubernetesのバージョンでは、batch/v1 CronJob APIはサポートされていません。

CronJobを使用すると、Jobを時間ベースのスケジュールで実行できるようになります。この自動化されたJobは、LinuxまたはUNIXシステム上のCronのように実行されます。

CronJobは、バックアップやメールの送信など、定期的なタスクや繰り返しのタスクを作成する時に便利です。CronJobはそれぞれのタスクを、たとえばアクティビティが少ない期間など、特定の時間にスケジューリングすることもできます。

CronJobには制限と特性があります。たとえば、特定の状況下では、1つのCronJobが複数のJobを作成する可能性があるため、Jobは冪等性を持つようにしなければいけません。

制限に関する詳しい情報については、CronJobを参照してください。

始める前に

  • Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

CronJobを作成する

CronJobには設定ファイルが必要です。次の例のCronJobの.specは、現在の時刻とhelloというメッセージを1分ごとに表示します。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

次のコマンドで例のCronJobを実行します。

kubectl create -f https://k8s.io/examples/application/job/cronjob.yaml

出力は次のようになります。

cronjob.batch/hello created

CronJobを作成したら、次のコマンドで状態を取得します。

kubectl get cronjob hello

出力は次のようになります。

NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        <none>          10s

コマンドの結果からわかるように、CronJobはまだスケジュールされておらず、まだ何のJobも実行していません。約1分以内にJobが作成されるのを見てみましょう。

kubectl get jobs --watch

出力は次のようになります。

NAME               COMPLETIONS   DURATION   AGE
hello-4111706356   0/1                      0s
hello-4111706356   0/1           0s         0s
hello-4111706356   1/1           5s         5s

"hello"CronJobによってスケジュールされたJobが1つ実行中になっていることがわかります。Jobを見るのをやめて、再度CronJobを表示して、Jobがスケジュールされたことを確認してみます。

kubectl get cronjob hello

出力は次のようになります。

NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        50s             75s

CronJobhelloが、LAST SCHEDULEで指定された時間にJobを正しくスケジュールしたことが確認できるはずです。現在、activeなJobの数は0です。つまり、Jobは完了または失敗したことがわかります。

それでは、最後にスケジュールされたJobの作成と、Podの1つの標準出力を表示してみましょう。

# "hello-4111706356" の部分は、あなたのシステム上のJobの名前に置き換えてください。
pods=$(kubectl get pods --selector=job-name=hello-4111706356 --output=jsonpath={.items[*].metadata.name})

Podのログを表示します。

kubectl logs $pods

出力は次のようになります。

Fri Feb 22 11:02:09 UTC 2019
Hello from the Kubernetes cluster

CronJobの削除

CronJobが必要なくなったときは、kubectl delete cronjob <cronjob name>で削除します。

kubectl delete cronjob hello

CronJobを削除すると、すべてのJobと、そのJobが作成したPodが削除され、追加のJobの作成が停止されます。Jobの削除について詳しく知りたい場合は、ガベージコレクションを読んでください。

CronJobのspecを書く

すべてのKubernetesの設定と同じように、CronJobにもapiVersionkindmetadataのフィールドが必要です。設定ファイルの扱い方についての一般的な情報については、アプリケーションのデプロイkubectlを使用してリソースを管理するを読んでください。

CronJobの設定には、.specセクションも必要です。

Schedule

.spec.scheduleは、.specには必須のフィールドです。0 * * * *@hourlyなどのCron形式の文字列を取り、Jobの作成と実行のスケジュール時間を指定します。

フォーマットにはVixie cronのステップ値(step value)も指定できます。FreeBSDのマニュアルでは次のように説明されています。

ステップ値は範囲指定と組み合わせて使用できます。範囲の後ろに/<number>を付けると、範囲全体で指定したnumberの値ごとにスキップすることを意味します。たとえば、0-23/2をhoursフィールドに指定すると、2時間毎にコマンド実行を指定することになります(V7標準では代わりに0,2,4,6,8,10,12,14,16,18,20,22と指定する必要があります)。ステップはアスタリスクの後ろにつけることもできます。そのため、「2時間毎に実行」したい場合は、単純に*/2と指定できます。

Job Template

.spec.jobTemplateはJobのテンプレートであり、必須です。Jobと完全に同一のスキーマを持ちますが、フィールドがネストされている点と、apiVersionkindが存在しない点だけが異なります。Jobの.specを書くための情報については、JobのSpecを書くを参照してください。

Starting Deadline

.spec.startingDeadlineSecondsフィールドはオプションです。何かの理由でスケジュールに間に合わなかった場合に適用される、Jobの開始のデッドライン(締め切り)を秒数で指定します。デッドラインを過ぎると、CronJobはJobを開始しません。この場合にデッドラインに間に合わなかったJobは、失敗したJobとしてカウントされます。もしこのフィールドが指定されなかった場合、Jobはデッドラインを持ちません。

.spec.startingDeadlineSecondsフィールドがnull以外に設定された場合、CronJobコントローラーはJobの作成が期待される時間と現在時刻との間の時間を計測します。もしその差が制限よりも大きかった場合、その実行はスキップされます。

たとえば、この値が200に設定された場合、実際のスケジュールの最大200秒後までに作成されるJobだけが許可されます。

Concurrency Policy

.spec.concurrencyPolicyフィールドもオプションです。このフィールドは、このCronJobで作成されたJobの並列実行をどのように扱うかを指定します。specには以下のconcurrency policyのいずれかを指定します。

  • Allow (デフォルト): CronJobがJobを並列に実行することを許可します。
  • Forbid: CronJobの並列実行を禁止します。もし新しいJobの実行時に過去のJobがまだ完了していなかった場合、CronJobは新しいJobの実行をスキップします。
  • Replace: もし新しいJobの実行の時間になっても過去のJobの実行が完了していなかった場合、CronJobは現在の実行中のJobを新しいJobで置換します。

concurrency policyは、同じCronJobが作成したJobにのみ適用されます。もし複数のCronJobがある場合、それぞれのJobの並列実行は常に許可されます。

Suspend

.spec.suspendフィールドもオプションです。このフィールドをtrueに設定すると、すべての後続の実行がサスペンド(一時停止)されます。この設定はすでに実行開始したJobには適用されません。デフォルトはfalseです。

Job History Limit

.spec.successfulJobsHistoryLimit.spec.failedJobsHistoryLimitフィールドはオプションです。これらのフィールドには、完了したJobと失敗したJobをいくつ保持するかを指定します。デフォルトでは、それぞれ3と1に設定されます。リミットを0に設定すると、対応する種類のJobを実行完了後に何も保持しなくなります。

2 - 静的な処理の割り当てを使用した並列処理のためのインデックス付きJob

FEATURE STATE: Kubernetes v1.21 [alpha]

この例では、複数の並列ワーカープロセスを使用するKubernetesのJobを実行します。各ワーカーは、それぞれが自分のPod内で実行される異なるコンテナです。Podはコントロールプレーンが自動的に設定するインデックス値を持ち、この値を利用することで、各Podは処理するタスク全体のどの部分を処理するのかを特定できます。

Podのインデックスは、アノテーション内のbatch.kubernetes.io/job-completion-indexを整数値の文字列表現として利用できます。コンテナ化されたタスクプロセスがこのインデックスを取得できるようにするために、このアノテーションの値はdownward APIの仕組みを利用することで公開できます。利便性のために、コントロールプレーンは自動的にdownward APIを設定して、JOB_COMPLETION_INDEX環境変数にインデックスを公開します。

以下に、この例で実行するステップの概要を示します。

  1. completionのインデックスを使用してJobのマニフェストを定義する。downward APIはPodのインデックスのアノテーションを環境変数またはファイルとしてコンテナに渡してくれます。
  2. そのマニフェストに基づいてインデックス付き(Indexed)のJobを開始する

始める前に

あらかじめ基本的な非並列のJobの使用に慣れている必要があります。

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

作業するKubernetesサーバーは次のバージョン以降のものである必要があります: v1.21. バージョンを確認するには次のコマンドを実行してください: kubectl version.

インデックス付きJobを作成できるようにするには、APIサーバーコントローラーマネージャー上でIndexedJobフィーチャーゲートを有効にしていることを確認してください。

アプローチを選択する

ワーカープログラムから処理アイテムにアクセスするには、いくつかの選択肢があります。

  1. JOB_COMPLETION_INDEX環境変数を読み込む。Jobコントローラーは、この変数をcompletion indexを含むアノテーションに自動的にリンクします。
  2. completion indexを含むファイルを読み込む。
  3. プログラムを修正できない場合、プログラムをスクリプトでラップし、上のいずれかの方法でインデックスを読み取り、プログラムが入力として使用できるものに変換する。

この例では、3番目のオプションを選択肢して、revユーティリティを実行したいと考えているとしましょう。このプログラムはファイルを引数として受け取り、内容を逆さまに表示します。

rev data.txt

revツールはbusyboxコンテナイメージから利用できます。

これは単なる例であるため、各Podはごく簡単な処理(短い文字列を逆にする)をするだけです。現実のワークロードでは、たとえば、シーンデータをもとに60秒の動画を生成するというようなタスクを記述したJobを作成するかもしれません。ビデオレンダリングJobの各処理アイテムは、ビデオクリップの特定のフレームのレンダリングを行うものになるでしょう。その場合、インデックス付きの完了が意味するのは、クリップの最初からフレームをカウントすることで、Job内の各Podがレンダリングと公開をするのがどのフレームであるかがわかるということです。

インデックス付きJobを定義する

以下は、completion modeとしてIndexedを使用するJobのマニフェストの例です。

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      initContainers:
      - name: 'input'
        image: 'docker.io/library/bash'
        command:
        - "bash"
        - "-c"
        - |
          items=(foo bar baz qux xyz)
          echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt          
        volumeMounts:
        - mountPath: /input
          name: input
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        emptyDir: {}

上記の例では、Jobコントローラーがすべてのコンテナに設定する組み込みのJOB_COMPLETION_INDEX環境変数を使っています。initコンテナがインデックスを静的な値にマッピングし、その値をファイルに書き込み、ファイルをemptyDir volumeを介してワーカーを実行しているコンテナと共有します。オプションとして、インデックスとコンテナに公開するためにdownward APIを使用して独自の環境変数を定義することもできます。環境変数やファイルとして設定したConfigMapから値のリストを読み込むという選択肢もあります。

他には、以下の例のように、直接downward APIを使用してアノテーションの値をボリュームファイルとして渡すこともできます。

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        downwardAPI:
          items:
          - path: "data.txt"
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

Jobを実行する

次のコマンドでJobを実行します。

# このコマンドでは1番目のアプローチを使っています ($JOB_COMPLETION_INDEX に依存しています)
kubectl apply -f https://kubernetes.io/examples/application/job/indexed-job.yaml

このJobを作成したら、コントロールプレーンは指定した各インデックスごとに一連のPodを作成します。.spec.parallelismの値が同時に実行できるPodの数を決定し、.spec.completionsの値がJobが作成するPodの合計数を決定します。

.spec.parallelism.spec.completionsより小さいため、コントロールプレーンは別のPodを開始する前に最初のPodの一部が完了するまで待機します。

Jobを作成したら、少し待ってから進行状況を確認します。

kubectl describe jobs/indexed-job

出力は次のようになります。

Name:              indexed-job
Namespace:         default
Selector:          controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
Labels:            controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
                   job-name=indexed-job
Annotations:       <none>
Parallelism:       3
Completions:       5
Start Time:        Thu, 11 Mar 2021 15:47:34 +0000
Pods Statuses:     2 Running / 3 Succeeded / 0 Failed
Completed Indexes: 0-2
Pod Template:
  Labels:  controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
           job-name=indexed-job
  Init Containers:
   input:
    Image:      docker.io/library/bash
    Port:       <none>
    Host Port:  <none>
    Command:
      bash
      -c
      items=(foo bar baz qux xyz)
      echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt

    Environment:  <none>
    Mounts:
      /input from input (rw)
  Containers:
   worker:
    Image:      docker.io/library/busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      rev
      /input/data.txt
    Environment:  <none>
    Mounts:
      /input from input (rw)
  Volumes:
   input:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-njkjj
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-9kd4h
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-qjwsz
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-fdhq5
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-ncslj

この例では、各インデックスごとにカスタムの値を使用してJobを実行します。次のコマンドでPodの1つの出力を確認できます。

kubectl logs indexed-job-fdhq5 # これを対象のJobのPodの名前に一致するように変更してください。

出力は次のようになります。

xuq