kokoni

クラウドおじさんの備忘録

Azure Functionsのスケーリングの確認とプラン別の比較

本投稿はAzure Advent Calendar 2019の16日目の投稿になります。

qiita.com

先月開催されたMicrosoft Ignite 2019でPremiumFunctionsがGAしたので、ここでAzure FunctionsのConsumptionプランとPremiumプランに負荷テストを実施してスケーリングの状態を確認する方法につてい記載していきたいと思います。

※この投稿は2019年12月16日時点の情報になります。

負荷テストサービス

スケーリングを確認するためには負荷をかける必要があるため、まずは負荷テストサービスの設定を行います。

今回はlaoder.ioを利用します。BlazeMeterはJMeterクラウドベース負荷テストツールです。どちらも無料プランが提供されていて利用方法も比較的簡単で機能が豊富なので重宝します。負荷テストサービス自体は国内・国外を問わずにほかにも結構ありますので興味のある方はググってみてください。

loader.io

loader.io

loaderのフリープランでは実行時間1分間までですが1回のテストで10,000回まで可能なので短時間で負荷をかけたい時に重宝します。

BlazeMeter

www.blazemeter.com

BlazeMeterのフリープランは仮想ユーザ50まで利用可能で合計で25,000リクエストまで実行可能です。実行時間の制限が長めに設定できるので時間をかけて負荷をかけたい場合に適しています。

Azure Function環境構築

ここでは下記のプランでFunctionsを作成して比較したいと思います。詳細な環境構築手順はここでは割愛します。

  • Consumptionプラン(従量課金プラン)
  • Premiumプラン

f:id:kingkino:20191213222128p:plain

ConsumptionプランとPremiumプランはイベントドリブンスケーリングに対してAppServiceは手動または計画的及び負荷ベースの自動スケーリングになるのでここでは比較対象から外します。スケール可能なインスタンス数やタイムアウト時間はプラン別に違います。詳細は下記の比較表を参照してください。

docs.microsoft.com

各プランでFunctionsを作成するときは「Applicaiton Insight」を必ず作成するようにしてください。スケーリングの可視化を行うためにApplicaiton Insightの「ライブメトリックス ストリーム」を利用します。

f:id:kingkino:20191213222232p:plain

Functionsを作成したら任意に作成したHTTP Triggerベースのプログラムをデプロイしておきます。ここではC#で作成した下記のサンプルプログラムをデプロイしています。サンプルプログラムは現在時刻をタイムゾーン別に一覧で返却するプログラムです。

[FunctionName("GetAllTimeZone")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation($"START:${DateTime.Now}");
               
    var times = new StringBuilder();
    var now = DateTime.Now;
    var jstTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
    var jstDateTimeOffset = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, jstTimeZoneInfo.BaseUtcOffset);

    foreach (TimeZoneInfo timeZone in TimeZoneInfo.GetSystemTimeZones())
    {
        try
        {
            var zone = TimeZoneInfo.FindSystemTimeZoneById(timeZone.Id);
            times.AppendLine($"{TimeZoneInfo.ConvertTime(jstDateTimeOffset, zone)}:[{timeZone.Id}]");
        }
        catch { }
    }

    log.LogInformation($"END:{DateTime.Now}");

    return (ActionResult)new OkObjectResult($"{times.ToString()}");
}

負荷テストサービスの設定

loader.ioに登録をしたらテストを実施するFunctionsのエンドポイントを登録します。

f:id:kingkino:20191213223215p:plain

エンドポイントを登録するとターゲットホストを設定するためのトークンが発行されます。これはDDOS攻撃対策のために設定するようです。負荷テストは言い換えてみれば大量アクセスによる攻撃をかけているのと同じですからこういう対策が必要なのでしょうね。トークンは控えておいてください。

f:id:kingkino:20191213224811p:plain

Azure Functionsの画面でプロキシを選択して設定をしていきます。

  • 名前は任意の文字列にしてください
  • ルートテンプレートに先に取得したトークンを設定します
  • 許可されているHTTPメソッドも任意に設定します、ここではGETのみ指定します
  • レスポンスヘッダーに「Content-Type」:「text/plain」を設定します
  • レスポンスボディに先に取得したトークンを設定します

各種入力が完了したら保存してください。

f:id:kingkino:20191213224840p:plain

loaderの画面にもどってverifyボタンを押下して下図のようにSuccessメッセージが表示されれば成功です。エラーが出る場合は設定に誤りがないか確認してください。

f:id:kingkino:20191213224329p:plain

作成したFunctionsプロキシの設定は「App Service Editor」で確認することができます。

f:id:kingkino:20191214142136p:plain

proxies.jsonが作成されているので設定の中身が確認できます。

f:id:kingkino:20191214142609p:plain

FunctionsをVisual StudioVS Codeからデプロした時にプロキシの設定が消失してしまうので、外部からデプロイする時はプロジェクトのルートディレクトリに「proxies.json」を作成してプロキシの上図の設定をプロジェクトに含めるようにしてください。Visual Studioで作業するときは作成した「proxies.json」のプロパティ「出力ディレクトリにコピー」でコピーするように設定を変更してください。この設定を忘れるとハマります。ちょっとめんどくさいですけどすべてのプランのFunctionsにこの手順をあてていきます。

これで負荷テスト環境が作成できましたのでいよいよスケーリングの確認を行います。

スケーリングの確認

Application Insightのライブメトリックス ストリームを利用してそれぞれのプランのスケーリングを確認します。

すべて同じ条件でテストを実施します。loaderで下図のように1分10,000回のリクエストでテストを実施するように設定します。1リクエストに対するTimeOutは初期値の10秒でテスト終了用のエラー率の閾値も初期値の50%とします。リクエスト先は先に作成したFunctionsのURLです。

f:id:kingkino:20191214160936p:plain

Consumptionプラン

Application Insightのライブメトリックス ストリームを見てみましょう。Consumptionプランは0スタートなので何も処理が走っていないときはライブメトリックス自体がNo Availableな状態です。

f:id:kingkino:20191214161721p:plain

負荷テストを実行してみましょう。loaderのテストの管理画面の右上にある実行ボタンか編集画面のRunTestボタンから実行ができます。実行中にメトリックスストリームを確認すると徐々にメトリックスにリクエストの情報が流れ始めてサーバインスタンスが立ち上がるのがわかると思います。

f:id:kingkino:20191214163030p:plain

処理が完了して何も操作が発生しなくなると最終的にスケールインしてサーバインスタンス数は0になります。

f:id:kingkino:20191214163101p:plain

下図は負荷テスト実行後の計測結果です。Response TimesのMax値を見ると10秒かかっています。これはコールドスタートが発生しているため初速に時間がかかっていることを表しています。またTimeoutが24件発生してますが、これはコールドスタート時に処理されずに発生しています。

f:id:kingkino:20191215154736p:plain

試しに0インスタンススタートではなくサーバインスタンスが起動している状態で負荷テストを行うと下図のようにResponseTImeに幅がなくなります。Timeoutも発生していないのでいったん稼働してしまえば安定して稼働していることがわかります。

f:id:kingkino:20191215155403p:plain

Premiumプラン

Functionsのスケール設定でスケールアウトの設定をします。ここでは最小インスタンスを2、最大バーストを4、ウォームアップインスタンスを2に設定します。ウォームアップインスタンスは最小インスタンスより大きな数を設定することはできません。 ウォームアップインスタンスがあるためコールドスタート問題を回避することができます。

f:id:kingkino:20191214165418p:plain

ライブメトリックス ストリームを確認するとウォームアップされたインスタンスが立ち上がっていることがわかります。これはウォームアップインスタンス数分立ち上がっているので、ウォームアップインスタンスを1にすればサーバーインスタンス数は1になります。ウォームアップインスタンスを0にすれば0インスタンススタートとなりコールドスタートになります。

f:id:kingkino:20191214165520p:plain

負荷テストを実施するとConsumptionプランと同様に徐々にスケールアウトし始めます。Consumptionプランと違うのはスケールアウトのリミット数を最大バースト数で4に設定しているためサーバインスタンス数が4までしかスケールアウトしないことです。

f:id:kingkino:20191214170348p:plain

処理が完了してしばらくするとサーバインスタンス数が最小インスタンス数の2までスケールインします。

f:id:kingkino:20191214171247p:plain

下図は負荷テスト実行後の計測結果です。ウォームアップインスタンスがいるためスムーズに処理が開始されていることがわかります。また、ResponseTimeも安定しています。

f:id:kingkino:20191215160637p:plain

試しにウォームアップインスタンスを0に設定して実行してみました。Consumptionプランの0インスタンススタートと同じ結果になるかと思ったのですがインスタンス起動が遅いようで結果は測定エラーになりました。ResponseTimeOutが大量に発生してloaderのテスト強制終了閾値にひっかかってしまったようです。

f:id:kingkino:20191215162721p:plain

TimeOut秒数を10秒から20秒に変更して実行してみたところ計測エラーにならずテストが開始されましたが、0インスタンススタートからの起動に15秒ほどかかっているようです。Consumptionプランのコールドスタートより遅いのはAppServiceとFunctionsの起動が発生しているからなのかもしれません。

f:id:kingkino:20191215162955p:plain

まとめ

ConsumptionプランやPremiumプランは自動スケーリングするので実際にどのようにスケールがコントロールされているのかをあまり意識せずに利用されている方も多いと思います。スケールを意識することで実装や処理時間、処理にかかる費用について意識することが出来るようになると思います。

Premiumプランは最大スケールにキャップを付けれるのでスケールのコントロールがしやすく計画に利用できます。またVNET Integration等の従来のApp Serviceプランではできなかった機能も増えているので機会があればApp Serviceプランからの乗り換えを考慮していこうと思っています。