数学を行う:オーケストレーターを使用したマイクロサービスアプリケーションのスケーリング

公開: 2022-03-11

マイクロサービスアプリケーションアーキテクチャがソフトウェア設計に侵入し続けていることは、まったく驚くべきことではありません。 開発とチーム管理を容易にしながら、負荷を分散し、高可用性デプロイメントを作成し、アップグレードを管理する方がはるかに便利です。

しかし、コンテナオーケストレーターなしでは話は確かに同じではありません。

それらの主要な機能のすべて、特に自動スケーリングを使いたいと思うのは簡単です。 コンテナの展開が1日中変動し、現在の負荷を処理できるサイズになっているのを見て、他のタスクに時間を割けるのは、なんと幸運なことでしょう。 コンテナ監視ツールが表示している内容に誇りを持って満足しています。 その間、いくつかの設定を構成しました。そうです、魔法を作成するのに必要なのは(ほぼ)それだけです!

これを誇りに思う理由がないというわけではありません。ユーザーが優れたエクスペリエンスを提供し、大規模なインフラストラクチャでお金を無駄にしないことを確信しています。 これはすでにかなりの量です!

そしてもちろん、そこにたどり着くまでの道のりはなんと素晴らしいことでしょう。 最終的に構成する必要のある設定がそれほど多くない場合でも、開始する前に通常考えるよりもはるかに難しいためです。 レプリカの最小/最大数、アップスケール/ダウンスケールのしきい値、同期期間、クールダウン遅延-これらすべての設定は非常に密接に関連しています。 1つを変更すると、もう1つに影響を与える可能性が高くなりますが、アプリケーション/デプロイメントとインフラストラクチャの両方に適合するバランスの取れた組み合わせを調整する必要があります。 それでも、ニーズに大きく依存するため、インターネット上で料理本や魔法の公式を見つけることはできません。

私たちのほとんどは、最初にそれらを「ランダム」またはデフォルト値に設定し、後で監視中に見つけたものに応じて調整します。 それは私に考えさせられました:もし私たちが勝利の組み合わせを見つけるのを助けるより「数学的な」手順を確立することができたらどうでしょうか?

コンテナオーケストレーションパラメータの計算

アプリケーションの自動スケーリングマイクロサービスについて考えるとき、実際には2つの主要な点で改善を検討しています。

  1. 負荷が急激に増加した場合にデプロイメントを迅速にスケールアップできることを確認します(ユーザーがタイムアウトやHTTP 500に直面しないようにするため)
  2. インフラストラクチャのコストを下げる(つまり、インスタンスの負荷が過小になるのを防ぐ)

これは基本的に、スケールアップとスケールダウンのためにコンテナソフトウェアのしきい値を最適化することを意味します。 (Kubernetesのアルゴリズムには2つのパラメーターが1つあります)。

インスタンス関連のすべてのパラメーターがupscale-thresholdに関連付けられていることを後で示します。 これは計算が最も難しいものです。したがって、この記事をご覧ください。

注:クラスター全体で設定されるパラメーターについては、適切な手順はありませんが、この記事の最後に、計算時にそれらを考慮したソフトウェア(静的Webページ)を紹介します。インスタンスの自動スケーリングパラメータ。 そうすれば、それらの値を変化させて、それらの影響を考慮することができます。

スケールアップしきい値の計算

この方法を機能させるには、アプリケーションが次の要件を満たしていることを確認する必要があります。

  1. 負荷は、アプリケーションのすべてのインスタンスに均等に分散する必要があります(ラウンドロビン方式で)
  2. リクエストのタイミングは、コンテナクラスタのロードチェック間隔よりも短くする必要があります。
  3. 多数のユーザー(後で定義)でプロシージャを実行することを検討する必要があります。

これらの条件の主な理由は、アルゴリズムが負荷をユーザーごとではなく分散として計算するという事実にあります(後で説明します)。

すべてのガウス分布を取得

まず、急激な負荷の増加、つまり最悪のシナリオの定義を作成する必要があります。 私にとって、それを翻訳する良い方法は、リソースを消費するアクションを短時間で多数のユーザーに実行させることです。また、別のユーザーまたはサービスのグループが他のタスクを実行しているときに発生する可能性が常にあります。 それでは、この定義から始めて、いくつかの数学を抽出してみましょう。 (アスピリンを準備してください。)

いくつかの変数の紹介:

  • $ N_ {u} $、「多数のユーザー」
  • $ L_ {u}(t)$、「リソースを消費する操作」を実行する1人のユーザーによって生成される負荷($ t = 0 $は、ユーザーが操作を開始した瞬間を指します)
  • $ L_ {tot}(t)$、合計負荷(すべてのユーザーによって生成された)
  • $ T_ {tot} $、「短期間」

数学の世界では、同時に同じことを実行する多数のユーザーについて話すと、時間の経過に伴うユーザーの分布は、次の式のガウス(または正規)分布に従います。

\ [G(t)= \ frac {1} {\ sigma \ sqrt {2 \ pi}} e ^ {\ frac {-(t- \ mu)^ 2} {2 \ sigma ^ 2}} \]

ここ:

  • µは期待値です
  • σは標準偏差です

そして、それは次のようにグラフ化されます($ µ = 0 $):

面積の99.7%がプラスマイナス3シグマの間にどのように収まるかを示すガウス分布のグラフ

おそらく、あなたが受講したいくつかのクラスを彷彿とさせます。新しいことは何もありません。 ただし、ここで最初の問題に直面します。数学的に正確にするには、$-\infty$から$+\ infty $までの時間範囲を考慮する必要がありますが、これは明らかに計算できません。

しかし、グラフを見ると、間隔$ [-3σ、3σ] $の外側の値はゼロに非常に近く、あまり変化しないことがわかります。つまり、それらの影響は実際には無視でき、脇に置くことができます。 私たちの目標はアプリケーションのスケールアップをテストすることであるため、これはより真実です。そのため、多数のユーザーのバリエーションを探しています。

さらに、間隔$ [-3σ、3σ] $にはユーザーの99.7%が含まれているため、合計に十分に近いため、$ N_{u}$に1.003を掛けて補う必要があります。違い。 この間隔を選択すると、$ µ =3σ$が得られます($ t = 0 $から作業するため)。

$ T_ {tot} $への対応に関しては、$6σ$($ [-3σ、3σ] $)と等しくなるように選択することは、ユーザーの95.4%が$ [- 2σ、2σ] $、これは$4σ$続きます。 したがって、$ T_ {tot} $を$6σ$と等しくなるように選択すると、ユーザーのわずか4.3%の時間の半分が追加されますが、これは実際には代表的ではありません。 したがって、$ T_ {tot} =4σ$を取ることを選択し、次のように推測できます。

\(σ= \ frac {T_ {tot}} {4} \)および\(µ = \ frac {3} {4} * T_ {tot} \)

それらの価値観はただ帽子から引き出されたのでしょうか? はい。 しかし、これが彼らの目的であり、これは数学的手順に影響を与えません。 これらの定数は私たちのためのものであり、私たちの仮説に関連する概念を定義します。 これは、それらが設定されたので、最悪のシナリオを次のように変換できることを意味します。

$ N {u} $の99.7%によって生成された負荷は、消費操作$ L {u}(t)$を実行し、そのうちの95.4%が$ T{tot}$の期間内に実行しています。

(これは、Webアプリを使用するときに覚えておく価値のあることです。)

以前の結果をユーザー分布関数(ガウス)に注入すると、次のように方程式を簡略化できます。

\ [G(t)= \ frac {4 N_ {u}} {T_ {tot} \ sqrt {2 \ pi}} e ^ \ frac {-(4t-3T_ {tot})^ 2} {T_ {tot } ^ 2} \]

これからは、$σ$と$ µ $を定義して、区間$ t \ in [0、\ frac {3} {2} T_ {tot}] $(持続する$6σ$)で作業します。

総ユーザー負荷はいくらですか?

自動スケーリングマイクロサービスの2番目のステップは、$ L_ {tot}(t)$を計算することです。

$ G(t)$は分布であるため、特定の時点でのユーザー数を取得するには、その積分を計算する(または累積分布関数を使用する)必要があります。 ただし、すべてのユーザーが同時に操作を開始するわけではないため、$ L_ {u}(t)$を導入して、方程式を使用可能な式に還元しようとすると、非常に混乱します。

したがって、これを簡単にするために、リーマン和を使用します。これは、小さな形状の有限和を使用して積分を近似する数学的な方法です(ここでは長方形を使用します)。 形状(細分割)が多いほど、結果はより正確になります。 サブディビジョンを使用するもう1つの利点は、サブディビジョン内のすべてのユーザーが同時に操作を開始したと見なすことができるという事実にあります。

リーマン和に戻ると、積分に関連する次の特性があります。

\ [\ int_ {a} ^ {b} f(x)dx = \ lim_ {n \ rightarrow \ infty} \ sum_ {k = 1} ^ {n}(x_ {k}-x_ {k-1}) f(x_ {k})\]

$x_k$を次のように定義します。

\ [x_ {k} = a + k \ frac {b --a} {n}、0 \ leq k \ leq n \]

これは次の場合に当てはまります。

  • $n$は細分化の数です。
  • $ a $は下限であり、ここでは0です。
  • $ b $が上限で、ここでは$ \ frac {3} {2} * T_{tot}$です。
  • $ f $は、その面積を概算する関数(ここでは$ G $)です。

関数Gのリーマン和のグラフ。X軸はT-sub-totの0から3つの半分になり、x-sub-k-minus-1とx-の間にあることを示す単一の長方形が強調表示されます。サブk。

注:サブディビジョンに存在するユーザーの数は整数ではありません。 これが、2つの前提条件の理由です。ユーザーの数が多い(したがって、小数部分があまり影響を与えない)ことと、負荷をすべてのインスタンスに均等に分散する必要があることです。

また、リーマン和の定義の右側に細分割の長方形の形状が表示されていることにも注意してください。

リーマン和の式ができたので、時間$ t $での負荷値は、すべてのサブディビジョンのユーザー数の合計に、対応する時間でのユーザー負荷関数を掛けたものであると言えます。 これは次のように書くことができます:

\ [L_ {tot}(t)= \ lim_ {n \ rightarrow \ infty} \ sum_ {k = 1} ^ {n}(x_ {k} --x_ {k-1})G(x_ {k}) L_ {u}(t --x_ {k})\]

変数を置き換えて式を単純化すると、次のようになります。

\ [L_ {tot}(t)= \ frac {6 N_ {u}} {\ sqrt {2 \ pi}} \ lim_ {n \ rightarrow \ infty} \ sum_ {k = 1} ^ {n}(\ frac {1} {n})e ^ {-{(\ frac {6k} {n} -3)^ {2}}} L_ {u}(t --k \ frac {3 T_ {tot}} {2n })\]

そして出来上がり! ロード関数を作成しました!

スケールアップしきい値の検索

最後に、しきい値を変更する二分法アルゴリズムを実行して、インスタンスあたりの負荷が負荷関数全体で最大制限を超えない最大値を見つける必要があります。 (これはアプリによって行われることです。)

他のオーケストレーションパラメータの推定

スケールアップしきい値($ S_ {up} $)が見つかるとすぐに、他のパラメーターを非常に簡単に計算できます。

$ S_ {up} $から、インスタンスの最大数がわかります。 (ロード関数の最大負荷を探し、インスタンスごとの最大負荷ごとに分割して切り上げることもできます。)

インスタンスの最小数($ N_ {min} $)は、インフラストラクチャに応じて定義する必要があります。 (AZごとに少なくとも1つのレプリカを用意することをお勧めします。)ただし、負荷関数も考慮する必要があります。ガウス関数は非常に急速に増加するため、最初は負荷分散が(レプリカごとに)より強くなります。この効果を緩和するために、レプリカの最小数を増やしたい場合があります。 (これにより、$ S_ {up} $が増える可能性があります。)

最後に、レプリカの最小数を定義したら、次のことを考慮してスケールダウンしきい値($ S_ {down} $)を計算できます。単一のレプリカをスケールダウンしても、他のインスタンスに影響を与えるのは、 $ N_ {min} +1$から$N_{min} $の場合、スケールダウンの直後にスケールアップしきい値がトリガーされないようにする必要があります。 許可されている場合、これはヨーヨー効果をもたらします。 言い換えると:

\ [(N_ {min} + 1)S_ {down} <N_ {min} S_ {up} \]

または:

\ [S_ {down} <\ frac {N_ {min}} {N_ {min} +1} S_ {up} \]

また、クラスターがスケールダウンする前に待機するように構成されている時間が長いほど、$ S_{down}$を上限に近づける方が安全であることを認めることができます。 もう一度、あなたはあなたに合ったバランスを見つける必要があります。

オートスケーラーでMesosphereMarathonオーケストレーションシステムを使用する場合、スケールダウンから一度に削除できるインスタンスの最大数はAS_AUTOSCALE_MULTIPLIER ($ A_ {mult} $)に関連付けられていることに注意してください。これは、次のことを意味します。

\ [S_ {down} <\ frac {S_ {up}} {A_ {mult}} \]

ユーザーロード機能はどうですか?

はい、それは少し問題であり、数学的に解決するのが最も簡単な問題ではありません。

この問題を回避するには、アプリケーションの単一インスタンスを実行し、サーバーの負荷が割り当てられた最大値に達するまで(ただし、それを超えないように)、同じタスクを繰り返し実行するユーザーの数を増やします。 次に、ユーザー数で割り、リクエストの平均時間を計算します。 ユーザーロード機能に統合するすべてのアクションでこの手順を繰り返し、タイミングを追加します。

この手順は、各ユーザーリクエストの処理に一定の負荷がかかることを考慮していることを認識しています(これは明らかに正しくありません)が、各ユーザーが同時に同じ処理ステップにいないため、ユーザーの大部分がこの効果を生み出します。 したがって、これは許容できる概算だと思いますが、これもまた、多数のユーザーを扱っていることを示唆しています。

CPUフレームグラフなどの他の方法を試すこともできます。 しかし、ユーザーの行動をリソースの消費に結び付ける正確な式を作成することは非常に難しいと思います。

app-autoscaling-calculator紹介

そして今、全体で言及されている小さなWebアプリの場合:ロード関数、コンテナーオーケストレーター構成、およびその他の一般的なパラメーターを入力として受け取り、スケールアップしきい値およびその他のインスタンス関連の数値を返します。

プロジェクトはGitHubでホストされていますが、ライブバージョンも利用できます。

これは、(Kubernetesの)テストデータに対して実行されたWebアプリによって提供された結果です。

インスタンスの数とインスタンスごとの負荷を経時的に示すグラフ

マイクロサービスのスケーリング:暗闇の中で手探りする必要はもうありません

マイクロサービスアプリケーションアーキテクチャに関しては、コンテナのデプロイがインフラストラクチャ全体の中心点になります。 また、オーケストレーターとコンテナーの構成が適切であればあるほど、実行時間はスムーズになります。

DevOpsサービスの分野の私たち​​は、アプリケーションのオーケストレーションパラメーターを調整するためのより良い方法を常に模索しています。 自動スケーリングマイクロサービスに、より数学的なアプローチを取りましょう!