🎢

Elastic Cloud用のAutoscaler

2022/12/30に公開

情報検索・検索技術 Advent Calendar 2022 の23日目の記事です。(だいぶ遅れてしまい恐縮です🙇‍♂️)

メルカリUSの検索チームでBackendエンジニアをしている、k-yomoです。

この記事では、筆者がOSSとして現在開発しているElastic Cloud Autoscalerを紹介したいと思います。
https://github.com/k-yomo/elastic-cloud-autoscaler

背景

Elasti Cloud Autoscalerは主に下記の2つのユースケースをカバーするために開発を始めました。

1. 負荷に応じてインスタンス数を自動で増減したい

負荷の増減に応じて、インスタンス数が勝手に増えたり減ったりすると便利ですよね。ElasticsearchクラスターをK8sに乗せて運用する場合はes-operatorなどを使うとオートスケーリングが可能になりますが、Elastic Cloudではデータサイズに応じたオートスケーリングの機能のみで、負荷に応じたオートスケーリングは提供されていません。
https://www.elastic.co/guide/en/cloud/current/ec-autoscaling.html

Advent Calender 9日目のElasticsearchのアーキテクチャとStateless / Serverlessでも紹介していただいているように、ElasticsearchはServerlessの方向性に向かっており、Serverlessの対応に伴ってオートスケーリングも提供されそうな雰囲気ではありますが、逆に言えば現行のアーキテクチャでは負荷に応じたオートスケーリングは提供される可能性が少ないと感じています。
https://blog.johtani.info/blog/2022/12/09/open-search-serverless/

2. 特定の期間中インスタンス数を増やしたい

土日にトラフィックが増えるためその期間だけインスタンス数を増やしておきたい、重いreindexバッチ処理の実行に合わせてインスタンス数を予め増やしておきたい、などなど、日時や時間帯に応じてインスタンス数を予め増やしておきたいケースはよくあるかと思います。

弊社ではありませんが、休日に手動でスケールアウト/スケールインを実施しているという話も伺ったことがあるので、これが自動化できるとエンジニアの休日もより穏やかになりそうです。

Elastic Cloud Autoscaler

概要


Elastic Cloud Autoscalerは上記のユースケースを満たす主に下記の2つの機能によるElastic Cloudのノード数、並びにElaticsearchのレプリカ数のオートスケーリングを実装しています。

  • CPU使用率に応じたオートスケーリング
  • Cronフォーマットで指定可能なスケジュールドスケーリング

また、技術スタックに応じて柔軟に利用できるようGoのライブラリとして提供しており、実際に使う際はCloud Scheduler + Cloud Funcitons / Cloud Run Jobsや、K8sのCron jobなどを使用する形を想定しています。

すぐに使えるDocker Imageも用意しているので、環境変数とYAMLの設定ファイルを埋めるだけで簡単に使用可能です。
https://hub.docker.com/r/kyomo/elastic-cloud-autoscaler

下記がスケーリングの設定項目になります。ノード数の下限と上限、指定indexのノードあたりのシャード数を設定し、各スケーリングの設定を埋める形になっています。
AutoScalingScheduledScalingsについては、この後にそれぞれ紹介させて頂きます。

type ScalingConfig struct {
	// Default memory min size. It can be overwritten by ScheduledScalingConfig.MinSizeMemoryGB.
	// Available number is only 64,...(64xN node)
	// If you have multiple zone, this memory is per zone.
	// NOTE: Currently we only support 64GB node for simplicity
	DefaultMinSizeMemoryGB int `validate:"gte=64"`
	// Default memory max size. It can be overwritten by ScheduledScalingConfig.MaxSizeMemoryGB.
	// Available number is only 64,...(64xN node)
	// If you have multiple zone, this memory is per zone.
	// NOTE: Currently we only support 64GB node for simplicity
	DefaultMaxSizeMemoryGB int `validate:"gte=64,gtefield=DefaultMinSizeMemoryGB"`

	AutoScaling *AutoScalingConfig
	// If the time is within multiple schedules, the last schedule will be applied
	// e.g. [{min: 1, max: 2}, {min:2, max:4}] => min: 2, max: 4
	ScheduledScalings []*ScheduledScalingConfig

	// Index to update replicas when scaling out/in
	Index         string `validate:"required"`
	ShardsPerNode int    `validate:"required,gte=1"`
}

AutoScaling - CPU使用率に応じたオートスケーリング

モニタリング用のデプロイメントのElasticsearchからノードのCPU使用率を取得し、CPU使用率が一定期間、ターゲットのCPU使用率を上まり続けた/下回り続けた場合、ターゲットの使用率に近づくようにノード数とレプリカ数を増減させます。

下記が実際の設定項目になります。ScaleOutCoolDownDurationScaleInCoolDownDurationを設定すると、スケール後に一定期間オートスケーリングを適用しないことができます。

type AutoScalingConfig struct {
	MetricsProvider       metrics.Provider `validate:"required"`
	DesiredCPUUtilPercent int              `validate:"required,gt=0,lt=100"`

	ScaleOutThresholdDuration time.Duration `validate:"gte=0"`
	ScaleOutCoolDownDuration  time.Duration `validate:"gte=0"`

	ScaleInThresholdDuration time.Duration `validate:"gte=0"`
	ScaleInCoolDownDuration  time.Duration `validate:"gte=0"`
}

例えば、現在の設定項目とCPU使用率が下記のような場合、Afterの構成にスケールアウトします。

[設定]
指定indexのシャード数: 2
ノードあたりのシャード数: 1

[Before]
ノード数: 4(2ノード x 2AZ)
レプリカ数: 1
平均CPU使用率: 60%
ターゲットCPU使用率 40%

[After]
ノード数: 6(3ノード x 2AZ)
レプリカ数: 2

スケーリングの際に、事前に設定された指定indexのノードあたりのシャード数を必ず満たすようにノード数を選択します。逆に言えば、下記のような、indexのノードあたりのシャード数が満たせない場合、スケーリングは行われません。

[設定]
指定indexのシャード数: 3
最低ノード数: 6
最大ノード数: 10
ノードあたりのシャード数: 1

[Before]
ノード数: 6(5ノード x 2AZ)
レプリカ数: 1
平均CPU使用率: 66%
ターゲットCPU使用率 40%

📝 ノード数を10(5ノード x 2AZ)に増やしたいが、レプリカを2に増やすと合計シャード数が9、3に増やすと合計シャード数が12になり、ノードあたりのシャード数の設定を満たせないため、スケールアウトができない

ScheduledScalings - Cronフォーマットで指定可能なスケジュールドスケーリング

スケジュールドスケーリングと言ってきましたが、実際には指定期間中の最低ノード数と最大ノード数を指定する事ができる設定項目となっています。

下記が実際のstructになります。

type ScheduledScalingConfig struct {
	// MinSizeMemoryGB is the minimum memory size during the specified period
	// If 0, then `ScalingConfig.DefaultMinSizeMemoryGB` will be used
	// NOTE: Currently we only support 64GB node for simplicity
	MinSizeMemoryGB int `validate:"gte=64"`
	// MaxSizeMemoryGB is the maximum memory size during the specified period
	// If 0, then `ScalingConfig.DefaultMaxSizeMemoryGB` will be used
	// NOTE: Currently we only support 64GB node for simplicity
	MaxSizeMemoryGB int `validate:"gte=64,gtefield=MinSizeMemoryGB"`
	// cron format schedule
	// default timezone is machine local timezone,
	// if you want to specify, set TZ= prefix (e.g. `TZ=UTC 0 0 0 0 0`)
	StartCronSchedule string
	Duration          time.Duration `validate:"gt=0"`
}

StartCronScheduleが指定期間の開始日時をCronのフォーマットで受け付けるようになっているため、下記のようトラフィックが増える土日の間は最低ノード数を2倍にする等、柔軟に期間を設定する事が可能です。

&ScheduledScalingConfig{
	// デフォルトが128の場合
	// 256 = 64g * 4 nodes
	MaxSizeMemoryGB: 256,
	StartCronSchedule: "TZ=Asia/Tokyo 0 0 * * SAT",
	Duration 48 * time.Hour
}

TZを指定しないとデフォルトでLocalのTZになるため、予期しないTZになってしまわないよう基本的にはTZを指定して使用する事をおすすめします。

デモ

気軽にElastic Cloud Autoscalerを試せるデモ用のリポジトリを用意しました!
https://github.com/k-yomo/elastic-cloud-autoscaler-demo

Elastic CloudのAPIキーを環境変数にセットした後、コマンド一発でTerraformにて必要なリソースが立ち上がります。そしてK6にて徐々に負荷をかけていった状況でどうノード数・レプリカ数が増減するのかを標準出力から確認する事ができます。

セットアップ

make setupコマンドを実行すると、デフォルトでは下記の設定でリソースを作成します。

  • クラスター
    • マスターノード x 3
    • データノード(64GB) x 6 (3 x 2AZ)
  • インデックス

スケーリングの挙動の確認

make runでAutoscalerを起動した後、make loadtestでk6を実行します。k6のデフォルトの設定は↓のようになっており、検索リクエストを5分間で徐々に1500rpsまで増やし、10分間を経て、徐々にリクエスト数が減少していくようなシナリオとなっています。

export const options = {
    discardResponseBodies: true,

    scenarios: {
        ramping_up_scenario: {
            executor: 'ramping-vus',
            startVUs:10,
            stages: [
                { target: 150, duration: '5m' },
                { target: 150, duration: '10m' },
                { target: 100, duration: '10m' },
                { target: 50, duration: '20m' },
                { target: 10, duration: '20m' },
            ],
        },
    },
};

こちらが実際に上記のk6を実行した際の標準出力の一部とメトリックスになります。

標準出力

2022/12/30 20:27:41 [START] elastic cloud autoscaler
==============================================
scaling direction: SCALING_OUT
node num updated from: 3 => to 4
replica num updated from: 5 => to 7
reason: CPU utilization (currently '65.2%') is greater than the desired CPU utilization '50%' for 60 seconds
==============================================
2022/12/30 20:28:36 [END] elastic cloud autoscaler
2022/12/30 20:29:36 [START] elastic cloud autoscaler
==============================================
scaling direction: SCALING_OUT
node num updated from: 4 => to 6
replica num updated from: 7 => to 11
reason: CPU utilization (currently '84.2%') is greater than the desired CPU utilization '50%' for 60 seconds
==============================================
2022/12/30 20:30:31 [END] elastic cloud autoscaler
...
2022/12/30 20:49:53 [START] elastic cloud autoscaler
==============================================
scaling direction: SCALING_IN
node num updated from: 6 => to 5
replica num updated from: 11 => to 9
reason: CPU utilization (currently '45.2%') is lower than the desired CPU utilization '50%' for 60 seconds
==============================================
2022/12/30 20:52:01 [END] elastic cloud autoscaler
...
2022/12/30 20:57:06 [START] elastic cloud autoscaler
==============================================
scaling direction: SCALING_IN
node num updated from: 5 => to 4
replica num updated from: 9 => to 7
reason: CPU utilization (currently '44.2%') is greater than the desired CPU utilization '50%' for 60 seconds
==============================================
2022/12/30 20:58:54 [END] elastic cloud autoscaler

Search RPS

CPU使用率

ノード数/AZ


※ ノード数のグラフがKibana上になかったため、このグラフだけ手動で作成しています、見づらくてすみません🙏

Latency

期待通り、負荷の上昇に伴ってスケールアウトし、負荷の減少に伴ってスケールインしています。スケーリングにかかる時間はスケールアウトは約1分ほど、スケールインは2分ほどでした。
(※この時間はクラスターのサイズ等によっても変わるかと思います)

またレイテンシについても、スケールアウトによってスパイクせずに、200ms周辺で止まっていることが見てとれます。

こちらのデモですが、簡単に実行できるようになっているので、もし興味がある方がいたら、是非試して頂けると幸いです!(お金がかかるので、最後にmake cleanupをお忘れなく...!)

制約

ここからは、現状把握している制約について紹介したいと思います。

CPU使用率に応じたオートスケーリングにはMonitoringデプロイメントが必要

ここでMonitoringデプロイメントと呼んでいるのは、下記のページで設定する、Monitoringのメトリックスを送るデプロイメントになります。

別のデプロイメントに送らず、自デプロイメントを指定する事も出来ますが、本番環境では推奨されていません。

For production use, you should send your deployment logs and metrics to a dedicated monitoring deployment. Monitoring indexes logs and metrics into Elasticsearch and these indexes consume storage, memory, and CPU cycles like any other index. By using a separate monitoring deployment, you avoid affecting your other production deployments and can view the logs and metrics even when a production deployment is unavailable.

Elasticsearch v8以上のみ対応

Monitoring用のindex名がバージョンによって異なる?ため、v7以前のバージョンでは正しく動かない可能性があります。

data_contentノードの増減にのみ対応

Warm tierやCold tier用のノードの増減には現状対応していません 。また、マスターノードの増減にも対応していないため、データノードを5つ以下から6以上(Dedicated マスターのノードが必要)に増やすことができない仕様となっています。

メモリ64GB以上のノードの増減のみ対応

8GBや32GBなどの64GB以下のサイズでは、スケールアップ/スケールダウンが必要になり、スケーリングの際に考慮する項目が増えるため、初期の実装では64GB以上のサイズのノード増減のみ対応しています。

同様の利用でAZの増減も対応していませんが、今後8GB x 2AZのノードを3AZに増やすスケーリングや、16GBにスケールアップするようなスケーリングに対応する等も検討しています。

auto_expand_replicas未対応

Elasticsearchではノード数の増減に伴って自動でレプリカ数を変更してくれるauto_expand_replicasをindexに設定する事ができますが、Elastic Cloud Autoscalerではauto_expand_replicasを使わずにレプリカ数を変更しています。

auto-expanded number of replicas ignores other allocation rules such as total shards per node, and this can lead to the cluster health becoming YELLOW
理由としては、上記の通りauto_expand_replicasを使うとノードあたりのシャード数の設定が適用されないこと、複数シャードの場合の挙動が未知なことの2点になります。

時間がある際にauto_expand_replicasの挙動を確認してみたいと思いますが、auto_expand_replicasの挙動に詳しい方がいたら、教えていただけると幸いです。

もしauto_expand_replicasを使っていい感じにレプリカ数を増減できるのであれば、将来的にauto_expand_replicasが設定されている場合はElastic Cloud Autosclerはノード数の増減のみを行うよう修正する可能性があります。

また、もし複数シャードで上手く動かないようであれば、シャード数1の時のみ、auto_expand_replicasにレプリカ数の増減を任せる、のような形になるかもしれません。

⚠️注意

Elastic Cloud Autoscalerは開発段階のライブラリであるため、想定外の挙動や、また今後破壊的な変更が発生する可能性もありますので、ご注意ください...!

また、実際のスケーリングは適用せずにどのようにスケーリングするかのみを確認できるようDryRunという設定項目も用意しています。 利用する前に一度DryRunで挙動を確認すると良さそうです。

まとめ

この記事では現在開発しているライブラリ、Elastic Cloud Autoscalerについて紹介しました。

まだまだ開発途中ではありますが、来年には本番環境でも便利に安心して使えるように出来ればと思います!
https://github.com/k-yomo/elastic-cloud-autoscaler

それでは皆さん、良いお年を!

Discussion