しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

後悔しないための Azure App Service 設計パターン (2020 年版)

Azure App Service (Web Apps) がリリースされて 6 年、情報のアップデートを行いつつ気になった情報は適当にブログに書くという日々ですが、Regional VNET Integration や Service Endpoins が使えるようになって設計に大きな変化が出るようになったのでまとめます。

最近は Microsoft で HackFest を行うことも多いのですが、App Service をこれから使い始めたいという場合に、失敗しない構成を共有したい、知ってほしいという意図もあります。多いですが中身は単純です。

アーキテクチャという観点では App Service だけで収まるものではないのですが、今回は App Service 周りに限って書いています。単純に App Service は機能がめちゃくちゃ多いというのもあります。

ひとまず自分が思いついたものを一通り書いたはずですが、足りないものがあれば後で追記するかもしれません。一部 Preview な機能も混ざってはいます。

基本設定

64bit Worker は必要な場合のみ利用する

最初の方で地味に迷うのが 32bit / 64bit の設定だと思います。デフォルトでは 32bit になっていますが、大量にメモリを消費することがわかっているアプリケーション以外では 32bit のままで問題ありません。

f:id:shiba-yan:20200112184344p:plain

64bit にすると扱えるメモリは増えますが、同時にメモリの使用量も増えます。

App Service は 2 コア以上の App Service Plan を使わない限り、大体は 4GB 以下のインスタンスなのを忘れないようにしましょう。あとアプリケーションだけではなく Kudu も同時に 64bit になるので注意。

FTP / Web Deploy をオフにする

FTP / FTPS の無効化はセキュリティ的な観点でも重要ですが、FTP / Web Deploy といったデプロイ方法は、今となってはアトミックではなくバージョン管理もされない悪手です。

利用できる状態にしていると、簡単にアプリを壊すことが出来るのでオフにします。

本当に最初期の開発ならともかく、それ以降でも Visual Studio から Web Deploy を使って新しいアプリケーションのデプロイを行うのは、あり得ない状態だと認識してください。

Always on を有効化する

App Service はアーキテクチャ的にコールドスタートは発生しうるものですが、Always on を有効化することでトラフィックが無い時にインスタンスが落ちるのを防ぐことが出来ます。

f:id:shiba-yan:20200112193039p:plain

これで常にウォームスタートになるので、いつトラフィックが来ても素早く応答することが可能です。

ウォームアップリクエストを受け取ったタイミングで、コネクションやキャッシュといった必要な初期化処理を行っておくと更に効果的です。

ARR affinity をオフにする

Session Affinity のために自動的に App Service がクッキーを発行して、2 回目以降も同じインスタンスにルーティングする仕組みですが、自動的にインスタンスの入れ替えが発生する App Service とは根本的に相性が悪いです。全てのリクエストにクッキーが付くので効率的にも微妙です。

ステートレスなアプリケーション設計ならば不要なのでオフにします。

アプリケーションがセッションを利用している場合には、ちゃんと Redis Cache や Cosmos DB といったインスタンス間での共有ストアを用意して、リクエストを受けたインスタンスに依存した作りにしないことです。

当然ながら ARR Affinity をオンにしていると、ロードバランサーは偏ったルーティングを行うこともあるので、一部のインスタンスに負荷が集中してしまうことも容易に考えられます。

HTTP/2 の有効化を検討する

HTTP/2 を有効化したからと言って全てが改善するわけではないですが、App Service では HTTP/2 と HTTP/1.1 が簡単に切り替え出来るのでテストが行いやすいです。

f:id:shiba-yan:20200112192450p:plain

実際のところケースバイケースという話が多いので、実際のアプリケーションでベンチマークを行っていくしかないですが、試してみる価値はあるでしょう。

Health Checks の有効化を検討する

プレビュー中の機能ですが、Health Checks を有効化すると App Service が設定されたエンドポイントへのリクエストを行い、失敗が続いているインスタンスは LB から切り離してくれるようになります。

LB から切り離されたインスタンスは、リクエストが成功すると再び LB に戻されます。この動作によって不良インスタンスがリクエストを処理するのを最小限に出来ます。

切り離されたインスタンスが 1 時間失敗し続けている場合には自動でリサイクルが行われて、完全に最初からアプリケーションの立ち上げが行われます。

タイムゾーンの設定を検討する

App Service に限らずパブリッククラウドを使っていて戸惑うことが多いのが、タイムゾーンが UTC で設定されていることだと思います。

日付をタイムゾーン込みで扱うのが理想ではありますが、国内のみ対象にした場合には割り切ってタイムゾーンを変更してしまうのが簡単です。

昔はタイムゾーンの設定は出来なかったので +9 時間するコードがよく書かれていましたが、今は App Service のアプリケーション単位での切り替えが出来るようになっています。

認証が必要な場合は App Service Authentication (Easy Auth) を利用する

関係者のみ見れるようにしたいステージングサイトや、本番向けでも認証が必要な場合には App Service Authentication (Easy Auth) を使うのが非常に簡単です。

後述する Deployment slot と組み合わせると、ステージングサイトを簡単に保護できるようになります。

f:id:shiba-yan:20200112190930p:plain

昔は Basic 認証を行うモジュールを個別にインストールするケースもありましたが、Easy Auth を使うと数クリックで Azure AD に登録されているユーザーのみ閲覧可能に出来ます。

ドキュメントには記載されていませんが、OpenID Connect に対応しているので Azure Portal に設定項目がないプロバイダーでも対応可能です。Azure Functions との組み合わせが特に強力です。

アプリケーション設定 / 接続文字列

Web.config / appsettings.json にべた書きしない

開発環境用の設定や接続文字列であれば問題ないケースは多いですが、本番環境向けの設定は Web.config や appsettings.json といったリポジトリに入るファイルには書かずに App Service 側でオーバーライドします。

たまに全く App Settings / Connection Strings を使っていないケースもありますが、ファイルに書いていると簡単に事故るので分けて管理しましょう。

App Service に設定する方法は ARM Template や Terraform といった IaC を利用すると、コードで分かりやすく定義でき更にバージョン管理もされるのでお勧めです。

アプリケーションレベルで対応する場合には、Key Vault や App Configuration を直接利用するという方法もありますが、後述する Managed Identity と組み合わせても最低限の設定は必要となります。

Key Vault Reference の利用を検討する

SQL Database や Azure Storage などの接続文字列を個別に App Settings に追加するのではなく、Key Vault の Secret として追加して App Service からは Key Vault を参照する形にすると安全に一元管理が行えます。

一部制約があり、現在は Key Vault の Service Endpoints を有効化すると、App Service が VNET Integration を使っていてもアクセスできなくなります。

Managed Identity の利用を検討する

理想としては Managed Identity を使って App Service 単位でアクセス可能なリソースを割り当てることで、キーの管理自体を無くしてしまうことです。寿命の短い Bearer Token を使った認証になるので安全です。

いくつかの Azure サービスが Managed Identity を使ったアクセスに対応してきていますが、クライアントが Managed Identity に適した設計になっていないケースもあるので、まだ全面導入は難しい部分があります。

ネットワーク設定

HTTPS を常に有効化する

世界的に常時 HTTPS での通信は当たり前になっています。App Service はデフォルトでオンになっていないですが、チェックを入れるだけで常に HTTPS へリダイレクトさせることが出来ます。

f:id:shiba-yan:20200112191031p:plain

TLS バージョンの選択も出来ますが、ここはデフォルトの TLS 1.2 で普通は問題ありません。こちらも世界的に TLS 1.0 / 1.1 はセキュアではないので、TLS 1.2 以降を利用する流れとなっています。

Regional VNET Integration を利用する

以前から VNET Integration は実装されていましたが、Point-to-Point VPN を使っていたので VNET ゲートウェイが必要で Service Endpoints や ExpressRoute が使えないなど制約が非常に多かったです。

新しい Regional VNET Integration ではそういった制約はなくなっています。

f:id:shiba-yan:20200112191246p:plain

ASE や VM のように専用のインスタンスが VNET 内に直接配置されるわけではなく、あくまでもマルチテナントでの実行となりますが、これまでは ASE でのみ実現可能だったアーキテクチャをマルチテナントの App Service でも構築出来るようになりました。

IP 制限ではなく Service Endpoints を利用する

App Service では元から IP アドレスベースでのアクセス制限は行えましたが、Service Endpoints が追加されたことにより特定の VNET の Subnet からの通信のみ許可するといった設定が行えるようになっています。

フロントエンドに用意した App Service が VNET を経由して、Service Endpoints でアクセス元を制限したバックエンドの App Service を実行するという構成も簡単に構築できます。

Service Endpoints は既に数多くの Azure サービスで対応しているので、App Service で VNET Integration を使っていればアクセス元の制御が簡単に行えるようになっています。

特に SQL Database や Cosmos DB といったストレージ系のサービスでは IP アドレスでのアクセス制限ぐらいしか出来なかったのが、Subnet 単位で許可出来るようになったのはかなり大きいです。

VNET Integration と合わせて積極的に利用していきましょう。IP アドレスでの制限は全て捨てます。

CDN / Front Door の導入を検討する

App Service の前に CDN や Front Door を置くことで、コンテンツ配信の最適化をすることが簡単に行えるようになっています。設定も Azure Portal から行えるので迷うことは無いでしょう。

最近のアプリケーションは静的ファイルが非常に多くなってきているので、適切なキャッシュ戦略と CDN を使った最適化は必須になってきています。

f:id:shiba-yan:20200112191116p:plain

特に Front Door は CDN と L7 LB を組み合わせたサービスなので、アプリケーションの負荷分散や DR にも使えます。ルーティングも柔軟に行えるので、アーキテクチャを変更した時にも Front Door で対応できます。

静的なコンテンツの配信には CDN / Front Door が圧倒的に有利なので、早めに導入を検討してお行きたいサービスです。Traffic Manager とは異なり Front Door は L7 LB なのでバックエンドの切り替えもすぐです。

スケーリング設定

本番環境では Premium V2 を利用する

App Service Plan はいくつも Tier があって最初は悩むかもしれませんが、最低でも Premium V2 の P1v2 を選んでおきましょう。Premium V2 はコストとパフォーマンスのバランスが取れています。

f:id:shiba-yan:20200112192306p:plain

今は Premium V2 を使う予定はないからといって、Standard や Basic で作ってしまうと Regional VNET Integration が利用できない Scale unit に当たってしまうこともあるので、最初は必ず Premium V2 を指定してインスタンスを作るのが正解です。

作成後は Standard や Basic に変更しても問題ありませんが、Premium V2 とそれ以外の Tier 間の変更には Outbound IP アドレスの変更が伴うので、その点だけ注意が必要です。

今は変更のタイミングで警告メッセージが表示されるようになっていますが、IP 制限に使っている場合には問題となるので VNET Integration と Service Endpoints を使っておくと安心です。

オートスケーリング設定を検討する

今の App Service のオートスケーリング設定は Azure Monitor ベースで、様々なメトリックを元にルールを作成できるようになっています。それ以外にも曜日での固定スケーリングルールも作成できます。

App Service はスケーリングが非常に高速に行われるので、オートスケーリング設定との相性が非常に良いです。常時同じアクセスが発生するアプリケーション以外ではオートスケーリングを検討する価値があります。

Consumption / Premium Plan を活用する

イベントドリブンなアプリケーションの場合は Azure Functions と Consumption / Premium Plan を使って、イベントベースでのスケーリングを利用するようにします。

App Service Plan を使うよりスケーリングが早く、インスタンス数の上限も多いため大量のイベントを簡単に処理できるようになります。Premium Plan を使うとコールドスタートを避けつつも、イベントベースでのスケーリングが利用できるので検討しましょう。

アプリケーションのデプロイ

最低でも Zip Deploy を利用する

App Service には複数のデプロイ方法が用意されていて、FTP / Source Control / Web Deploy / Zip Deploy などがありますが、カジュアルな Web サイト以外では Zip Deploy を利用しましょう。

FTP / Web Deploy はデプロイがアトミックではないので、一部のファイルがデプロイ失敗すると整合性が破綻してアプリケーションが正しく動作しなくなります。

よくデプロイで発生する問題が古いファイルが残っていて、良くわからないエラーが発生することですが、FTP / Web Deploy を使っているから発生する問題と言えます。

特に Visual Studio からデプロイする際に使われる Web Deploy は今となってはパフォーマンスも悪く、デプロイに失敗しやすいのでメリットがほぼありません。CI を導入して Zip Deploy に切り替えると安定します。

Run From Package の利用を検討する

Zip Deploy ではアップロードされた Zip ファイルを展開して wwwroot 以下にデプロイする仕組みですが、Run From Package を使うと zip をそのままマウントするので、デプロイ時間の短縮や起動パフォーマンス改善につながります。もちろん単一ファイルをマウントするのでアトミックです。

Consumption / Premium Plan で Azure Functions を利用する場合には必須、App Service で Web アプリケーションを利用する場合にはローカルストレージへの書き込みを行わない場合に利用を検討出来ます。

App Service はローカルストレージのパフォーマンスが良くはないので、状態は外部のストレージに持たせる設計が多いはずです。その場合は Run From Package が使えます。

Deployment slot を使って Swap でのリリースを行う

開発中はともかく、本番環境へのデプロイ時には動作している App Service に対して直接デプロイするのではなく、別のスロットへのデプロイを行った後にスワップでリリースを行うようにします。

スワップのタイミングで App Service がウォームアップを行ってくれるので、初回のリクエストが遅いといった問題を回避しつつ、新バージョンでエラーが発生した場合には再スワップで元のバージョンに戻す運用が簡単に実現できます。

スワップ時のウォームアップへの対応や App Settings に追加すると良いキーなどいろんな情報がありますが、Ruslan のブログに書いてある内容が非常に参考になります。

Azure Pipelines / GitHub Actions などを利用して自動化する

初期の開発中はともかく、基本的には App Service へのデプロイは Azure Pipelines や GitHub Actions などの CI SaaS を使ってビルドとデプロイを自動化します。

App Service へのデプロイ自体は Task や Action が用意されているので簡単です。

Visual Studio や VS Code などを使って手動でデプロイするのはバージョン管理出来ていないですし、デプロイに必要な権限すら適切に管理出来てないことになります。

権限管理が出来ていないのはあり得ないので、予め CI 前提で考えていく必要があります。App Service のデプロイに必要な権限は実際かなり強いです。

リソース構築のコード化 (IaC)

ARM Template / Terraform を使って構築する

App Service は機能が多いので、その分設定項目も非常に多いです。なので複数の環境を用意する際に Azure Portal から手作業で作成するとミスが発生したり、設定が微妙に異なるという問題が簡単に発生します。

そういった問題を避けるためにも、最初から ARM Template や Terraform を使ってコード化して管理します。

App Settings や Connection Strings に関しても、ARM Template や Terraform を使うと管理できるため、実行時に動的に変更する必要がないものは IaC に寄せた方が管理しやすくなります。

Terraform を使う場合は、それ自体を Azure Pipelines などを使って自動デプロイするように構築します。

モニタリング / 診断

Application Insights を有効化する

アプリケーションに直接 Application Insights SDK を入れている場合はキーの設定だけ良いですが、それ以外の場合は Azure Portal から有効化するか、必要な App Settings を追加することで有効に出来ます。

f:id:shiba-yan:20200112194210p:plain

Application Insights を利用したモニタリングと実践については去年の de:code で話しているので、使用したスライドを参考にしてもらえれば良いかと。

モニタリング系は問題が発生してからでは遅いので、きっちりと最初から設定しておく必要があります。

特に Application Insights は 5GB まで無料の容量課金なので設定しない手はありません。

Azure Monitor を使ったログ転送を有効化する

歴史的経緯から App Service のログはローカルストレージや Azure Storage にしか書き出せませんでしたが、Azure Monitor との統合が行われて他のサービスと同じ機能が使えるようになりました。

f:id:shiba-yan:20200112194120p:plain

ファイルとして書き出すだけではなく、Log Analytics や Event Hub への書き出せるので、KQL を使って複雑な条件で絞り込むことも簡単に出来るようになっています。クエリを簡単に実行できるのは重要です。

Event Hub を使えばストリームでログを処理できます。異常検知を行いたい場合にも使えそうです。

App Service Diagnostics を積極的に活用する

App Service は組み込みの診断機能が非常に優秀で機能が豊富です。Azure Portal から "Diagnose and solve problems" を選ぶと対話式に診断を開始できます。

様々なメトリックから問題を検出してくれるので、App Service で動かしているアプリケーションに問題が発生した場合は、必ず最初に試しておくことを推奨しています。

特に TCP Connections は SNAT 絡みで問題となるケースが多いので便利です。一部の診断は Consumption で使えない制約がありますが、一時的に App Service Plan 上に移動してしまえば問題ありません。