mercariengineering
人間によるKubernetesリソース最適化の”諦め”とそこに見るリクガメの可能性

Platformチームでエンジニアをしているsanposhihoです。メルカリのPlatformチームでオートスケーリング周りの課題の解決を担当しており、Kubernetes UpstreamでもSchedulingやAutoscaling周りの開発に参加しています。

メルカリでは全社的にFinOpsに取り組んでおり、Kubernetesリソースは最適化の余地があるエリアです。
メルカリではPlatformチームとサービスの開発チームで明確に責務が分かれています。Platformではサービス構築に必要な基礎的なインフラストラクチャを管理し、それらを簡単に扱うための抽象化された設定やツールなどの提供を行っています。サービスの開発チームは、それらを通してサービスごとの要件に応じたインフラストラクチャの構築を行います。
サービスやチームの数も多く、そのような状況での全社的なKubernetesリソースの最適化には多くの課題がありました。

この記事ではメルカリにおいて、これまでPlatformが行ってきたKubernetesリソースの最適化の取り組みと、その取り組みの課題から生まれたTortoiseと呼ばれるオープンソースのツールの紹介をします。

これまでの Kubernetes リソースの最適化の取り組み

Kubernetesリソースの最適化は以下の2つに分解することができます。

  • Podレベルの最適化: サービスの信頼性を損なわない範囲で、1Podあたりのリソース割り当て量やPod数を調節し、サービス全体で見た時の割り当てられるリソースの量を減らす。
  • Nodeレベルの最適化: 各Podから割り当て要求されたリソースをできるだけ安いコストで動作させる。

後者に関しては、PlatformがKubernetesクラスターレベルの設定を変更することで最適化をできる部分が大きく、クラスター全体のスケジューリングの調節(bin packing)や価格の安いインスタンス(spot instance)への移行などが手法として存在します。直近のメルカリにおける施策だと、Instance TypeのT2Dへの変更もありました。
対して前者のPodレベルの最適化では、サービスごとのリソースの使用の仕方の特性に応じて、Resource Request/Limitを変更したり、オートスケーラーの設定を調整する必要があります。

リソース最適化には、サービスの信頼性を損なうことなく、リソースの使用を効率化することが求められ、そのように安全な最適化を行うためにはしばしばKubernetesに関わる深い知識が必要です。

他方、メルカリではマイクロサービスのアーキテクチャーを採用していることもあり、1000以上のDeploymentが存在し、マイクロサービスごとに開発チームも独立して存在しています。

このような状況で個々のサービスの開発者にKubernetesの深い知識を要求するのは難しく、その一方でPlatformが各サービスごとに最適化して回るには限界があります。

そのため、Platformチームがツールの提供やガイドラインの策定を行い最適化をできるだけ簡略化し、それぞれのサービスの開発チームはそれらに沿って最適化を行う、という形を取り全社的なKubernetesリソースの最適化を推進してきました。

メルカリにおけるオートスケーラーの現状

Kubernetesが公式に提供しているオートスケーラーには以下の二つが存在します。

  • Horizontal Pod Autoscaler(HPA): Podのリソース使用量に応じて、Podの数を増減する。
  • Vertical Pod Autoscaler(VPA): Podのリソース使用量に応じて、Podが使用できるリソース量を増減する。

メルカリではHPAがかなり普及しており、ある程度の規模を持ったDeploymentはほぼ全てHPAで管理されています。対して、VPAに関してはほとんど使用されていません。HPAはCPUに対してのみ設定されていることが多く、Memoryは手動で管理されているケースがほとんどです。

記事の理解が進みやすいように、HPAの設定についてのみ軽く紹介します。
HPAではそれぞれのコンテナのそれぞれのリソースに対して、理想のリソース使用率(閾値)を設定することができます。以下の例では、applicationという名前のコンテナのCPUに対して、理想の使用率を60%と定義しており、HPAはPodの数をリソース使用率が60%に近くなるように調整します。

apiVersion: autoscaling/v2 
kind: HorizontalPodAutoscaler
metadata:
  name: <HPA_NAME>
  namespace: <NAMESPACE_NAME>
//…
metrics:
  type: ContainerResource
  containerResource:
    name: cpu
    container: application
    target:
      type: Utilization
      averageUtilization: 60

その他、minReplicasと呼ばれる、Podの最低数を決めるパラメータなど、多くの補助的なパラメータが存在します。より詳細な内容は公式のドキュメントを参照してください。

Resource Recommender Slack Bot

リソースの最適化に対して、メルカリのPlatformが内部で独自に提供している代表的なツールがResource Recommenderと呼ばれるものです。これはSlack Botで月に一度最適なリソースのサイズ (Resource Request) を計算し、サービス開発チームにお知らせします。これによりリソースの最適化を簡略化することを目的にしています。

Recommendation from the recommender

内部的には前述のVPAを使用しており、過去数ヶ月のVPAの推奨値から最適で安全な値を算出しています。

ただ、このResource Recommenderにはいくつかの課題点がありました。

まずは、推奨値の安全性です。推奨値は本来送られた瞬間が賞味期限で、時間が経つほど推奨値の正確性は薄れていきます。アプリケーションの実装の変更やトラフィックのパターンの変化によって、推奨値が大きく変わる可能性もあり、OOMKilledなどの危険な状況につながる危険性がありました。

そして、サービス開発者がこれらの推奨値を適応してくれるとは限らない点です。前述の危険性の観点から、開発者は推奨値を適応する前にその推奨値が安全か、適応後に何も問題が起こっていないかを注意深く確認する必要があり、エンジニアの時間を少なからず取ってしまうことになります。また、例えばメモリを3 GBから1 GBに減らすように推奨値が送られてきた場合、段階的に2GBを適応する、といったケースもあり、単純に推奨値がどれほど役に立っているのかの計測が難しいという観点もありました。

最後に、最適化はサービスが動き続ける限り終わらない点です。前述のように様々な状況の変化により、推奨される値というのは変化し続けます。開発者は一度Resource Recommenderに即してResource Requestを調整したら最適化が終了するのではなく、定期的に調整し続ける必要があります。

HPA の最適化

上記のResource Recommenderの課題とは別に、大きな問題点となっているのがHPAの最適化です。
HPAに管理されているリソースに関しては、基本的にリソースのサイズではなく、HPAの設定を最適化する必要があります。しかし、Resource RecommenderはHPAの設定の推奨値の算出に対応していません。
前述のように、メルカリでは規模の大きなサービスはほぼHPAを持っており、CPUをターゲットにしていることから、クラスターで使用されているCPUのほとんどはResource Recommenderによって最適化できないことを意味しています。

まず、最適化のためにはHPAに設定している理想のリソース使用率(閾値)をサービスの信頼性を損なわない範囲で上げる必要があります。
また、設定された閾値が十分に高いとしても、実際のリソース使用率が閾値に達していないというシナリオは多く存在し、その場合閾値以外のパラメータやResource Requestなどを調節する必要が出てきます。

HPAの最適化はかなり奥が深く、別でもう一本記事がかけるくらいにはかなりの知識を要します。(このスライドではHPAの最適化について難しさと考慮すべきシナリオが軽く説明されています。興味のある方は確認してみてください。)
その複雑性からResource Recommenderに単純に組み込むことは難しく、とはいえ膨大な数のHPAに対して多くのチームに定期的に手動の最適化を行い続けてもらう、というのは現実的ではありません。

…ここまで辿り着いて私たちは気がつきました。「…無理じゃね?」と。

現状のHPAとResource Recommenderの構成では、クラスターを最適化された状態に維持するにはどうしても手動で複雑な作業が全てのチームで定期的に、そして永遠に必要になります。

Tortoiseを用いたリソース最適化

そこで開発されたのが、Tortoiseです。(Tortoise: 日本語でリクガメの意味です)

Tortoise

このTortoiseは可愛いだけではなく、Kubernetesのリソース管理と最適化を全て自動で行なってくれるように訓練されています。

Tortoiseは過去のリソースの使用量や過去のレプリカの数を記録しており、それを元にHPAやResource Request/Limitを最適化し続けます。詳しいリコメンデーションのロジックが知りたい方は、公開されているドキュメントを参照してみてください。Tortoiseが単なるHPAやVPAのラッパーではないことが理解できると思います。

前述のようにこれまでサービスの開発チームがリソース/HPAの設定や最適化を行なっていましたが、Tortoiseはそれらの責務をサービスの開発チームからPlatformチームに完全に移すことを意図しています。サービス開発チームはTortoiseを一度セットアップすることでリソースの管理のことを完全に忘れることができ、もしTortoiseによって十分に最適化されていないマイクロサービスがあればPlatformがTortoiseの改善を行います。
Platformでは、メルカリの全てのPodをTortoiseによって最終的に管理することを目標にしています。

ユーザーは以下のようにCRDを通して、Tortoiseを設定します。

apiVersion: autoscaling.mercari.com/v1beta3
kind: Tortoise
metadata:
  name: lovely-tortoise
  namespace: zoo
spec:
  updateMode: Auto 
  targetRefs:
    scaleTargetRef:
      kind: Deployment
      name: sample

Tortoiseは非常にシンプルなユーザーインターフェースにデザインされており、ほとんどのサービスに対する設定は上記で完了します。その後、Tortoiseは自動でHPAやVPAなどの必要なものを作成し、オートスケールを開始します。

HPAは複数のパラメーターがユーザーに対して公開されています。これはユーザーに対して柔軟な設定を可能にする一方、現状のメルカリのように、HPAの設定やResource Requestを改善しないとHPAが本来のパワーを発揮できない、という状況に繋がり得ます。
メルカリでは運の良いことに、ほとんどのマイクロサービスがGoで書かれており、gRPC/HTTP サーバーであり、内部で公開されているマイクロサービスのテンプレートをベースに作成されています。そのため、HPAの設定もほとんどのサービスで非常に似ており、サービスのリソース使用量の変化やレプリカ数の変化などの特性も非常に似ています。
そのため、HPAの複数のパラメーターをTortoiseの背後に隠し、Tortoise側で共通のデフォルト値を与え、内部のリコメンデーションのロジックを通してそこから最適化をし続ける、というのがうまく働いています。

また、シンプルなユーザーインターフェース(CRD)とは打って変わり、Tortoiseはクラスター管理者向けの多くの設定を備えています。
これによって、そのクラスターにおけるサービスの振る舞いを元に、クラスター管理者が全てのTortoiseの挙動を管理するということが可能になっています。

Tortoiseへの安全な移行と検証

前述のようにTortoiseはHPAやVPAの代替となるツールです。Tortoiseを作成することでHPAは必要がなくなる一方で、前述のようにMercariには非常に多くの数のDeploymentがHPAと共にすでに動作しています。
この状況でHPAからTortoiseに移行するには、Tortoiseの作成からHPAの削除など、煩雑なリソース操作を安全に行う必要がありました。

そのような移行をできるだけ簡略化し安全な移行を確保するために、Tortoiseには「既存のHPAをTortoiseに管理させる」ための機能が実装されています。

apiVersion: autoscaling.mercari.com/v1beta3
kind: Tortoise
metadata:
  name: lovely-tortoise
  namespace: zoo
spec:
  updateMode: Auto 
  targetRefs:
    # 既存のHPAを指定することで、Tortoiseは新たなHPAを作成する代わりに、このHPAを最適化し続ける。
    horizontalPodAutoscalerName: existing-hpa 
    scaleTargetRef:
      kind: Deployment
      name: sample

horizontalPodAutoscalerNameを使用することで、既存のHPAをTortoise-managedなHPAにシームレスに移行することができ、移行のコストを下げています。

現在私たちはメルカリの開発環境で複数のサービスをTortoiseに移行して、安全性の検証を行っています。TortoiseはDryRunを行うためのupdateMode: Offを備えており、Tortoise Controllerから公開されているメトリクスを通して、推奨値の妥当性を検証することができます。

開発環境では、かなり多くの数のサービスですでにOffモードのTortoiseによる検証が始まっており、50ほどのサービスではすでにTortoiseを用いたオートスケーリングが使用され始めています。
本番環境での検証、そしてTortoiseへの移行も近い将来に計画されており、Tortoiseはより洗練されたツールとなっていくことでしょう。

まとめ

この記事ではメルカリのこれまでのKubernetesリソース最適化の取り組みと、そこに見えた課題から生まれたTortoiseと呼ばれるツールを紹介しました。

メルカリではPlatformで一緒に働く仲間を探しています。
一緒にCI/CDを改善したり、抽象化を色々作ったり、リクガメを飼育したり(!?)しませんか?
興味のある方はこちらからどうぞ!

Have a better life with cute tortoises

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追åŠ