KubernetesではAPIサーバーやバッチ処理、イベント駆動型のタスクなど、さまざまなケースに合わせた「ワークロードリソース」の種類を選択し、柔軟に運用できます。 本記事では、弊チームのシステム設計の例をもとに、ワークロードリソースの中でも、ScaledJobとScaledObject + Deploymentの違いに注目して、使い分けにおける学びを共有できればと思います。
結論
イベント駆動型タスクであっても、実行間隔がPod起動にかかる時間より短いのであればScaledJobではなくScaledObject + Deploymentを選定するのが良いです。
設計の背景
ユーザーが行った通話の情報(通話時間、対応者、発信か着信か、不在かなど)を整えてデータベースに保存する仕組みを新たに構築することになりました。このデータは、サービスのさらなる利便性向上のために活用されるものです。
しかし、既存のシステムから最新の情報をスピーディーに連携することが難しく、既存の仕組みのみでは「最新情報を遅延2分以内に取得したい」という計画当初の要件を満たすことができませんでした。また、長期間のデータ集計の際に、レスポンス遅延が発生する懸念がありました。そこで、新たな仕組みを導入することになりました。
この通話情報は1000件/分以上の頻度で送られてきても処理できる必要があります。 ただし遅延は減らしたいものの、リアルタイム反映が求められるシーンとは利用シーンの切り分けができました。そこで将来的に通話情報を複数形式で保存する可能性を見越し、AWSのSNS+SQSのファンアウト形式を採用しました。この形式を取ることで、情報を柔軟に拡張し、新たな処理を追加することになっても影響を最小限に抑えることができます。
また、これにより通話情報を送信する側の通話履歴管理システムと、通話情報を利用する側の連絡先システムとで、関心ごとの分離を明確にできました。それぞれのシステムを運用している別々のチームが独立してシステムの改善や保守を進めやすい構成にできた点も大きなメリットです。
初期選択:ScaledJobの活用と課題
「イベント駆動型タスクの処理になる」と考えたことから、最初にScaledJobを選択しました。ScaledJobは、SQSをトリガーにしてジョブをスケーリングできることから最適に思えたためです。さらに、処理時間が短いタスクなので、Podの台数を増やすことで大量のSQSであってもさばけると考えました。
しかしScaledJobではトリガーごとに新しいPodを立ち上げる必要があります。つまりPod起動時間がかかります。メインとなる処理時間は短時間だったとしても、Pod起動の時間に数分かかるとすると、起動中にも次々やってくる1000件/分のSQSを処理するためには結果的に同時に何千ものPodを立ち上げていないといけないことになります。これでは、求めるスピードや効率に達することが難しいと判断し、別のアプローチを検討することになりました。
方向転換:ScaledObject + Deploymentの選択
次に選んだのは、ScaledObject + Deploymentを用いる方法です。ScaledObjectでスケーリングをして、DeploymentのPod内で、SQSのメッセージを定期的にポーリングし続け、ScaledObjectのスケーリングとは別に内部でも並列で処理する形で実装しました。これにより、Podの立ち上げ時間の無駄を削減し、大幅に効率を改善することができました。
Rolloutを選ばずDeploymentを採用した理由は、ジョブ処理ではサービスを維持しながら安全にデプロイする必要がないためです。APIのように即時性を求められるものではなく、メッセージがSQSに溜まる設計であるため、サービスを一時停止しても問題ないからです。 また、Rollout は ArgoCD によるワークロードリソースなので、特に理由がなければKubernetesの標準機能であるDeploymentを選定し、シンプルに扱えるようにしたいと思いました。
ScaledObject + Deploymentを選択したことで、最新情報の取得速度は大幅に改善され、当初目指していた以上に遅延縮小することができています。また、Deploymentの利用により、必要なPodの台数を削減し、リソース効率も向上しました。
manifestの例
今回の構成のmanifestでの定義の例と、チューニングのポイントです。
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: terminationGracePeriodSeconds: 300 containers: - name: sample image: DOCKER_IMAGE command: ["echo hoge"] --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: sample-scaledobject spec: scaleTargetRef: name: sample-deployment pollingInterval: 60 minReplicaCount: 1 maxReplicaCount: 16 triggers: - type: aws-sqs-queue
- Deployment
- terminationGracePeriodSeconds
- scale in発生時に処理途中で終わったSQS messageに関してはpopしたmessageをdeleteせずに終わる可能性がある
- メイン処理に必要な時間を考慮した上で適切に設定する必要あり
- 余裕をもたせると良い
- terminationGracePeriodSeconds
- ScaledObject
- maxReplicaCount
- 一番SQSが多い時間帯に十分なPodが起動できるように設定
- pollingInterval
- scale in/outが発生する頻度の調整をする
- 急激なSQS量の増減に備えるため
- maxReplicaCount
学びと今後の展望
Kubernetesは学習コストが高いと言われますが、実際に使う中で得た知識や工夫を共有し合うことで理解を深めることが重要だと感じています。また、今回のように要件に応じて最適なアプローチを模索するプロセスは非常に有意義でした。
今後は、さらにKubernetesの知識を深め、最新バージョンの機能も活用することで、よりシンプルで効率的な設計・運用を目指していきたいです。
この記事が、Kubernetesの運用に興味を持っている方々の参考になれば幸いです。