コンテナこんにちは、インフラチームの菊池です。今までこのブログでも何度か関連する記事を投稿させていただいていますが、現在ピクスタでもコンテナプラットフォームの導入に日々勤しんでいます。その甲斐あって昨年末にPIXTAの主要なサービスをkubernetes(以下k8s)環境に移行できました。webサービスから移行したのですが、現在はサーバーのcronで実行されているバッチ処理についてもk8s環境への移行を進めています。今回はk8s環境において定期的にバッチ処理を実行するCronJobについて、実際に作業してみて分かったちょっと細かい仕様や工夫した事などについて書いてみたいと思います。(k8sやCronJob/Jobオブジェクトに関する基本的な説明は省略させていただいてます)本題に入るその前にはじめに簡単に現在作業を行なっている環境の説明をします。前提として、過去の記事でも度々触れていますが、弊社ではインフラプラットフォームとして主にAWSを利用しています。k8sクラスターまず、クラスターの構築にはEKSではなくRancher(執筆時点でv2.2.4とv2.3.2の2つ)を使っています。EKSのTokyoリージョン上陸以前に構築・運用を開始したという歴史的経緯があります。クラスターを構成する各ノードは全てEC2インスタンスで、Rancherを運用するクラスター自体もEC2インスタンスで構築しています。workerノードは基本的にAutoScalingGroupで管理し、主にSpotインスタンスを利用していますが、数台onDemandインスタンスも含めています。kubernetesは執筆時点でクラスターによりv1.15.5とv1.13.5の2バージョンを利用しています。k8s以前k8s移行前はOpsWorksを使って構築されたEC2インスタンスにてcronによる運用をしていました。ここから本題ここからが本題ですが、以下が今回お話するトピックになります。JobのノードスケジューリングについてスケジュールはUTC時間になるrestartPolicyの違いsupend指定とstartingDeadlineSecondsJobのノードスケジューリングについてこれはCronJobに関するというより、運用環境に関する話になります。前述の通り今回バッチ処理の移行に取り組んでいるクラスター環境では、workerノードは基本的にspotインスタンスである為中断が伴います。インスタンスの中断によるJobの中断は、このような環境での実行を想定していない古くに作られたバッチ処理にとっては許容し難いものがあります。onDemandインスタンスのみで構成するバッチ専用クラスターの構築も検討しましたが、コスト的に見合わないと考えwebサービスとバッチ処理を同一クラスターに共存させることとしました。そこで各バッチ処理については基本的にJobのnodeSelector設定によりonDemandインスタンスのworkerでの稼働を保証するような設定をしています。実行時間が1、2分程度と短く実行頻度が高い処理などについては、affinity設定によりOnDemandインスタンスでの稼働を優先しつつもSpotインタンスでも稼働できるようしています。現状この方法でクラスターの節約はできていますが、やはり最低限のonDemandワーカーを確保する為のサイジングが必要になってしまうのが若干残念です。スケジュールはUTC時間になるk8sのCronJobオブジェクトにおいてもJobの実行スケジュールはcrontabと同様に指定します。従来のcronの場合、スケジュールはそのサーバーに設定された時間帯で実行されますが、k8sのCronJobオブジェクトの場合は基本的にUTC時間になります。CronJobコンテナ内の時間帯、またはコンテナが実行されるworkerノードやmasterノードの時間帯に拘わらず、です。ドキュメントには以下のようにあります。時刻はジョブが開始されたマスタータイムゾーンに基づいています。https://kubernetes.io/ja/docs/concepts/workloads/controllers/cron-jobs/ここでいうマスタータイムゾーンが何を指すのか不明です。調べてみるとCronJobの時間帯を変更するにはcontroleplaneノードで稼働するkube-controller-managerの時間帯を変更する必要がありました。簡単には変更しづらいところです。移行前は日本時間でスケジュールを登録していましたが、UTCに変更しました。restartPolicyの違いJobの設定項目の1つであるrestartPolicyについては、以下のような説明をよく目にします。OnFailure:Podが再作成されないNever:Podが再作成されるこれに加えて、Jobがエラーとなった場合に以下のような違いがありました。OnFailure:Podが残らない(リトライ数MAXに達した場合)Never:Podが残るこれについてはドキュメントに小さく注意書きがありました。inadvertently.https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#pod-backoff-failure-policyrestartPolicyがOnFailureの場合、Jobは失敗時のリトライ数(backoffLimitの値、デフォルト:6)のMaxに到達するとTerminateされます、と。Terminateという説明で、Podが削除されるとは書かれていないのですが、restartPolicyがOnFaiureの場合エラー時のPodは削除され後からPodのログを確認できませんでした。注意書きにもある通り、別途ログを取得するような実装がない環境では、restartPolicyの設定に注意する必要があります。今回の作業環境ではログを取得する仕組みは実装していましたが、Rancherのコンソールからログが確認できる利便性を考え、restartPolicyをNeverに設定してエラー時のPodを残しています。suspend指定とstartingDeadlineSecondsについてCronJobの設定項目の1つにsuspendというものがあります。その名の通りこれがtrueに設定されているとJobはスケジュールされません。デフォルトはもちろんfalseです。弊社ではStaging環境や、確認環境と呼ばれる修正ブランチごとの環境においてこの項目をtrueに設定しています。環境についての詳細な説明は省きますが、これらの環境では通常Jobの実行が不要であり、稀にJobに修正が入った時などに実行できれば良い程度でした。これらの環境においては、開発者に必要に応じてsuspend設定を変更してJobを実行してもらうようにしています。suspendの設定を変更して停止していたJobをいざ実行しようとしても実行されないという問題が起こりました。前述のドキュメントにもある通り、CronJobは最後の実行時から現在までの失敗数をカウントし、100回以上失敗しているとJobは開始されなくなります。suspend状態でもこの失敗数がカウントアップされてしまうようでした。そこでstartingDeadlineSecondsという項目の設定を使ってこの問題を回避しています。startingDeadlineSecondsを設定すると、最後の実行時から現在までという判定基準がstartingDeadlineSecondsの値から現在までに代わります。startingDeadlineSecondsを3600などにすれば、失敗数の判定期間が過去1時間になり、100を超えるようなことはほぼありません。この設定により長期間停止していたJobも必要なタイミングで随時実行できるようになりました。まとめ以上、バッチ処理のk8s対応を行なった中で分かった、CronJobのちょっと細かい話を書かせていただきました。ここで紹介したトピックは主に弊社の環境に最適化するよう工夫してみた結果遭遇したような問題です。普通に運用していれば特に気にする必要もない問題かもしれませんが、何かのお役に立てれば幸いです。ピクスタでは、サービスを盛り上げていきたいデザイナー・エンジニアを募集しています。Passengerをやめましたこんにちは、開発部でインフラエンジニアをしているもりです。エンジニアアルバイトの高木です。響け!ユーフォニ...2017-12-24経営者視点から眺めるWebサービス開発環境の変遷こんにちは。"てくすた"ピクスタ株式会社のエンジニア・デザイナーがストックフォトサイトPIXTAの裏側と、テクニカルな話題についてよもやまに綴るブログです。(4)スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。