App Service を正しく使うために
App Service を本番環境で正しく使うためには App Service Plan の正しい理解が欠かせません。しかし「App Service Planとはマシンのスペックのようなもの」というざっくりした説明が多いためか、細かい挙動が理解されなかったりトラブルの原因になっているケースをよく見ます。なのでここでキーポイントを書いておこうと思います。
※ Functions の Consumption Plan (従量課金) にはここでは触れません。
App Service Plan == アプリを入れる箱?
新しく Web アプリを作るときには必ず App Service Plan を選択するか、新たに作成する必要があります。これは、Azure にどのようなスペックのハードウェアを使いたいかを指定するためです。はい、ざっくりした説明ですね。ここからもっと詳しく見ていきます。
インスタンスとは?
ひらたく言えば「マシン」です。VMですね。
Free プランと Basic プラン以上との違い
Free プランでは、アプリは他のユーザのアプリと同じインスタンスで実行されます(データが見えることはありません)。つまり別のアプリの影響をもろに受けるということです。Basic 以上では専用のインスタンスが割り当てられますので、他のユーザが勝手に使うことはありません。インスタンスが割り当てられる前にディスクがreimageされますので、他のユーザのデータが見えることはありませんし、自分のデータが他のユーザから見えることもありません。
複数アプリを入れるとどうなるか
ひとつの App Service Plan に複数のアプリを入れるとどうなるでしょうか。まずインスタンス数が1の場合、すべてのアプリが同じマシン上で実行されます。別の言い方をすると、すべてのアプリが一つのマシンのCPU、メモリ、ネットワーク帯域(以後単に計算資源と書きます)を共有します。つまり、ある1つのアプリが大量にメモリを使う場合、他のアプリが使えるメモリ量はその分少なくなります。
Kudu
Kudu (ポータル上では Advanced Tools)は App Service にデプロイしたり簡易診断するのに重宝する便利な機能です。この Kudu は一つの独立したウェブアプリなので、デプロイしたり Kudu セッションを使うとアプリ本体と Kudu アプリの2つが計算資源を共有した状態になります。
もし3つのアプリを1つの App Service Plan で動かし、それらすべてに同時にデプロイしたらどうなるでしょうか。Kudu は各アプリごとに個別に実行されますので、合計で6アプリが計算資源を共有していることになります。単純に倍の数のアプリが起動していると考えてください。
スロット
App Service の便利な機能の1つ、スロット機能。これは要するに同じ名前の別アプリに他なりません。したがって、例えば3つのスロット(Production, Staging, dev)を持っている場合、それは3個のアプリが計算資源を共有していることを意味します。Kudu もそれぞれ別々に起動します。
例
ある App Service Plan に3つのアプリ(app1, app2, app3)を作りました。app1とapp2には2つずつのスロット(Production, Staging)があり、app3には3つのスロット(Production, Staging, dev)があります。3つのアプリすべての Kudu を常に立ち上げています。
この場合、(2 + 2 + 3) * 2 = 14 個のw3wpが計算資源を共有しています。ということは、このうちのどれか1個が何らかの極端に大きなI/Oリクエストを発行、あるいは多量のSNATポート資源を消費した場合、他の13個が一斉に影響を受けることになります。
w3wpの自動的な再起動について
アプリ、正確には w3wp.exe プロセスは様々な理由から自動的に再起動されます。複数アプリが起動中のとき、これらのプロセスはほぼ同時に再起動されます。Kudu が起動中だった場合はそれらも再起動されます。このすべての同時再起動が必要とする計算資源はアプリ数およびアプリごとに使う計算資源に依存します。分かりやすく書くと、重いアプリが沢山実行中の場合、再起動が遅くなります。こう書くと「そりゃそうだろ」と思うかも知れませんが、実際1個の App Service Plan に沢山アプリを詰め込んで「起動が遅い!」というケースが多々あるんです。
プラットフォームが全部を一斉に起動させるのが悪い、というのはもっともなのですが、プラットフォーム側からするとそれしか方法がないです。一つずつ順番に起動させようとした場合には、起動順序によっては期待しない結果になります。例えば最初に起動させたアプリがとても時間がかかると、他のアプリがいつまでも待たされることになります。
複数インスタンスの場合
複数のインスタンスを立ち上げた場合の挙動を列挙します。
インスタンス割り当て
すべてのインスタンス上で、すべてのアプリが起動します(デフォルトの挙動)。ただし同時に全部が起動するわけではなく、HTTPリクエストが到達した時点で起動シーケンスが発動します。いわゆるコールドスタートですね。たとえば1インスタンスのみの状態から2インスタンスにスケールアウトしたとします。この場合、この新しいインスタンスにいつトラフィックが流れ始めるかは正確には規定されていません。起動後すぐかも知れませんし、ずっと後かも知れません。トラフィックの分配はFront End (App Service内のリバースプロキシ)が各インスタンスの負荷を見て決めていますので、各インスタンス毎に正確に 1/n に分配されるわけではありません。
ファイルシステム
すべてのインスタンスが同じディレクトリを参照します。Local Cache を有効にしていない場合は、あるインスタンスからの書き込みは即座に他のインスタンスからも参照可能です。
ただし、そのディレクトリを提供しているファイルサーバが切り替わった際、すべてのアプリが一斉に影響を受けます。ダウンタイム自体は極力短くなるようないろいろな工夫が入っているものの、w3wpは再起動されることが多いです。「ユーザが予期しない再起動」に分類されるものです。この再起動が起きると、前述したように一斉に全アプリがコールドスタートします。この際に大量の計算資源へのアクセスが集中しがちになり、長い起動時間、502、503エラーなどにつながることがあります。
この影響を完全に避けるには、別アプリを別のリージョンに作ってファイルシステムも分散させる必要があります。したがって Traffic Manager や Azure Front Door 等を使用してトラフィックを制御することになります。
Health Check
しばらく前から Health Check 機能が使用可能になっています。2020年8月にはGAしました。公式ドキュメントはこちら: リンク
これを有効にすると、5xxエラーを連続して返すインスタンスにはリクエストを送らなくなります。一旦リクエストを送らなくなると、以降は内部 Front End からだけは定期的にHTTPリクエストを送り続け、200を返すようになったら再び外部からのリクエストが送られるようになります。
便利な機能ですが、一つ注意点があります。例えば2インスタンスが割り当ている状態で1つのインスタンスが 5xx エラーを返しているとしましょう。そのインスタンスにはトラフィックは送られませんが、代わりに残っているインスタンスがすべてのトラフィックをさばき続けなければなりません。あくまでトラフィックを制限しているだけで、App Service Plan 自体がスケールしたわけではないことに注意してください。
App Service Plan の賢い使い方
以上に書いた特徴などを踏まえると、以下のような使い方が高い安定性を得られてお勧めです(2020年9月時点)。もちろん予算と相談の上でのトレードオフが必要になりますが。
- 高いSLAが必要な、ビジネス・クリティカルなアプリは単独、あるいは少数のアプリだけを入れたApp Service Plan に分離する。テスト用アプリ等とは混在させない。
- Health Check を有効にする。
- リージョンごとに同じアプリをデプロイし、Traffic Managerや Azure Font Door 等でリスク分散する。
まとめ
他にも Application Insights、Azure Monitor などなど書き出すときりがありませんが、とりあえずこの辺で。あと、すべては公式ドキュメントに書いてありますので、よく読みましょう!!