はじめに
CronJobを一時的に止めておきたかったので .spec.suspend
を使ってみたのですが、自分的に予想外の動きをしてしまったので、対処方法をまとめておきます。
この .spec.suspend
の注意点を見逃すと、CronJobが一時的に停止した状態からJobを生成できなくなります。
対処方法までまとめましたので、是非最後まで読んでいただければと思います。
本記事は、 1.22.12-gke.300
で動作確認しました。
やりたいこと
1分ごとに Hello! と言ってくれるCronJobを作成しました。社交的なCronJobですね。
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の実行を、ある期間だけ止めておきたい。
深夜の寝ている時間はうるさいから止めておきたい、といった感じです。
なので、次のコマンドのように .spec.suspend
を TRUE にして止めておいて、
kubectl patch cronjob hello -p '{ "spec": { "suspend": true } }'
動かしたいタイミングで、FALSE にしてまた動かそうと考えました。
kubectl patch cronjob hello -p '{ "spec": { "suspend": false } }'
.spec.suspend
を TRUE に指定したことで何が起きたか
冒頭にお話した通り、CronJobがJobを生成してくれなくなりました。
流れ
- 18:00, CronJobを作成。コマンドが実行される。
- 21:00, 就寝するため、
.spec.suspend
を TRUE に設定。Jobが作成されなくなる。 - 5:00, 起床。
.spec.suspend
を FALSE に設定。しかしCronJobがJobを生成してくれない。
期待して起きたのに、Hello!と言ってもらえないのは悲しいですね。
なぜ問題が起きたか
.spec.suspend
を TRUE にすると内部で何が起きているのか、公式ドキュメントに記載されていました。
注意: スケジュールされた時間中にサスペンドされた実行は、見逃されたJob(missed job)としてカウントされます。starting deadlineが設定されていない既存のCronJob.spec.suspendがtrueからfalseに変更されると、見逃されたJobは即座にスケジュールされます。
https://kubernetes.io/ja/docs/tasks/job/automated-tasks-with-cron-jobs/#starting-deadline
つまり、停止中に実行されるはずだったJobは、失敗としてカウントされるようです。そして、CronJobは、生成するJobが100回連続して失敗すると、Jobを作成できなくなります。
公式ドキュメントに記載されていました。
最後にスケジュールされた時刻から現在までの間に、CronJobコントローラーはどれだけスケジュールが間に合わなかったのかをCronJobごとにチェックします。もし、100回以上スケジュールが失敗していると、ジョブは開始されずに、ログにエラーが記録されます。
https://kubernetes.io/ja/docs/concepts/workloads/controllers/cron-jobs/#cron-job-limitations
なぜ100回なのか、これはソースコードのコメントに記載されていました。
https://github.com/kubernetes/kubernetes/blob/release-1.25/pkg/controller/cronjob/utils.go#L96-L112
訳してみると、
金曜日の午後5時1分、皆が帰宅した時にコントローラが動かなくなった場合、火曜日の午前中に誰かが来て問題を発見し、コントローラを再起動すると、1時間毎のcronJobの80以上のジョブが、それ以上の介入なしに全て動き出すはずです。80個よりは多く、「たくさん」よりは少ないということで、多少恣意的に100個を選んでみた。
例えば30分に1回のように、毎時より高頻度で実行する場合や、3連休以上の休暇があった場合は、CronJobはJobを作成できなくなります。 100回 というのはハードコーディングされているため、「設定で変更しよう」といったことは出来なさそうです。
今回の場合は、
停止時間(8h) * 実行間隔(60/h) = 480回
なので、480回失敗としてカウントされていたことになります。
対応したこと
素直に、CronJobを削除、再度作成するほうがよさそうです。
しかし、その他の制約上どうしても .spec.suspend
を使いたい(CronJobを削除したくない)場合もあります。
そんなときは、 .spec.schedule
を編集することでこの問題を回避することができます。実行頻度を減らして、停止中の失敗回数を減らす作戦です。
例えば、 毎分実行 の場合100分で上限に達してしまいますが、毎時実行 に変更すれば100時間未満まで停止できることになります。
なので、次のコマンドのように .spec.suspend
を TRUE にする前に、 .spec.schedule
を毎時0分実行に編集します。
kubectl patch cronjob hello -p '{ "spec": { "schedule": "0 * * * *" } }'
kubectl patch cronjob hello -p '{ "spec": { "suspend": true } }'
そして、FALSE に戻す前に、次のコマンドで.spec.schedule
を戻してまた動かします。
kubectl patch cronjob hello -p '{ "spec": { "schedule": "* * * * *" } }'
kubectl patch cronjob hello -p '{ "spec": { "suspend": false } }'
これで、ひとまずCronJobがJobを作成してくれるようになりました。
さいごに
停止中に実行できなかったJobを生成する方法もあります。 .spec.startingDeadlineSeconds
を指定するやり方です。
こちらの記事が参考になると思います。
参考