しばやん雑記

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

App Service Static Web Apps の仕組みを探る(非公式)

Build 2020 では App Service に関する話は非常に少なかったですが、唯一大きなリリースとしては Static Web Apps がありました。名前の通り静的コンテンツをホスティングするためのサービスですが、同じドメインで API (Azure Functions) が付いてくるのが特徴です。

詳しくは Daria の公式ブログや三宅さんの記事を読んでおいてください。このエントリではその辺りの紹介は全くしないので、知識がある前提で進めていきます。

自分は Static Web Apps がどのように構築されているのかのが気になるので、現在分かっていて触れる範囲で内部アーキテクチャを探ってみました。当然ながら非公式ですし、正しい保証はありません。

Static Web Apps でお手軽ホスティングしたい人には必要ないエントリです。普通は気にせずに GitHub からサクサクデプロイして配信するだけで良い部分です。

TL;DR

  • エンドポイントには App Service (Windows, ASE) が使われている
  • API 部分は Linux Consumption で動作していて、エンドポイントからリライト
    • Linux Consumption は Run From Package で実行されている
  • Static Web Hosting 部分はエンドポイントから恐らく Blob Storage などへリライト
  • デプロイ用 GitHub Actions では app / api それぞれでビルドして zip を作成
    • 良くある Push 型ではなく Pull 型が使われている
  • Global Content Distribution = 全世界の App Service にデプロイ + Traffic Manager でルーティング
    • ただし API 部分は作成時に指定したリージョンのみ

基本的な設定

Static Web Apps は Global Content Distribution を謳っているので、リソース作成時のリージョン指定は何の意味があるのか、冷静になって考えると疑問です。とりあえず一番遠そうな East US 2 に作成しました。

現時点では API は単一リージョンのデプロイになるので、その指定と考えておけば良いです。

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

リソース作成後に設定から弄れる内容は結構少ないです。Static Content 向けなので当たり前感はありますが、Azure Functions 向けに App Settings の追加だけは行えるようになっています。

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

ARM レベルだともうちょっと弄れるのではと思いましたが、特に違いはありませんでした。Private Link 対応は比較的近いうちに来るんだろうなと思わせる程度です。

プラットフォーム

とりあえず最初に nslookup でホスト名を調べるのは義務という感じなので、azurestaticapps.net なホスト名に対して名前解決を行います。

この時点で CDN が使われておらず、Traffic Manager と App Service Environment が使われていることが分かりますね。p.azurewebsites.net は External ASE を作成した時に付けられるホスト名だからです。

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

更に East US 2 に作成したはずなのに East Asia の ASE のアドレスが返ってきていることも分かります。各リージョンに存在する ASE に自動でデプロイされていて、Traffic Manager が近いものを返しているようです。

今日に App Service Team への Ask the Team というセッションがあったので目いっぱい質問をしてみたところ、現時点では世界中のリージョンにデプロイされていて、GA までには CDN が追加されるようです。

Static Content

少なくとも App Service が使われているのは分かったので、Postman などを使って静的コンテンツに対してリクエストを投げて、レスポンスヘッダーを確認してみます。

特徴的なのは ServerX-Powered-By ですね。明らかに Windows Server 2016 と IIS 10.0 がリクエストのどこかに紛れ込んでいます。全ての App Service はフロントに ARR 3.0 がいますが、今は特殊なヘッダーを付けないので無視して良いです。

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

普通に考えると、この App Service に静的コンテンツをデプロイして IIS でホストしているのかと思いますが、気になったので .env ファイルをルートにデプロイして、アクセスできるか試してみました。

結果としてはエラーにならず、問題なくファイルが返ってきました。この時点で IIS が静的コンテンツを返しているという線はなくなります。

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

理由としては IIS は MIME Type が登録されているファイル以外は応答を返さないからです。昔の IIS で .json へのアクセスがエラーになって困った人もいるはずですが、それと同じ理由です。

.env という普通は絶対に応答を返さないファイルであってもアクセスが出来るので、IIS ではなく別途リバースプロキシによって別のストレージから取得していると想像できます。公式にルーティング機能が提供されていることから考えて、ARR などではなく独自のリバースプロキシだと思われます。

API (Azure Functions)

次は API 部分ですが、適当に HttpTrigger な Function をデプロイして、これもまた Postman などでレスポンスヘッダーを確認してみます。

X-Powered-By が付いているのは Static Content 側と同じです。全く同じ Function を別の Linux Function にデプロイしたところ chunked が付いていたので、これもリバースプロキシで処理されていそうです。

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

Azure Functions は環境変数で様々な情報がやり取りされていることは有名なので、以下のように環境変数をごそっと返す Function を作成すると様々な情報が見えてきます。

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    context.res = {
        body: process.env
    };
};

デプロイして API を叩いてみると、一瞬で Service Fabric Mesh 上で動作していることが分かるので、裏側は Linux Consumption というのが確定します。

リージョン名も環境変数に入ってくるので、East US 2 で動作していることも分かります。

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

中でも WEBSITE_HOSTNAME という環境変数には Azure Functions 実体のホスト名が入ってくるので、ブラウザから叩いてみると Azure Functions v3 が動作していることも判明しますね。

つまり /api 以下にアクセスすると、このホストへ転送されているということですね。

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

なんとなく host.json にある routePrefixapi 以外にしてみましたが問題なくアクセス出来ました。システム的に Prefix は固定されていそうです。

環境変数からは分かることが多く、例えば Run From Package を使って Function がデプロイされていることも分かります。ストレージアカウントはシステムが持っているものが使われています。

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

URL を指定した Run From Package は Pull 型のデプロイになるので、Geo Replication 向けではあります。

ビルド・デプロイ

最後にデプロイ周りですが、Static Web Apps 専用の GitHub Actions がビルドからデプロイまで行うので、相当にブラックボックスになっています。

ビルドコマンドはデフォルトでは npm run buildnpm run build:Azure が使われますが、パラメータを渡せばカスタマイズ可能です。

ビルドには Oryx が使われているので、実体としては App Service on Linux とような仕組みです。

ビルドログを見ると、app と api でそれぞれ zip が作られてアップロードされているようです。Oryx を使っているので、もうちょっとこみ入ったカスタマイズも出来そうですが、情報待ちにしときます。

そして polling を行いビルドが完了したかをチェックしています。全リージョンにデプロイするために非同期で処理が実行されているようですね。デプロイだけを単独で行うことも出来そうですが、API が公開されるかクライアントが OSS にならないと手を出しにくいです。