背景
障害対応では、初動対応としてまず動いているプロセスを停止させることがあります。
k8s の CronJob (が生成する Job) を停止対象とする場合のよい手順がわからなかったため、整理してみました。
基本の確認
ここでは以下の3つのオブジェクトと1つの実リソースが関与します。
- CronJob オブジェクトは、決まった時間に Job を作成することを指示するオブジェクトです。
- Job オブジェクトは、指定した処理の完遂を目標とするオブジェクトで、処理の並列性やリトライ条件をもとに Pod を作成・管理します。
- Pod オブジェクトは多様なワークフローの起点となるオブジェクトで、1つ以上のコンテナを単一のノードにスケジュールし状態を監視します。
- コンテナは Pod によって作成される実リソースで、 CRI ランタイム (containerd, CRI-O など) によって起動され、ホストカーネルの機能によって隔離されたユーザー空間上で実行されます。
実現したいこと
ここでは以下を実現したいです。
- 現在実行中の Job の処理を応急的に停止する。
- CronJob の今後の処理を応急的に停止する。
- オブジェクトやリソースはなるべく保全する。
注意点
応急停止を実現するにあたり、以下の注意点があります。
- kubectl delete を発行すると、当然ながら、オブジェクトは最終的に削除されてしまいます。 kubectl delete はオブジェクトの保全とは相性が悪いです。ただ、にっちもさっちもいかなくなったらとりあえず全部 delete すれば停止できる可能性は高いです。
- Job を suspend すると、その Job は running に準ずる扱いを受けます。あとで CronJob の実行を再開しようとしたとき、 concurrencyPolicy に引っかかって期待したように動作しない可能性があります。加えて、 Pod も削除されてしまうため、保全の目的を部分的にしか達成できません。
- Pod を孤児化する形で Job を削除 (--cascade=orphan) すると、 Pod の停止処理は行われません。単体では応急停止の効果がない点に注意が必要です。
- Job を削除すると、タイミング次第では CronJob によって Job が再作成されてしまうことがあります。
- Pod を削除または失敗させると、設定次第では Job によって Pod が再作成されてしまうことがあります。
- 応急停止は実行中の Job / Pod にミュータブルな操作をすることに他ならないため、どの変更が実際に反映されるかに注意が必要です。また、一般論としてはオブジェクトへの変更が親オブジェクトからの reconcilation で巻き戻されてしまわないかも重要ですが、 CronJob や Job はその仕組み上作成済みの子リソースにはそこまで関与しないので問題は起きにくいと思います。
また、以下ではジョブの完了数が1、並列数が1であることを仮定します。そうでない場合は並列で実行されている Pod のうちの1つしか保全されない可能性があります。
応急停止のフロー
以上の要求と注意点を踏まえた応急停止のフローは以下の通りです。
まず、 Job が Pod を再作成しないように、 Pod 側をいじる前に backoffLimit をゼロに絞っておきます。
kubectl patch job/my-cronjob-29736200 -p '{"spec":{"backoffLimit":0}}'次に、 Pod を強制的に Error にさせます。そのために activeDeadlineSeconds を短くします。
kubectl patch pod/my-cronjob-29736200-abcdef -p '{"spec":{"activeDeadlineSeconds":1}}'これにより Pod が強制的に Error になり、結果として Job が Failed になります。
Pod object は残り、 kubectl logs などの操作もできます。実際にやってみると、 SIGTERM の送信により graceful shutdown されていることが確認できました。
上記の2つの対応と並行して (順番は逆でもよい)、 CronJob を suspend します。これは次の実行でも問題が起きうる場合に実施します。
なお、 CronJob を Argo CD で管理している場合など、そのままでは変更が勝手に元に戻されてしまうこともあります。このような構成の場合は同期を一時的にオフにするか、設定の大元から変更するなどの対応が必要です。
kubectl patch cronjob/my-cronjob -p '{"spec":{"suspend":true}}'調査・対応が進んで、 CronJob を再開してもよくなったら、上記と逆の patch を当てるか Argo CD 等からデータを入れ直すなどして CronJob を再開します。
なお、ここで紹介している spec.backoffLimit (Job), spec.activeDeadlineSeconds (Pod), spec.suspend (CronJob) はいずれもミュータブルで、変更は実行中の Job / Pod の振舞いにも反映されます。
まとめ
CronJob の応急停止を確実に遂行しつつリソースの保全を行うには意外と工夫が必要でした。
ポイントは Job と Pod を Failed / Error 状態に持っていくことで、これによりなるべく後処理をしやすい形で Pod を停止させることができるようです。
そもそも障害が起こらないに越したことはないですが、障害対応で必要なときは参考にしてみてください。