koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

Azure Function App 『だけ』でWebサービスをホスティングする

事を試していて、少々無理筋だな、と思った話。

最近2つのテーマを持ってWebサービスの実装を試していた。

  1. SPAっぽく、クライアントコードとサーバコードを完全に切り離す実装を体験する
  2. Azure Functionの従量課金だけで安価にAzureでWebサービスホスティングする

この記事のテーマは後者である。

Azureのプログラムホスティング用のPaaSは概ねApp Serviceという共通の基盤の上で提供されており、Azure Functionsも例外ではない。

そのため、App ServiceがWebサービスホスティング用に備えている強力な機能をFunctionsでも使用することができる。 加えて、Functions固有の機能も使用できる。

これらを組み合わせると、Azure Functions(+そのためのStorageアカウント)のみでWebサービスホスティングできるのではないか? と思え、この2週間ほど試していた。

結論から言うと、出来なくもないが固執するのは良くない、といったところだった。

確かにやってできないことはないのだが、より向いているマネージドサービスと組み合わせたり、無理に組み込み機能を使わない方が平易だったりする感じを受けた。 適材適所である。

静的ファイルの配信

まず、Webサービスを構成するには、Functionsで提供する類のAPIとは別に、HTMLやCSSJavaScriptなどを配信しなくてはならない。

Functionsには特定のパスへのリクエストを、他の関数やURLにプロキシする機能がある。

docs.microsoft.com

Azureでは静的ファイルはBlob Storageに保存・HTTPS配信することができるので、Blob Storageをオリジンとしてプロキシしてみた。 これによってAPIと同一ドメインで静的ファイルを配信することができ、CORSのわずらわしさやCookieの使用など課題と利便性に便宜を図ることができる。

が、正直あんまりいいとは思えなかった。

FunctionsのProxyは実態としては単なる関数で、当然その関数をホスティングする環境が必要になる。 この環境の立ち上げには20~30秒程度かかる様子(従量課金プラン)で、寝ていたサイトにアクセスすると、サイト構成要素のファイル毎に環境立ち上げの遅延が発生する辛みのある待ち時間があった。

f:id:koudenpa:20180610205359p:plain

この遅延はサービスに断続的にアクセスされる状況なら最小化されるが、それにしてもあまり気持ちの良いものではない。

FunctionsではCORS等は当たり前の課題として取り組んで、APIと他のリソースのドメインは別のものにする、と割り切って構成した方が良さそうだった。

同一ドメインで配信し、APIと他のリソースを同一ドメインで配信するような場合は、素直に通常のApp Serviceで通常のWebアプリケーションとした方が良さそうである。 今回この思い付きを試しながら、何度もそう感じた。

※一度環境が立ち上がってしまえば、現実的な速度で配信される。

f:id:koudenpa:20180610205116p:plain

↑赤枠で囲った分がFunctionsのProxyでの配信分。

↓同じ状況でCDN(CloudFront)で配信した場合。相手が悪い。

f:id:koudenpa:20180610213508p:plain

認証

AzureのApp Service(含むFunctions)には、各IDプロバイダーとの認証統合機能が組み込まれている。 この組み込み認証機能で賄える範囲であれば非常に平易にユーザーを認証し、認可を得ることができる。

が、正直Functions向けに便宜が図られてはいないので、しっかりと使用するにはそれなりにアプリケーション(関数)側でユーザー毎の情報を管理するなどしなくてはならなかった。

今回は『ユーザーをTwitter認証して、そのユーザー名を関数で使いたい』程度の要求だったが、何も考えずにそれを満たせるわけではなかった。無念。

同機能に関しては、以下のブログ記事が非常に詳しい。

Azure App Service の Authentication 徹底解説 – Tsmatz

なお、冒頭にこう書かれている。

なお、あまりこの仕組みに拘って、無理矢理、高度な使い方をする必要はありません。認証用のコードを直接組み込むなど、従来の手法でプログラミングしても 勿論 OK ですので、適材適所で活用してください。

ごもっとも!

一応出来事をメモしておく。

二つの認証フロー~楽な方は使えません事件~

docs.microsoft.com

にある通り、組み込みの認証機能にはCookieベースとトークンベースでの認証情報連携フローがある。 このうち、Cookieベースの認証は、アプリケーションが同一オリジンで構成されている必要があるが、App Serviceの認証用エンドポイントにアクセスするだけで、Cookieに認証情報が設定された状態になるという非常に平易なものだ。 同一オリジンで完結するWebアプリケーションであればとても便利に使用できると思う。

今回も当初はそうしていたのだが『Azure CDNで静的ファイルを配信したい』などという色目が出てきたためボツになった。

そのため、組み込みの認証機能に頼らず各プロバイダー(Twitterだけだけれど)のアクセストークンを取得し、 組み込みの認証機能にPost、ZUMO-AUTHという組み込みの認証機能向けトークンでの認証を行うように構成した。

ユーザー名が空~結局はアプリケーションでユーザー情報を管理しないとね事件~

ZUMO-AUTHでの認証とした結果、Functionsの実行時に設定される認証情報からTwitterのユーザー名が消えてしまった。

そりゃあ、そうだ。ZUMO-AUTHはApp Service固有の認証であり、Twitterはその接続先のIDプロバイダーの1つにすぎないのだから。

App ServiceはZUMO-AUTHで認証したユーザーの関連プロバイダーの情報をHTTP Headerにインジェクションして支配下のアプリケーションに伝えてくる。

docs.microsoft.com

実際にTwitterのみ認証統合した状態では以下の様なヘッダが設定されていた。 が、欲しかったTwitterのユーザー名なんてものはなかった。 (PRINCIPALはJWTなので中身を見てみたがなかった)

"X-MS-CLIENT-PRINCIPAL-ID: sid:xxxx",
"X-MS-CLIENT-PRINCIPAL-IDP: twitter",
"X-MS-CLIENT-PRINCIPAL: yyyy",
"X-MS-TOKEN-TWITTER-ACCESS-TOKEN: 123-zzzz",
"X-MS-TOKEN-TWITTER-ACCESS-TOKEN-SECRET: aaaa"

当然、このトークンとシークレットを使用すれば、Twitterからそのユーザーの情報を取得できる。 リクエスト毎にTwitterAPIを呼び出すわけにもいかないので、アプリケーションとしてそのユーザーの情報を管理していくことになるだろう。

当たり前であるが、過度の手抜きはできない様子だった。

まとめ

急がば回れ

Webサービスアーキテクチャには適材適所がある。