本投稿はAzure Advent Calendar 2019の16日目の投稿になります。
先月開催されたMicrosoft Ignite 2019でPremiumFunctionsがGAしたので、ここでAzure FunctionsのConsumptionプランとPremiumプランに負荷テストを実施してスケーリングの状態を確認する方法につてい記載していきたいと思います。
※この投稿は2019年12月16日時点の情報になります。
負荷テストサービス
スケーリングを確認するためには負荷をかける必要があるため、まずは負荷テストサービスの設定を行います。
今回はlaoder.ioを利用します。BlazeMeterはJMeterのクラウドベース負荷テストツールです。どちらも無料プランが提供されていて利用方法も比較的簡単で機能が豊富なので重宝します。負荷テストサービス自体は国内・国外を問わずにほかにも結構ありますので興味のある方はググってみてください。
loader.io
loaderのフリープランでは実行時間1分間までですが1回のテストで10,000回まで可能なので短時間で負荷をかけたい時に重宝します。
BlazeMeter
BlazeMeterのフリープランは仮想ユーザ50まで利用可能で合計で25,000リクエストまで実行可能です。実行時間の制限が長めに設定できるので時間をかけて負荷をかけたい場合に適しています。
Azure Function環境構築
ここでは下記のプランでFunctionsを作成して比較したいと思います。詳細な環境構築手順はここでは割愛します。
- Consumptionプラン(従量課金プラン)
- Premiumプラン

ConsumptionプランとPremiumプランはイベントドリブンスケーリングに対してAppServiceは手動または計画的及び負荷ベースの自動スケーリングになるのでここでは比較対象から外します。スケール可能なインスタンス数やタイムアウト時間はプラン別に違います。詳細は下記の比較表を参照してください。
各プランでFunctionsを作成するときは「Applicaiton Insight」を必ず作成するようにしてください。スケーリングの可視化を行うためにApplicaiton Insightの「ライブメトリックス ストリーム」を利用します。

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のエンドポイントを登録します。

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

Azure Functionsの画面でプロキシを選択して設定をしていきます。
- 名前は任意の文字列にしてください
- ルートテンプレートに先に取得したトークンを設定します
- 許可されているHTTPメソッドも任意に設定します、ここではGETのみ指定します
- レスポンスヘッダーに「Content-Type」:「text/plain」を設定します
- レスポンスボディに先に取得したトークンを設定します
各種入力が完了したら保存してください。

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

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

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

FunctionsをVisual StudioやVS Codeからデプロした時にプロキシの設定が消失してしまうので、外部からデプロイする時はプロジェクトのルートディレクトリに「proxies.json」を作成してプロキシの上図の設定をプロジェクトに含めるようにしてください。Visual Studioで作業するときは作成した「proxies.json」のプロパティ「出力ディレクトリにコピー」でコピーするように設定を変更してください。この設定を忘れるとハマります。ちょっとめんどくさいですけどすべてのプランのFunctionsにこの手順をあてていきます。
これで負荷テスト環境が作成できましたのでいよいよスケーリングの確認を行います。
スケーリングの確認
Application Insightのライブメトリックス ストリームを利用してそれぞれのプランのスケーリングを確認します。
すべて同じ条件でテストを実施します。loaderで下図のように1分10,000回のリクエストでテストを実施するように設定します。1リクエストに対するTimeOutは初期値の10秒でテスト終了用のエラー率の閾値も初期値の50%とします。リクエスト先は先に作成したFunctionsのURLです。

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

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

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

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

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

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

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

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

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

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

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

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

まとめ
ConsumptionプランやPremiumプランは自動スケーリングするので実際にどのようにスケールがコントロールされているのかをあまり意識せずに利用されている方も多いと思います。スケールを意識することで実装や処理時間、処理にかかる費用について意識することが出来るようになると思います。
Premiumプランは最大スケールにキャップを付けれるのでスケールのコントロールがしやすく計画に利用できます。またVNET Integration等の従来のApp Serviceプランではできなかった機能も増えているので機会があればApp Serviceプランからの乗り換えを考慮していこうと思っています。
コメント