Amazon ECS のサービス自動スケーリングを最適化する - Amazon Elastic Container Service

Amazon ECS のサービス自動スケーリングを最適化する

Amazon ECS サービスは管理されたタスクのコレクションです。各サービスには、関連するタスク定義、必要なタスク数、オプションの配置戦略があります。Amazon ECS サービスの自動スケーリングは、Application Auto Scaling サービスを通じて実装されます。Application Auto Scaling は CloudWatch メトリクスをスケーリングメトリクスのソースとして使用します。また、CloudWatch アラームを使用して、サービスをスケールインまたはスケールアウトするタイミングのしきい値を設定します。スケーリングのしきい値は、メトリクスターゲットを設定して (ターゲットトラッキングスケーリング) か、しきい値を指定する (ステップスケーリング) ことで指定します。Application Auto Scaling を設定後、サービスに必要な適切なタスク数が継続的に計算されます。また、必要なタスク数を変更する必要がある場合、スケールアウトまたはスケールインによって Amazon ECS に通知します。

サービスの自動スケーリングを効果的に使用するには、適切なスケーリングメトリクスを選択する必要があります。

需要が現在の容量を超えると予測される場合は、アプリケーションをスケールアウトする必要があります。逆に、リソースが需要を上回る場合は、アプリケーションをスケールインしてコストを節約できます。

メトリックの特定

効果的にスケーリングするには、使用率または飽和度を示すメトリクスを特定することが重要です。このメトリクスがスケーリングに役立つためには、以下の特性を備えている必要があります。

  • メトリクスは需要と相関している必要があります。リソースが安定していても需要が変化したら、メトリクスの値も変化させる必要があります。需要が増減すると、メトリクスも増減します。

  • メトリクスの値はキャパシティに比例してスケールインする必要があります。需要が一定であれば、リソースを追加すると、メトリクスの値も比例して変化する必要があります。そのため、タスクの数を 2 倍にすると、指標は 50% 減少するはずです。

使用率メトリクスを特定する最良の方法は、ステージング環境などの実稼働前の環境で負荷テストを行うことです。商用およびオープンソースの負荷テストソリューションは広く利用可能です。これらのソリューションは通常、合成負荷を生成することも、実際のユーザートラフィックをシミュレートすることもできます。

負荷テストのプロセスを開始するには、アプリケーションの利用状況メトリクスのダッシュボードを設定します。これらのメトリクスには、CPU 使用率、メモリ使用率、I/O 操作、I/O キューの深さ、ネットワークスループットが含まれます。これらのメトリクスは Container Insights などのサービスを使用して収集できます。詳細については、「Container Insights を使用して Amazon ECS コンテナをモニタリングする」を参照してください。このプロセスでは、アプリケーションの応答時間や作業完了率に関するメトリクスを必ず収集してプロットしてください。

まずは小さなリクエストやジョブの挿入率から始めてください。アプリケーションがウォームアップできるように、このレートを数分間一定に保ってください。その後、速度をゆっくりと上げて、数分間一定に保ちます。アプリケーションの応答時間または完了時間が遅すぎてサービスレベル目標 (SLO) を達成できなくなるまで、このサイクルを繰り返し、その都度レートを上げていきます。

負荷テストを行う際には、各使用率メトリクスを調べてください。負荷とともに増加するメトリクスは、最適な使用率メトリクスとして役立つ最有力候補です。

次に、飽和状態に達しているリソースを特定します。同時に、使用率メトリクスを調べて、最初に高いレベルで横ばいになったものと、ピークに達して最初にアプリケーションをクラッシュさせたものを確認します。たとえば、負荷を追加するにつれて CPU 使用率が 0% から 70 ~ 80% に増加し、さらに負荷を追加してもそのレベルにとどまる場合は、CPU が飽和状態であると言っても過言ではありません。CPU アーキテクチャによっては、100% に達しない可能性があります。たとえば、負荷を追加するとメモリ使用率が増加し、タスクまたは Amazon EC2 インスタンスのメモリ制限に達するとアプリケーションが突然クラッシュしたとします。このような状況では、メモリが完全に消費された可能性があります。アプリケーションが複数のリソースを消費する可能性があります。そのため、最初に枯渇したリソースを表すメトリクスを選択します。

最後に、タスクまたは Amazon EC2 インスタンスの数を 2 倍にしてから、負荷テストを再試行します。キーメトリクスが以前の半分の速度で、増加または減少すると仮定します。この場合、メトリクスはキャパシティに比例します。これは自動スケーリングに適した使用率メトリクスです。

次に、この架空のシナリオを考えてみましょう。アプリケーションの負荷テストを行い、1 秒あたり 100 リクエストで CPU 使用率が最終的に 80% に達したとします。負荷が増えても、CPU 使用率は上昇しなくなります。ただし、アプリケーションの応答は遅くなります。次に、負荷テストを再度実行して、タスクの数を 2 倍にしますが、速度は以前のピーク値に保たれます。CPU の平均使用率が約 40% に低下した場合、CPU 平均使用率がスケーリングメトリクスの候補として適しています。一方、タスク数を増やしても CPU 使用率が 80% のままであれば、平均 CPU 使用率は適切なスケーリング指標ではありません。その場合は、適切なメトリクスを見つけるためにさらに調査する必要があります。

一般的なアプリケーションモデルおよびスケーリングプロパティ

あらゆる種類のソフトウェアが AWS で実行されます。多くのワークロードは自社開発ですが、他のワークロードは一般的なオープンソースソフトウェアをベースにしています。その出所に関係なく、サービスの一般的なデザインパターンがいくつか確認されています。どのように効果的にスケーリングできるかは、そのパターンに大きく依存します。

効率的な CPU バウンドサーバー

効率的な CPU バウンドサーバーは、CPU とネットワークスループット以外のリソースをほとんど使用しません。各リクエストはアプリケーションだけで処理できます。リクエストはデータベースなど他のサービスには依存しません。アプリケーションは何十万もの同時リクエストを処理でき、そのために複数の CPU を効率的に活用できます。各リクエストは、メモリーオーバーヘッドの少ない専用スレッドによって処理されるか、リクエストを処理する各 CPU で実行される非同期イベントループによって処理されます。アプリケーションの各レプリカは、同じようにリクエストを処理できます。CPU が枯渇する前に枯渇する可能性のあるリソースは、ネットワーク帯域幅のみです。CPU バウンドサービスでは、ピークスループットでもメモリ使用率は、利用可能なリソースのほんの一部です。

このタイプのアプリケーションは、CPU ベースの自動スケーリングに適しています。このアプリケーションはスケーリングに関して最大限の柔軟性を備えています。より大きな Amazon EC2 インスタンスまたは Fargate vCPUs を提供することで、垂直方向にスケーリングできます。また、レプリカをさらに追加することで水平方向にスケールすることもできます。レプリカを追加したり、インスタンスサイズを 2 倍にしたりすると、容量に対する平均 CPU 使用率が半分に削減されます。

このアプリケーションに Amazon EC2 の容量を使用している場合は、c5 または c6g ファミリーなどのコンピューティング最適化インスタンスに配置することを検討してください。

効率的なメモリバウンドサーバー

効率的なメモリバウンドサーバーは、リクエストごとに大量のメモリを割り当てます。同時実行性が最大になると、必ずしもスループットが高くなるわけではありませんが、CPU リソースが使い果たされる前にメモリが使い果たされます。リクエストに関連するメモリは、リクエストが終了すると解放されます。メモリに余裕がある限り、追加のリクエストを受け付けることができます。

このタイプのアプリケーションは、メモリベースの自動スケーリングに適しています。このアプリケーションはスケーリングに関して最大限の柔軟性を備えています。より大きな Amazon EC2 または Fargate メモリリソースを提供することで、垂直方向の両方にスケーリングできます。また、レプリカをさらに追加することで水平方向にスケールすることもできます。レプリカを追加したり、インスタンスサイズを 2 倍にしたりすると、容量に対する平均メモリ使用率を半分に減らすことができます。

このアプリケーションに Amazon EC2 キャパシティを使用している場合は、r5 または r6g ファミリーなどのメモリ最適化インスタンスにキャパシティを割り当てることを検討してください。

メモリに制約のあるアプリケーションの中には、同時実行数を減らしても使用されるメモリが減らないように、リクエストの終了時にそのリクエストに関連するメモリを解放しないものがあります。このため、メモリベースのスケーリングを使用することはお勧めしません。

ワーカーベースのサーバー

ワーカーベースのサーバーは、個々のワーカースレッドごとに 1 つのリクエストを次々に処理します。ワーカースレッドは POSIX スレッドなどの軽量スレッドでもかまいません。UNIX プロセスなど、より負荷の大きいスレッドの場合もあります。どのスレッドであっても、アプリケーションがサポートできる同時実行数は常に最大です。通常、同時実行数の制限は、使用可能なメモリリソースに比例して設定されます。同時実行数の上限に達すると、追加のリクエストがバックログキューに入れられます。バックログキューがオーバーフローすると、それ以降の受信リクエストは直ちに拒否されます。このパターンに当てはまる一般的なアプリケーションには、Apache Web サーバーや Gunicorn などがあります。

このアプリケーションを拡張するには、通常、リクエストの同時実行性が最適なメトリクスです。各レプリカには同時実行数の制限があるため、平均制限に達する前にスケールアウトすることが重要です。

リクエストの同時実行メトリクスを取得する最良の方法は、アプリケーションに CloudWatch にレポートさせることです。アプリケーションの各レプリカは、同時リクエストの数をカスタムメトリクスとして高い頻度で公開できます。この頻度は少なくとも 1 分に 1 回に設定することをおすすめします。複数のレポートを収集したら、平均同時実行数をスケーリングメトリクスとして使用できます。このメトリクスは、同時実行数の合計をレプリカ数で割って計算されます。たとえば、同時実行数の合計が 1000 で、レプリカの数が 10 の場合、平均同時実行数は 100 です。

アプリケーションが Application Load Balancer の背後にある場合は、ロードバランサーの ActiveConnectionCount メトリクスをスケーリングメトリクスの要素として使用することもできます。平均値を求めるには、ActiveConnectionCount メトリクスをレプリカの数で割る必要があります。スケーリングには未加工のカウント値ではなく、平均値を使用する必要があります。

この設計を最適に機能させるには、リクエストレートが低い場合でも応答レイテンシの標準偏差を小さくする必要があります。需要が少ない時期には、ほとんどのリクエストに短時間で回答し、平均応答時間よりも大幅に時間がかかるリクエストは多くないことをお勧めします。平均応答時間は 95 パーセンタイル応答時間に近くなります。そうしないと、結果としてキューオーバーフローが発生する可能性があります。これはエラーにつながります。オーバーフローのリスクを軽減するために、必要に応じて追加のレプリカを提供することをお勧めします。

待機中のサーバー

待機中のサーバーはリクエストごとに何らかの処理を行いますが、機能するには 1 つ以上のダウンストリームサービスに大きく依存しています。コンテナアプリケーションは多くの場合、データベースやその他の API サービスなどのダウンストリームサービスを多用します。特に大容量または同時実行の多いシナリオでは、これらのサービスが応答するまでに時間がかかることがあります。これは、これらのアプリケーションが CPU リソースをほとんど使用せず、使用可能なメモリの観点から最大限の同時実行性を利用する傾向があるためです。

待機サービスは、アプリケーションの設計方法に応じて、メモリーバウンドのサーバーパターンとワーカーベースのサーバーパターンのどちらにも適しています。アプリケーションの同時実行がメモリによってのみ制限される場合は、平均メモリ使用率をスケーリングメトリクスとして使用する必要があります。アプリケーションの同時実行がワーカーの制限に基づいている場合は、平均同時実行数をスケーリングメトリクスとして使用する必要があります。

Java ベースのサーバー

Java ベースのサーバーが CPU に依存し、CPU リソースに比例してスケーリングする場合は、効率的な CPU バウンドのサーバーパターンに適している場合があります。その場合は、平均 CPU 使用率がスケーリングメトリクスとして適切である場合があります。ただし、多くの Java アプリケーションは CPU に依存していないため、スケーリングが困難です。

最高のパフォーマンスを得るには、Java 仮想マシン (JVM) ヒープにできるだけ多くのメモリーを割り当てることをお勧めします。Java 8 update 191 以降を含む最近のバージョンの JVM では、コンテナに収まるようにヒープサイズをできるだけ大きく自動的に設定しています。つまり、Java では、メモリー使用率がアプリケーション使用率に比例することはほとんどありません。要求率と同時実行数が増加しても、メモリ使用率は一定に保たれます。このため、メモリー使用率に基づいて Java ベースのサーバーをスケーリングすることはお勧めしません。代わりに、通常は CPU 使用率に基づいてスケーリングすることをおすすめします。

Java ベースのサーバーでは、CPU を使い果たす前にヒープが枯渇することがあります。同時実行数が多いとアプリケーションがヒープを使い果たしやすい場合は、平均接続数が最適なスケーリングメトリクスです。アプリケーションが高スループットでヒープを使い果たしやすい場合は、平均リクエストレートが最適なスケーリングメトリクスです。

ガベージコレクションされた他のランタイムを使用するサーバー

多くのサーバーアプリケーションは、.NET や Ruby などのガベージコレクションを実行するランタイムをベースにしています。これらのサーバーアプリケーションは、前述のパターンのいずれかに当てはまります。ただし、Java の場合と同様に、これらのアプリケーションの平均メモリ使用率はスループットや同時実行性と相関関係がないことが多いため、メモリに基づいてアプリケーションをスケーリングすることはお勧めしません。

このようなアプリケーションでは、アプリケーションが CPU に制約されている場合は CPU 使用率に基づいてスケールすることをおすすめします。それ以外の場合は、負荷テストの結果に基づいて、平均スループットまたは平均同時実行性を基準にスケーリングすることをお勧めします。

ジョブプロセッサ

多くのワークロードには非同期のジョブ処理が含まれます。その中には、リクエストをリアルタイムで受信せず、代わりにワークキューにサブスクライブしてジョブを受け取るアプリケーションが含まれます。この種のアプリケーションでは、ほとんどの場合、適切なスケーリング指標はキューの深さです。キューの増加は、保留中の作業が処理能力を上回っていることを示しているのに対し、キューが空の場合は、実行すべき作業よりも容量が多いことを示します。

Amazon SQS や Amazon Kinesis Data Streams などの AWS メッセージングサービスは、スケーリングに使用できる CloudWatch メトリクスを提供します。Amazon SQS では、ApproximateNumberOfMessagesVisible が最適なメトリクスです。Kinesis Data Streams では、Kinesis Client Library (KCL) が公開している MillisBehindLatest メトリクスの使用を検討してください。このメトリクスは、スケーリングに使用する前に、すべてのコンシューマで平均化する必要があります。