毎分実行するようなバッチをKubernetesで動かしたい
Kubernetesでは、Linuxのcronと同じように、時間を指定してJobを実行するCronJobという仕組みがあります。
メール送信や重い処理など非同期で行いたい場合、キューを使ってバックグラウンドプロセスに渡して処理をするのが一般的ですが、古いシステムの場合、cronで毎分バッチを実行させてこういった処理を行うことをやっている場合もあります。
今回は、そういった古いシステムをKubernetesに移行する場合のCronJobの設定について調べてみました。
ECインスタンスなどでバッチの突き抜けをしない工夫
"突き抜け"とは、バッチ処理で規定の時間内に処理が終了しないことを指します。
例えばデータ量が増えたため、夜間バッチが朝の業務時間までに完了しなかったために業務に支障が出たり、
処理が完了しないままに後続のバッチが実行されて不具合が発生したりすることです。
今回のように毎分実行する設定になっているバッチの場合、処理するデータ量が多すぎるなどの理由でバッチ処理が完了する前に次のバッチが実行されると二重に処理が行われてしまう可能性があります(メール処理の場合だと二重に送信されてしまうとか)
こういった処理の場合、多重起動されないようにバッチ処理側でロックファイルの処理を追加したり、 以下のようにflock
コマンドを使ったりして前の処理が実行中の場合は起動しないように設定することが多いかと思います。
$ crontab -l
* * * * * /usr/bin/flock -w 0 /tmp/batch.lock /usr/local/project/bin/batch.sh
ただ、これらの処理は同じインスタンス内で実行されるのが前提の対応方法で、KubernetesのJobは実行される度に新しいコンテナインスタンスが作られるため、上記のような方法を取ることができません。
KubernetesのCronJobについて
基本的にLinuxのcronと同じような仕組みなので、ドキュメントの記載もあるように以下のような制限があります。
cronジョブは一度のスケジュール実行につき、 /おおよそ/ 1つのジョブオブジェクトを作成します。ここで /おおよそ/ と言っているのは、ある状況下では2つのジョブが作成される、もしくは1つも作成されない場合があるためです。通常、このようなことが起こらないようになっていますが、完全に防ぐことはできません。したがって、ジョブは /冪等/ であるべきです。
https://kubernetes.io/ja/docs/concepts/workloads/controllers/cron-jobs/
本来は公式ドキュメントにも強調されているように、バッチ処理は何度実行されても結果が同じになるように冪等性を持つようにするべきなのですが、ロックファイルで多重起動されないようにすることで回避しているバッチの場合、KubernetesのCronJobで多重起動を回避する設定する必要がありそうです。
Kubernetes でバッチの同時実行を許可しない設定
まず、KubernetesのCronJob独特の特性として、100回以上スケジュールが失敗していると、以下のようなログが記録され、ジョブは開始されません。
Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
この 100回 を記録する時間範囲ですが、spec.startingDeadlineSeconds
で設定できます。デフォルトはnil
なので、累計100回以上失敗するとそのCronJobは再作成しない限り実行されなくなります。
また、spec.concurrencyPolicy
をForbid
に設定すると、前回のバッチが実行中の場合、新しいバッチは実行されません。
ただし、この場合も失敗とみなされるため、100回以上カウントされると停止してしまいます。
この場合は、spec. startingDeadlineSeconds
を適切に設定して回避します。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batch
spec:
concurrencyPolicy: Forbid
startingDeadlineSeconds: 12000
KubernetesのJobについて
古いシステムのバッチの場合、自前でロック処理を行なっていて、なんらかの理由で停止した場合に原因を調査してから手動でロックファイルを削除して再実行を行うような運用の場合があります。
KubernetesのJobでは、デフォルトの設定の場合、実行に失敗すると再実行が行われるので、この場合は実行に失敗すると素直に停止したままにしておく設定がよさそうです。
KubernetesのJobでバッチ実行が失敗した場合に再実行しない設定
KubernetesではCronJobがスケジュールされた時間になると定義されたJobを生成してバッチ実行されます。
JobのjobTemplate.spec.backoffLimit
は、実行に失敗すると指定回数だけ再実行を試みます。
デフォルトは6
なので、単純に再実行を行わないようにするには、この値を0
に設定するだけです。
また、Job が失敗した場合に再実行するかどうかは、jobTemplate.spec.restartPolicy
の設定にも依ります。
例えば、この値がOnFailure
の場合、実行に失敗すると再実行されます。
この際、back-off delay (10s, 20s, 40s,...) が加算され、最大5分まで再実行の開始を遅らせます。
また、10分間エラーが発生しなければ、この遅延実行時間はリセットされます。
もし、バッチの実行時間にタイムアウトの設定を入れたい場合は、jobTemplate.spec.activeDeadlineSeconds
を指定します。
実行開始から指定時間を過ぎると実行が停止され、エラーとして記録されます。