しばやん雑記

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

Azure App Service の Outbound IP Address は Premium V2 にスケールアップすると変わる

基本的に App Service の Inbound と Outbound IP Address はスケーリングによって変化しないものだと理解していましたが、ドキュメントを読むと Outbound IP に関しては変化することがあるらしいです。

以下のドキュメントに Outbound IP Address が変化するタイミングが記載されています。

曰く、新しい Premium V2 とそれ以外のサイズ間で切り替えを行うと、Outbound IP が変わるようです。

The set of outbound IP addresses for your app changes when you scale your app between the lower tiers (Basic, Standard, and Premium) and the Premium V2 tier.

変わらないと思っていたので、ちゃんと調べてみることにしました。

実際に Premium V2 な App Service を用意して確認してみました。基本的に全て Windows で確認をしています。まずは S1 なインスタンスを立ち上げて Outbound IP を確認しました。

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

大体の Service Plan で使われる Outbound IP は 4 つですね。

これを Premium V2 にスケールアップすると、以下のように Outbound IP が全て切り替わりました。

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

ファイヤーウォールなどに接続元の IP として App Service の Outbound IP を登録している場合には、プランを切り替えることで問題が発生しそうです。

なので、予め割り当てられる可能性のある IP Address を登録しておけば、こういった問題は防げます。Azure Portal からは確認できませんが、ARM Explorer を使うと可能性のある IP Address 一覧を確認できます。

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

数は多くなりますが、後からスケールアップを行う可能性があれば、安全側に倒しておいた方が良さそうです。Premium V2 内での切り替えや、S1 から B1 への切り替えなどでは Outbound IP は変化しません。

今の App Service では Premium V2 が使える Service Plan と使えない Service Plan が混在しています。

Premium V2 が使える Service Plan は今回紹介したとおりの挙動になりますが、使えない Service Plan の場合は outboundIpAddresses と possibleOutboundIpAddresses の値は同じになります。

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

IP 制限などが必要な場合には possibleOutboundIpAddresses の値を使うようにすれば安全ですね。

ASP.NET Core 2.1 の暗黙的なバージョンには注意

適当に Twitter を眺めていると、モリス先輩が ASP.NET Core Module 周りのエラーではまってそうだったので、また鰻を奢ってもらうために助けることにしました。

曰く、Visual Studio のテンプレートから作成して、App Service にデプロイしただけで発生するとか。

確かに同じようにテンプレートから作成して、App Service にデプロイしたところ HTTP Error 502.5 - Process Failure が返ってきて、アプリケーションは正しく起動してくれませんでした。

ANCM が原因で落ちる時は、そもそもランタイムのバージョンが間違っていてプロセスを起動できないというケースぐらいしかないです。なので、原因は ASP.NET Core 2.1.1 がリリースされたことにあります。

App Service のランタイムは Microsoft 管理なので、大体は RTM リリースと同タイミングで VM にインストールされるのですが、今回の 2.1.1 は少しタイムラグが発生したようです。*1

とはいえ 2.1.0 はインストールされているはずなので、これまでの場合は明示的に ASP.NET Core のバージョンを変更しない限り問題なかったのですが、2.1 からは SDK から暗黙的にバージョンが選択される機能が追加されています。

When the version is not specified, an implicit version is specified by the SDK, that is, Microsoft.NET.Sdk.Web. We recommend relying on the implicit version specified by the SDK and not explicitly setting the version number on the package reference.

Microsoft.AspNetCore.App metapackage for ASP.NET Core 2.1 and later | Microsoft Docs

具体的には PackageReferenceVersion を無指定に出来ます。Visual Studio から新規作成すると、この状態でプロジェクトが生成されます。

f:id:shiba-yan:20180625011154p:plain:w550

ソリューションエクスプローラーからバージョンを確認すると、2.1.1 になっていることがわかります。

f:id:shiba-yan:20180625011208p:plain:w400

このため 2.1.1 がインストールされていない App Service にランタイムを含まないポータブルな形でデプロイを行った結果、必要なバージョンのアセンブリが読み込めずエラーになったという話です。

とりあえずの対応方法としては App Service でインストールされているバージョンを明示的に指定すれば良いのですが、ドキュメントにはメタパッケージの暗黙的なバージョンに関して以下のような記述もあります。

The implicit version is set to major.minor.0 for portable apps. The shared framework roll-forward mechanism will run the app on the latest compatible version among the installed shared frameworks. To guarantee the same version is used in development, test, and production, ensure the same version of the shared framework is installed in all environments. For self contained apps, the implicit version number is set to the major.minor.patch of the shared framework bundled in the installed SDK.

Microsoft.AspNetCore.App metapackage for ASP.NET Core 2.1 and later | Microsoft Docs

ポータブルなアプリに関しては暗黙的なバージョンとして major.minor.0 がセットされるとあります。

なので、ドキュメント通りであれば Microsoft.AspNetCore.App の暗黙的なバージョンは 2.1.0 になると思うのですが、実際にはそうはなってませんでした。理由はよくわかりません。

Docker を使っている環境でも起こり得る問題なので、バージョン管理は気を付けましょう。

GitHub では Issue が乱立されるような状態になっていて、中々に影響範囲が広い問題だったようです。

次に ASP.NET Core 2.1.2 や 2.2 がリリースされたときにも同じような問題が発生するかも知れませんので、暗黙的なバージョンに関してはもうちょっと慎重になった方が良さそうです。

*1:2.1.0 の時も Japan East / West は遅かったので、時差の関係もあるっぽい?

Azure Storage Emulator を最新バージョンへアップデートする

最新の Azure Storage ライブラリを使って Table Storage を操作するコードを書いて実行したところ、以下のようなエラーが出て焦りました。

よく読むと REST API のバージョンに Storage Emulator が対応してないのが原因らしいです。

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

ちなみに Visual Studio は最新バージョンをインストールしている環境です。

昔は Web PI 経由で Azure SDK をアップデートしていたので、そのタイミングで Storage Emulator もアップデートされていた気がするのですが、とりあえず書いてある URL を開いてスタンドアロン版のインストーラをダウンロードしてきます。

ダウンロードしてきた MSI を見ると、バージョンが v5.5 となっていました。インストールされているのは v5.4 だったので、確かに一つ古いバージョンでした。

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

MSI を使ってインストール後、Storage Emulator を立ち上げると初期化が始まって、既存の開発用ストレージのデータが全て吹き飛んだので、その点だけ注意が必要な感じです。

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

その後、エラーで動かなかったコードを再実行したところ、問題なく動作しました。Azure Functions の Table バインディングを使った場合は普通に扱えていたので、ライブラリのバージョン依存で発生するようです。

地味にこういうのは困るのでアナウンスが欲しいところですが、Azure Table の REST API がアップデートされていたことに驚いてしまったので、まあ良いです。

Azure Cloud Services でも Application Insights のフル機能が使えるのか試した

最近の Application Insights は App Service で使われることしか考えられてないのでは、という感じがしていたので Cloud Services でもいろんな機能が使えるようになっているか調べました。

一応 Cloud Services 向けのドキュメントが用意されています。されていますが、コレジャナイ感はあります。

リクエストやパフォーマンスカウンターなどの基本的な情報はもちろん見れるようになっていますが、App Service でも Site Extension のインストールが必要な Profiler や新しめの Snapshot Debugger を確認します。

とりあえず Profiler から確認します。こっちの方が需要が多そうなので。

Profiler

App Service 以外で使うためのドキュメントが一応用意されていました。

Virtual Machines では拡張機能をインストールすれば良いみたいですが、Cloud Services では設定ファイルを弄って再デプロイが必要のようです。

厄介なことに設定ファイルに Instrumentation Key を明示的に書く必要があるみたいなので、環境ごとにキーを変える場合には多少工夫が必要な気もしました。

とはいえ、設定自体はとても簡単で ApplicationInsightsProfiler の Sink を追加するだけです。

<?xml version="1.0" encoding="utf-8"?>
<DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
  <PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
    <WadCfg>
      <SinksConfig>
        <!-- ↓ を追加する -->
        <Sink name="MyApplicationInsightsProfiler">
          <ApplicationInsightsProfiler>APPINSIGHTS_INSTRUMENTATION_KEY</ApplicationInsightsProfiler>
        </Sink>
        <Sink name="applicationInsights">
          <ApplicationInsights />
          <Channels>
            <Channel logLevel="Error" name="errors" />
          </Channels>
        </Sink>
      </SinksConfig>
    </WadCfg>
  </PublicConfig>
</DiagnosticsConfiguration>

キーの管理場所が散らばってしまうので、この辺りはあまり良くない仕様ですね。

そして Application Insights の Performance ブレードから Profiler を有効にしておきます。App Service の場合はいい感じにやってくれるのですが、それ以外の環境では手動で有効化しないといけないみたいです。

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

アプリケーションをデプロイ後、しばらくすると Profiler Trace が見れるようになります。

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

微妙な部分はありましたが、動作としては問題ないことが分かりました。

Snapshot Debugger

基本的には Microsoft.ApplicationInsights.SnapshotCollector をインストールするだけで動くようになりますが、ドキュメントにもあるようにダンプファイルの保存先容量が足りなくなる場合があるらしいので、ローカルストレージを割り当てて使うように設定します。

久し振りに弄るので、この辺りの制約を半分ぐらい忘れてました。

設定したアプリケーションをデプロイすれば、例外が投げられたタイミングで良い感じにダンプを取って Application Insights で確認出来るようになります。

スタックトレースだけじゃなくて変数の値も見れるのが特徴です。

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

多少制約はあるものの、Cloud Services でも Application Insights が提供している機能は一通り使えるようになっていました。

同様に Virtual Machines でも使えるようになっているはずです。

Azure SignalR Service がリリースされたのでサクッと試した

Build 2018 が始まったようですね。基本的な内容は明日の朝にブチザッキを見て確認する予定ですが、Cloud Platform Release Announcements に Azure SignalR Service という心惹かれる項目があったので、サクッと確認してみました。

Azure Blog でも紹介されてました。SignalR を大規模に使うと、大体はスケーリングと管理がめんどくさくなってしまうのですが、その辺りは Azure にお任せという何時もの流れです。

内容からして SignalR の WebSocket などを利用した接続部分だけ外部サービスとして切り出すものだと思っていましたが、必然的にクロスドメインになるので少し挙動が気になってました。使えるトランスポートは WS / SSE / XHR のようです。

チュートリアルがまとまっていたので、参考にしつつ適当なアプリケーションを用意しました。

とりあえず先に Azure SignalR Service を作成しておきます。Preview 中は Pricing Tier としては Free / Basic が選べます。Basic を選んだ場合のみ 10 ユニットまで拡張することが出来るようになってます。

将来的には Standard や Premium が追加されて上限が増えるのではないかと予想します。

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

当然ながら使用可能なリージョンに日本はないので、West US 2 に作成することにしました。Basic での 1 ユニット当たりの価格は非常にお手軽ですね。おそらく裏側の Redis 分も含まれているはずです。

グローバル IP は固定みたいなので、カスタムドメインも設定できるようになりそうです。

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

恐らくこの性能でリミットがかかっているわけではなく、あくまでも目安という感じがします。*1

デプロイ自体は割とあっという間に完了します。数分以内に 3 ユニットが立ち上がってきたので、裏側はそれなりに気合が入っている感じがします。

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

Pricing Tier に Basic_DS2 とあったのが少し気になりました。VM のサイズ感ありますね。

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

アプリケーションの開発は ASP.NET Core SignalR と同じですが、追加で Azure SignalR Service 用の NuGet パッケージをインストールします。

この辺りはサンプルコードと同じなので省略しますが、普通に SignalR アプリケーションが動くようになりました。予想通り WebSocket 接続は Azure SignalR Service を向いていました。

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

negotiate が多いですが、初回の段階で Azure SignalR Service のエンドポイントを返しているので、その後のリクエストは全てローカルには流れてきません。

完全に Hub などの操作を行うサーバーサイドと、WS / SSE などでの配信を行う部分が完全に切り離された実装になっているので、スケールがさせやすそうです。

Azure Functions からも接続文字列だけあれば、簡単にメッセージを投げることが出来るみたいですが、クライアントライブラリが未整備のようなので、いきなり使うにはちょっとハードルが高いと思いました。

API のドキュメントがぱっと見公開されてないみたいなので、割と厳しそうです。とはいえ、アーキテクチャ的には重要な使い方になってくると思うので、GA までにはリリースされるのではないかと。

App Service が HTTP/2 に対応したので ASP.NET で試した

Windows Server 2016 へのアップグレードが行われた際に、一部のリージョンでテスト的に有効化されていた HTTP/2 がグローバルで GA になってました。例によって ASE にはまだデプロイされていないみたいですが、近いうちに対応するみたいです。

HTTP/2 はデフォルトで有効化されているわけではなく、設定から有効にする必要があります。この辺りはブログにも書いてるので省略。

まだ Azure Portal から設定できないので、ARM Explorer を使って設定を変えます。

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

HTTP/2 よりも気になる設定が増えているので、さらっと試しておきました。とりあえず適当な Web App で HTTP/2 を有効にしたので、適当にブラウザで確認しておきました。

設定を行う前は HTTPS となっています。

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

設定後は HTTP/2 と表示が変わっているので、ちゃんと有効化されています。今後もデフォルトで有効化という流れにはならないと思うので、必要なケースで都度有効化しましょう。

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

HTTP/2 と言えば ASP.NET 4.6 から Server Push に対応していたので、App Service でも使えるか確認しておきました。結論としては使えませんでした。

App Service の HTTP/2 は ARR 部分での対応っぽいので、ARR <=> Web Worker の部分が HTTP/2 になっていないので Server Push が使えないのかも知れません。リバースプロキシが絡むと少し面倒ですね。

具体的には Response.PushPromise を呼び出した瞬間にエラーとなってしまいました。

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

ARR が Preload に対応していれば問題なく使えるようになりそうですが、これに関しては割と望み薄ですね。Service Worker 周りで App Service を使いたい場合に困るケースがあるかも知れませんね。

HTTP/2 に関してはこのくらいにして、もう一つ増えていた minTlsVersion について適当に試しておきました。正式リリースされていないので挙動は不安定っぽいので注意。

デフォルトでは TLS 1.0 が有効になってますが、これを 1.2 に変えると TLS 1.2 のみを有効に出来るはずです。出来るはずなのですが、今の挙動はよくわからない感じです。

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

謎挙動ですが、この時点で SSL Labs のテスト的には TLS 1.2 のみ有効という扱いになってました。なので、HSTS を追加すれば App Service でも A+ 評価を得ることが出来るはずです。

Web.config を弄って適当に Strict-Transport-Security ヘッダーを追加します。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

これでテストを再実行すると無事に A+ 評価になりました。

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

6 月までには正式リリースされるはずなので、その時には再度挙動を確認しておきたいと思います。

Application Insights で IP が取れない件と ASP.NET Core で Telemetry Initializer を追加する方法

ASP.NET Core アプリケーションに Application Insights を追加して使ってましたが、いつからかクライアントの IP アドレスが 0.0.0.0 になってしまってました。

元々最後のオクテットは 0 になってたのは知ってましたが、なんか不具合化と思ってました。

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

軽く検索してみると GDPR の関係で IP アドレスを保持するのを止めたみたいです。*1

Client IP logged as 0.0.0.0 but geolocation is logged correctly

All octets of IP address will be set to Zero – Azure Application Insights Service Status Blog

なので、Application Insights が自動的に取っているデータからは IP アドレスを取ることは出来ません。もし IP アドレスが必要な場合は Telemetry Initializer を使ってカスタムデータとして送信しろと書いてます。

適当に IP アドレスを送信する Telemetry Initializer を書きました。IP アドレスは既に Application Insights が Location.Ip に設定してくれているので、これをそのまま送信することにします。

public class CustomIpTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        if (!(telemetry is RequestTelemetry requestTelemetry))
        {
            return;
        }

        requestTelemetry.Context.Properties["client-ip"] = requestTelemetry.Context.Location.Ip;
    }
}

テストなので特にマスクなどの処理を入れてないですが、GDPR が関係する場面だともうちょっと考える必要があるのかも知れません。知らんけど。

作成した Telemetry Initializer ですが、ASP.NET の場合は ApplicationInsights.config に追加すれば有効になってましたが、ASP.NET Core の場合は DI に追加すると有効になります。これ知りませんでした。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITelemetryInitializer, CustomIpTelemetryInitializer>();

    services.AddMvc();
}

ITelemetryInitializer としてシングルトンで追加します。XML を書くより簡単になった気がします。

デプロイすれば、それ以降のテレメトリに IP アドレスが含まれるようになります。

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

ASP.NET Core での Telemetry Initializer の追加方法を知らなかったので良い勉強になりました。それにしても GDPR 絡みは地獄感しかないですね。

*1:GDPR では IP アドレスは個人情報に該当するか微妙そうだけど、安全側に倒したということのようだ

Application Insights の Snapshot Debugger を使って本番環境のデバッグを効率的に行う

仕事でも Application Insights を弄っていたのに全く知らなかった機能である Snapshot Debugger ですが、チャックさんの記事で存在を知ったのと何となく試して見たかったので検証してみることにしました。

Tech Summit 2017 のチャックさんのセッションで話があったっぽいですね。Tech Summit 2017 は登壇側だったのと、登壇者は他のセッションに参加できないパスだったので見れてませんでした。

最近実装されたのかと思ってたらそんなことはなく、地味に前からある機能でした。ドキュメントもちゃんと整備されていて ASP.NET 4.6 以降と ASP.NET Core 2.0 アプリケーションならすぐに使えるようです。

上のスライドでは Visual Studio からの利用になってますが、アプリケーションにインストールしておくと、例外が投げられたタイミングでダンプを自動的に取るという素晴らしい機能です。

使い方も書いてある通りで、NuGet パッケージを 1 つ追加するだけで終わります。

プロダクション環境で発生した例外は Application Insights でも確認できますが、例外が発生したタイミングの変数の値などはデバッグに非常に役立ちます。なので今すぐ有効にした方が良い機能でした。

とりあえず試します。適当に MVC 5 アプリケーションを Web App / Application Insights を用意して、わざと落ちるようなアクションを書いてデプロイしました。

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

Snapshot が取得できているとリクエストの横にアイコンが表示されます。

今回の Snapshot Debugger とは関係ないですが、Application Insights に Preview として実装されている E2E Transaction Diagnostics は有効にした方が調査が捗ります。

実際に Snapshot を確認するには、例外を選択して表示されるペインから Open debug snapshot を選びます。

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

Snapshot を開くとコールスタックの確認が行えます。普通に Application Insights で確認出来るものより詳細な気がしますが、まあこっちはおまけみたいなものです。

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

スクロールするとローカル変数の値を確認できるようになっています。こっちがメインの機能です。

コードが最適化されて消えてしまった場合は確認できないですが、大体のケースでは役に立つ情報が確認できるはずです。変数の値をログに書き出すよりも、ダンプがあった方が圧倒的に捗ります。

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

少しのオーバーヘッドはあるでしょうが、圧倒的に便利な機能なので気にしません。

Visual Studio Enterprise を使っている場合には、収集された Debug Snapshot をダウンロードして Visual Studio でデバッグすることが出来ます。実際にデバッグするとソースコードと紐づいた形で確認できます。

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

チャックさんの記事にもあったように、実行中のアプリケーションに対しても Visual Studio から Snapshot Debugger をアタッチすることが出来るので、プロダクション環境でデバッグが簡単に行えます。

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

これまで Application Insights は他社の APM に比べると機能が少ないし、処理が遅くて残念に思っていましたが、最近は Profiler や Snapshot Debugger でかなりテコ入れがされた感じがあります。

そういえば Application Insights Profiler について書いてなかったので、暇な時にまとめます。

AppVeyor と CircleCI 2.0 を使って Azure Functions v2 の CI/CD を行う

GitHub を使って Azure Functions の管理をしている場合には、やはりデプロイは自動化しておきたいです。VSTS は標準で対応してるみたいですが、AppVeyor は見た記憶がないので試しました。

少し試した感じでは WebDeploy Package を Azure Functions プロジェクトで作るのは難しそうだったので、dotnet publish を実行してから普通の zip を作るようにします。

dotnet publish -c Release

Azure Functions v2 は .NET Core なので dotnet publish を実行すれば良いです。

最近は zip をプッシュするだけでデプロイ出来るようになったので利用します。App Service Team のブログに zip デプロイについての解説があるので、参照しておくと良さそうです。

https://blogs.msdn.microsoft.com/appserviceteam/2017/10/16/zip-push-deployment-for-web-apps-functions-and-webjobs/

デプロイする zip を作ってしまえば、後は curl を使って Kudu の API を呼び出すだけです。

AppVeyor

AppVeyor は App Service に zip でのデプロイを行うプロバイダーが追加されているので、これまでの WebDeploy とは異なり、非常に簡単に行えるようになっています。

今回作成した appveyor.yml は以下のようになりました。

dotnet publish で生成したファイルは自前で zip にしないといけないのが少し面倒ですが、1 行追加で済むので妥協します。Artifacts に設定しないと後続のデプロイがめんどくさくなるので注意。

version: 1.0.{build}
image: Visual Studio 2017
build_script:
- cmd: dotnet publish -c Release -o ..\publish
after_build:
- cmd: 7z a artifact.zip .\publish\*
artifacts:
- path: artifact.zip
deploy:
- provider: AzureAppServiceZipDeploy
  website: <FUNCTION_NAME>
  username: <USERNAME>
  password:
    secure: <ENCRYPTED_PASSWORD>

AzureAppServiceZipDeploy に指定する値は発行プロファイルに書いてある値なので簡単です。

実際に GitHub にリポジトリを作成して、AppVeyor でビルドを実行しました。

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

ビルド自体は 1 分以内で完了するので非常に早いです。

ビルドが成功したのを確認した後に、Azure Portal で確認するとちゃんと作成した Function が確認できます。

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

当然ながらちゃんと動作します。デプロイ後に Azure Functions Runtime のバージョンを変更できないので、そこだけは予め変更しておかないとはまります。

CircleCI 2.0

CircleCI を使う場合も AppVeyor とやることはほぼ同じですが、CircleCI 2.0 では Docker が使えるので Microsoft の公式 SDK イメージを利用してビルドを行います。

作成した config.yml は以下のようになりました。7z が入っていないのでインストールします。

version: 2
jobs:
  build:
    docker:
      - image: microsoft/dotnet:sdk
    steps:
      - checkout
      - run:
          name: Install 7zip
          command: apt-get update && apt-get install -y p7zip-full
      - run:
          name: Build Azure Function
          command: dotnet publish -c Release -o ../publish
      - run:
          name: Pack to Zip
          command: 7z a artifact.zip ./publish/*
      - run:
          name: Push to App Service
          command: curl -X POST -u $SITE_USER:$SITE_PASS --data-binary @artifact.zip https://***.scm.azurewebsites.net/api/zipdeploy

App Service へのデプロイは curl を使って 1 発で終わります。

CircleCI 2.0 の対応言語には .NET Core がありませんでしたが、今回利用した SDK イメージはキャッシュ済みのようで、一瞬で環境の構築が完了しました。

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

ビルドは AppVeyor よりも早く感じました。デプロイが完了後は Azure Portal から確認できます。

.NET Core になってからはビルド環境の構築が非常に楽になり、プラットフォームも気にする必要がほぼなくなったため SaaS の選択肢が大幅に広がり便利になりました。

Azure Functions のコールドスタートを Application Insights で確認してみる

何かと(一部?で)話題になる Azure Functions のコールドスタートが非常に遅いという話ですが、App Service Team Blog にてどのような動きとなっているか解説記事が公開されていました。

ぶちぞう RD のアンテナは高いので、フォローしておくと有益な情報が得られます。

https://blogs.msdn.microsoft.com/appserviceteam/2018/02/07/understanding-serverless-cold-start/

曰く 20 分ぐらいアイドルだとインスタンスが落とされて、次からはコールドスタートになるようです。

Azure Functions というか App Service のコールドスタートを早くするために、予め Function Runtime を ngen しておいたり、placeholder を使ってプロビジョニングを短縮してるようです。

常に ping を飛ばしてコールドスタートに落ちないように維持する方法はありますけど、ぶちぞう RD の言う通りスケールした時に問題となるケースが出てきます。

コールドスタートを早くする努力をしようとしても、Kudu にアクセスした瞬間 Web App はプロビジョニングされてしまうので、コールドスタートを検知するのは地味に難しいという罠があります。

なので今回は Application Insights の Live Metrics Stream を使って試しました。いろいろ試した結論としては、ちゃんとコールドスタートを検知できているように思います。

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

送られてくるログを確認すると、ロックを取ったり拡張機能を読み込んだりという処理が見えます。

ウォームスタートでは行われない処理なのと、前後でインスタンス ID が変わっているので別のマシンに移動したことが分かります。インスタンスが落ちると Servers から消えるので分かりやすいです。

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

実際に Application Insights から消えた後に HTTP リクエストを投げると 5-10 秒ぐらいかかるので、コールドスタートになっていると考えられます。w3wp が死んでから、App Service 自体が落ちるまでは多少のタイムラグがありそうです。

.NET Core 版を実行した時のログでは host.ini が読み込まれてから、実際に Function が実行されるまで Application Insights 上の時間で 6 秒ぐらいかかっているので、やはり読み込み周りが辛いみたいです。

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

まだ .NET Core 版は最適化されていないらしいので、GA 時には改善されているはずですが、結局のところ Azure Functions の Consumption Plan が遅い原因は Azure Files だろうと思っています。

本末転倒ですが App Service Plan で Premium V2 に Azure Functions を作れば劇的に改善しそうです。