しばやん雑記

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

Azure App Service が .NET Framework 4.7.2 に対応

予定では 10 月中旬にデプロイされる予定だった App Service の .NET Framework 4.7.2 対応ですが、今日にようやくデプロイが行われたようです。

手元にある Japan East と Central US の Scale unit で確認していますが、全ての Scale unit にデプロイされたかは公式のアナウンスを待つ必要があります。*1

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

そろそろ appservice.info も複数リージョンにデプロイして、情報を集約出来るようにしたいです。

Visual Studio 2017 でもちょっと前に Installer から .NET 4.7.2 SDK がインストール出来るようになっているので、これで新しい機能を使う準備が整った形ですね。異常なほど時間かかりましたけど。

.NET Framework 4.7.2 での更新内容は以下のページを参照してください。

What's new in the .NET Framework | Microsoft Docs

大きな機能追加とかはないですが、バグ修正やパフォーマンス改善など細々としたものが多いです。信頼性の向上としては重要です。セキュリティ周りとコレクション、そして ASP.NET を見ておけば良いです。

今回のバージョンは暗号化周りで API が結構追加されています。個人的には Rfc2898DeriveBytes で HMAC-SHA256 が選べるようになったのは良い感あります。対応が遅すぎるという気もしないこともないですが、BCL レベルで入ったのは良いです。

大きく挙動が変わるものは無さそうなので、.NET 4.7.2 に再ターゲットしつつ検証すればよいかと思います。

おまけ : ASP.NET Web Forms の DI 対応

.NET Framework 4.7.2 での割と大きめの機能追加が ASP.NET Web Forms 向けの DI です。

こういうことを書くと今更 Web Forms かとか言われそうですが、Web Forms から ASP.NET Core MVC への移行を考えた場合には、適切に Web Forms のコードビハインドから処理が分離されていることが重要なので、まずは DI を使って処理を別のプロジェクトに分けるのは必要だと思ってます。

特に ASP.NET Core は全てが DI で解決されるので、そのスタイルに持って行った方がスムーズです。

4.7.2 がリリースされた当初はイマイチ情報が少なくて面倒でしたが、最近は Unity DI ベースの NuGet パッケージがリリースされていたので、これを使うとサクッと組み込むことが出来ます。

Unity DI は最近アクティブに開発されているので良い感じです。

実際に ASP.NET Web Forms のプロジェクトを作成して確認してみました。

ポイントとしては Application_Start で DI の設定を行うぐらいで、後はコンストラクタインジェクションでインスタンスが受け取れます。

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        var container = this.AddUnity();

        container.RegisterType<TestService>();
    }
}

型の登録時に LifetimeManager の設定も出来ますが、ASP.NET の場合はどれを使うべきか多少迷いますね。昔は PerRequestLifetimeManager を作ってましたが、デフォルトでまだ入っていないんですね。*2

登録してしまえば後はコンストラクタインジェクションで受け取れます。Page や UserControl 以外にも大体の部分で使えるみたいですが、基本的にはこの二つで使えれば問題ないでしょう。

public partial class Default : System.Web.UI.Page
{
    public Default(TestService testService)
    {
        _testService = testService;
    }

    private readonly TestService _testService;

    protected void Page_Load(object sender, EventArgs e)
    {
        label.Text = _testService.SayHello("buchizo");
    }
}

流石に IHttpModule とかで使うのはアンチパターンだと思いますし、使いたいケースもほぼないでしょう。

デバッガーを使って実際にインジェクションされるか確認しておきました。

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

ちゃんと指定したクラスのインスタンスが渡されていることが確認出来ますね。

ちなみに HttpRuntime.WebObjectActivator を使って実装されてますが、必要な IServiceProvider の実装は少し面倒なので、用意されているパッケージのものを使うのが良いです。

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

最後に App Service にデプロイして確認しておきました。ちゃんと動いてますね。

これまで .NET Framework 4.7.2 が使える PaaS はほぼ無かったので、今回のデプロイでようやく新機能を使うことが出来るようになって嬉しいです。

*1:数日中にアナウンスがあるとは思いますけど

*2:代替があるのかも知れないけど未確認

Azure Functions v2 は .NET Standard から .NET Core へ

ちょっと前に Azure Functions v2 で netcoreapp2.1 が使えるようになったと書きましたが、気が付いたらテンプレートもアップデートされ、デフォルトが netcoreapp2.1 になっていました。

当時はデバッグに問題がありましたが、最新のツールへアップデートすることで解消されました。

これで Azure Functions v2 は晴れて .NET Core 2.1 対応と言うことが出来そうです。使う前に確認は必要なので、メモとして確認のポイントを残しておきます。

実際に使う前に Visual Studio にインストールされているツールが最新版か確認しておきます。10/17 にリリースされた 15.10.2046.0 以降なら、デバッグ時に謎エラーが発生することなく実行できます。

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

ツールのアップデート後は適当に新しく Azure Functions プロジェクトを作成するダイアログを表示します。このタイミングで新しいランタイムとテンプレートがある場合にはアップデートが行われます。

左下でぐるぐるが回っている場合には、アップデートが行われてるので少し待ちます。

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

アップデートの完了後、最新の情報に更新すると新しいテンプレートが使われるようになります。

適当に v2 を選んで Azure Functions プロジェクトを作成すると、最新の Azure Functions SDK と .NET Core 2.1 SDK が使われていることがソリューションエクスプローラーから確認できます。

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

netcoreapp2.1 なので当然ながら .NET Standard のライブラリはインストールして使えますし、Span<T> や Memory<T> などの新しいクラスも利用可能です。

プロジェクトの設定からデバッグ周りの設定を弄る必要もありません。

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

Azure Functions のランタイム側も少し更新されたのか、Http Functions が許可するメソッドが確認出来るようになっていました。多分、最近のアップデートで追加されたものだと思います。

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

netcoreapp2.1 にしたとしても、何の違和感もなく .NET Standard 2.0 の時と同じようにデバッグが出来ますし、互換性面で問題になる要素はないはずです。

元々 v2 へのアップデートが推奨されていることもあるので、同時に netcoreapp2.1 にしてしまうのはありだと思います。サイズも小さくなり、使える API も増えるのでアップデートするメリットは割と大きいです。

Azure Functions v2 で netcoreapp2.1 が使えるようになった

.NET Standard 2.0 で書く必要があった Azure Functions v2 ですが、最新版の SDK を使うと .NET Core 2.1 向けにビルド出来るようになりました。正確には netcoreapp2.1 をターゲットに設定出来ます。

先に SDK をリリースして、問題なければテンプレートも netcoreapp2.1 になりそうですね。

これでようやく Span<T> などの新しい API が使えるようになりそうです。流石に .NET Standard 2.0 はアプリのコードを書くには API が少なすぎました。

対応している SDK は 1.0.23 からです。このバージョンからは UseNETCoreGenerator の設定も不要になっているので、単純にターゲットを変更するだけでビルドが通るので楽です。

テンプレートから作ると netstandard2.0 になっているので、netcoreapp2.1 に変更します。

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

後はリビルドを実行するとパッケージの復元から走るので、ビルドに成功するはずです。手元の環境ではリビルドを実行しないと、古いファイルが残っていて失敗することが多かったです。

これで netcoreapp2.1 が使えるようになっていろいろ捗りそうですが、今はそのままの状態だと Visual Studio からデバッグ実行が出来ないので、デバッグ周りの設定を変更をして対処します。

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

デバッグ時の起動対象を実行可能ファイルに変更して、Azure Functions Tools を直接使うように設定します。

パスは現時点では %localappdata%\AzureFunctionsTools\Releases\2.8.1\cli\func.exe を指定すれば良いですが、バージョンが上がった時には追従させた方が良いでしょう。

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

設定が終われば、F5 でデバッグ実行出来るようになります。この不具合は今後修正されるはずです。

作成した Function は Azure にデプロイしても、何の問題もなく動作します。

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

netstandard2.0 から netcoreapp2.1 に変更することで、デプロイに必要となるファイル数が減るので zip 後のサイズもかなり減りました。パフォーマンス面でのメリットもありそうです。

正式にテンプレートレベルで netcoreapp2.1 に対応されたら、既存の移行が捗りそうな気配があります。

.NET Core における Activity と Application Insights による分散トレーシング

.NET Core 2.0 から追加された Activity というクラスがありますが、トレース用の ID を管理する重要な機能を持っています。特に Application Insights と組み合わせることで分散トレーシングが容易になります。

.NET Framework 4.5 以降なら System.Diagnostics.DiagnosticSource を NuGet からインストールすれば使えます。.NET 4.6 以降の場合は AsyncLocal<T> で実装されてます。

Activity Class (System.Diagnostics) | Microsoft Docs

新しく ASP.NET Core 2.1 のプロジェクトを作成すると、エラーページにユニークな ID を表示するコードとして、既に Activity を使うようになっています。

Activity.Current は null の可能性があるので HttpContext.TraceIdentifier も参照しています。

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

とはいえ、基本的には Activity が自動的に開始されているので、変なことをしない限りは値は取れます。

実装としては全く別物なので、それぞれの ID フォーマットは異なります。

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

これだけだと利用価値がいまいちですが、HttpClient を使うと自動的に ID を HTTP ヘッダーに付与してくれるので、受け取り側が ASP.NET Core の場合は ID が継承されて Activity が開始されます。

実際に以下のようなコードで HTTP リクエストを投げてみます。httpbin.org を使うとリクエストのデータを JSON で返してくれるので簡単に確認出来て便利です。

class Program
{
    private static readonly HttpClient _httpClient = new HttpClient();

    static async Task Main(string[] args)
    {
        var activity = new Activity("Main").Start();
        
        activity.AddBaggage("name", "kazuakix");

        var response = await _httpClient.GetStringAsync("https://httpbin.org/get");

        activity.Stop();
    }
}

デフォルトだと ID だけ付与されますが AddBaggage を使うと追加のデータとして付与されます。

実行した結果は以下の通りです。Request-IdCorrelation-Context というヘッダーが何もしなくても付与されていることが確認出来ます。

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

このヘッダーに関しては CoreFx のリポジトリにてドキュメントが公開されています。標準化に向けた作業中という話ですが、ちょっと進捗は怪しいですね。

Request-Id の値は親子関係を持っているので、受け取った側が更に別のサービスに処理を投げたとしても、ちゃんとその呼び出し関係は保持されるので、単純な ID よりも情報量が多いです。

Application Insights で分散トレーシング

Activity によって分散トレーシングに必要な値が準備されるので、後は Application Insights に送信してあげるだけです。既に SDK レベルで対応されているので、アプリケーションに Application Insights を追加すればすぐに使える状態になっています。

ドキュメントも用意されていますが、大体は Id の仕様についての話です。

Application Insights 的にはユニークな ID が送信されたら、それを関連付けて扱うだけなので、実際のプロトコルには依存していません。なので別にどんなプロトコルでも使えるはずです。

挙動を確認するために ASP.NET Core なアプリケーションを作って、HttpClient を使って別のアクションを呼び出してみました。E2E Transaction を確認すると分かりやすいです。

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

特に専用のコードを書いているわけではないですが、透過的に HttpClient に Request-Id が引き継がれているので、一連の処理として Application Insights では扱われています。

これが別の App Service になると、Application Map での見栄えが良くなります。

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

ちゃんと App Service 間のリクエストが可視化されて表示されています。1 つや 2 つぐらいだと大したことはないですが、関係のあるアプリケーションが増えていくと非常に有用だと思われます。

もちろん AIQL を使って Request-Id 単位でのクエリ実行も可能です。

ASP.NET での対応

ASP.NET Core はデフォルトで Request-Id の値を見るようになっていますが、ASP.NET 向けにも以下の Http Module をインストールすると、同じように処理されるようになります。

Activity の実装には AsyncLocal<T> が使われているので、実行コンテキストが変わった時に意図しない挙動になる可能性はありますが、ASP.NET / ASP.NET Core の両方ともリクエストスコープになるストレージも併用されているので、基本的には問題とならないはずです。

Azure Functions での対応

最近リリースされた Azure Functions v2 でも同じような仕組みを使ったトレースの仕組みが追加されています。基本的には Request-Id を引き継いでいく仕組みですが、いろんな Trigger で使えるようです。

ソースを確認した感じでは Service Bug Trigger の場合は UserProperties を使って、Request-Id を引き継いでいくみたいです。Storage の Trigger も一部は対応しているようでした。

念のために HttpTrigger でも試してみましたが、ちゃんと Request-Id を引き継いでくれませんでした。最新のランタイムでもまだ未対応のようです。

なお Request-Id を引き継ぐ必要があるので、メタデータが無い Storage Queue は難しそうです。

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

ちなみに Application Map での表示は Cloud Role Name でまとめられるので、Telemetry Initializer を使って上書きすることが出来ます。実際に Kubernetes 向け拡張ではコンテナ名で上書きされています。

大注目されている Durable Functions の場合は、Queue の値が隠蔽されているので比較的簡単に対応は行えそうですが、今のところは特に動きは無さそうです。Azure Functions の Consumption Plan の場合はスケーリングの状態を可視化出来るので、結構面白そうです。

Azure Functions の各 Trigger の動作が不安定な時にやること

最近、新しくデプロイした Azure Functions の TimerTrigger が何故か発火してくれない現象に悩んでいました。実際に Monitor で実行ログを確認すると、時間がめちゃくちゃになっています。

Consumption の場合は App Service Plan とは違う仕組みで動いているので、安定するのに時間がかかるのかとかタイムゾーンの設定を疑ったりしていましたが、結論から言うと違いました。

今は TimerTrigger を Let's Encrypt の証明書更新で使っているので、上手く動かない場合は割と致命的です。

いい加減になんとかせねばと真面目に調べたら、以下の Issue が引っ掛かりました。

どうやら Run From Package を使って外部 URL から zip をデプロイした場合に、正しく各 Trigger のメタデータが Scale Controller に同期されない問題があるらしいです。

コメントに書かれていた Workaround は以下になります。

  • 明示的に Azure Portal から Restart を行う
  • syncfunctiontriggers を実行する

Web Deploy を使っても解決するみたいですが、今更 Web Deploy には戻れません。修正が行われるまではどちらかの方法を実行する必要がありそうです。

手っ取り早いのは Restart ですが syncfunctiontriggers の方は Azure Functions のアーキテクチャ部分を垣間見ることが出来るので、調べておいて損はない感じです。

最近は API ドキュメントからすぐに試すことが出来るので便利です。

ちなみに Azure Resource Explorer から実行することも出来ます。実行には多少時間がかかります。

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

そして Cloud Shell などから Azure CLI を使っても実行することが出来ます。ARM Action として提供されているので、割と簡単に叩くことが出来て便利ですね。

az resource invoke-action --resource-group <ResourceGroupName> --action syncfunctiontriggers --name <SiteName> --resource-type Microsoft.Web/sites

そもそも何故メタデータの同期が必要かというと、Queue や Timer の場合は実行しないといけないタイミングになっても、Function 本体がそもそも立ち上がっていないことがあるからです。

Consumption では必要な時に必要な数立ち上げることになるので、インスタンスを管理する Scale Controller はそういった Trigger の情報を知っておく必要があるというわけです。

今回、私の場合は TimerTrigger で発生しましたが、原理上は HttpTrigger 以外でも発生するので Run From Package を使って外部 URL の zip デプロイする際には気を付けましょう。

後から CRON 式を変更したり、新しく Function を追加した場合にもメタデータが同期されないことがあるみたいなので、地味にはまります。早く直ってくれると良いのですが、少し時間がかかりそうです。

ちなみに syncfunctiontriggers を実行後は TimerTrigger が正しく動くことを確認しました。

Azure SignalR Service の GA と ASP.NET SignalR への対応がプレビューになったので試した

これも Ignite 2018 で発表されましたが、予定通り Azure SignalR Service が GA となりました。

分かりやすい変更点としては SLA 99.9% が付いたり、Standard Tier での 100 units までのスケールアウトがサポートされて、仕様上は 10 万コネクションまで扱えるようになりました。

GA 後の Standard Tier の価格は 1 unit 当たり 2800 円 / 月ぐらいなので、Redis Cache のサイズなどで悩んだりするより、SignalR Service に移行した方が全体的なコスト最適化となる可能性が高いですね。

今後は Standard より上の Tier も出るみたいですし、スケーリングに関しては大体のケースで問題となることは無くなるでしょう。ちなみに今回は珍しく Japan East に最初からデプロイされました。

それよりも気になったのは、サポートされているライブラリの紹介の 1 文です。

The Azure SignalR Service supports existing libraries for ASP.NET Core, ASP.NET, Java, and JavaScript clients, opening this service to a broad array of developers.

ASP.NET Core や Java / JavaScript は分かるんですが、ひっそりと ASP.NET も入っています。

気になったので Ignite 2018 の SignalR Service セッションを確認すると、Core じゃない ASP.NET SignalR を使って SignalR Service を利用するデモが行われていました。

ひっそりと ASP.NET SignalR 向けのライブラリが公開されていましたが、ダウンロード数が少ないですね。

ASP.NET Core SignalR への移行は地味に面倒な部分があるので、既に ASP.NET SignalR でアプリケーションを動かしている場合でも SignalR Service が選択肢に入りそうです。

早速なのでサンプルアプリケーションを使って試しておきました。とりあえず SignalR Service を新しく作るところから始めます。

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

何となく Japan East にデプロイしました。とりあえず 2 units を使うようにしてあります。

サンプルアプリケーションは懐かしい Stock Ticker を使いました。空の ASP.NET プロジェクトに NuGet からインストールするだけなので非常に楽です。同時に先ほどのライブラリもインストールします。

SignalR Service を使うための設定は OWIN Startup で呼び出されている MapSignalRMapAzureSignalR に変更するだけです。この辺りは Core SignalR と同じような感じです。

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // ↓ は要らない
        //app.MapSignalR();

        // applicationName は適当で良い
        app.MapAzureSignalR(GetType().FullName);
    }
}

最後に Azure Portal からコピーしてきた SignalR Service の接続文字列を Web.config に追加します。

名前はデフォルトだと Azure:SignalR:ConnectionString が使われるので、今回はその名前で追加しました。MapAzureSignalR の呼び出し時に明示的に設定も出来るみたいです。

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

これで SignalR Service を使う設定は完了です。Core SignalR と同じぐらい簡単に設定できました。

最後にちゃんと SignalR Service が使われているのか動作を確認するわけですが、分かりやすいのは WebSocket の接続先が SignalR Service になっているかどうかです。

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

無事に接続先が SignalR Service になっていることが確認出来ました。簡単でしたね。

とはいえ、個人的には ASP.NET Core SignalR に移行が可能な場合は、そっちに移行した方が良いと思っています。今回の ASP.NET SignalR への対応のために SignalR 自体もバージョンが上がっていて、地味にプロトコルバージョンが 2.0 に変わっています。

なので、C# / JavaScript 以外のクライアントを使っていると動作しない可能性が高いです。注意しましょう。

Azure Key Vault を使って Let's Encrypt 周りを自動化する Function を作った話

Twitter で世界のやまさが Azure Front Door を試していて、Key Vault 向けにも Let's Encrypt の証明書を自動でいい感じに出来れば良いみたいなオーラを感じ取ったので、前に作った App Service 向けのものからコピペで新しく Key Vault 向けを作ってみました。

鍵は Key Vault に保存されているので安全です。てか公式で欲しい Integration でした。

ちなみに Key Vault は単に証明書をストアする機能だけではなく、新しく CSR を作成したり、その後認証局で署名して貰った証明書をマージする機能もあるので、実装自体は非常に簡単でした。

統合された CA の場合は簡単なんですが Let's Encrypt は対応していないので、例によって Managed Service Identity を有効にした Azure Functions で処理させることになります。

Key Vault に証明書を格納すると、他の Azure サービスから簡単に扱えるようになるので便利です。Key Vault 内の証明書はバージョニングされているので、更新するといい感じに新しいものが使われていきます。

今のところは Front Door / Application Gateway v2 / App Service が Key Vault に対応しています。

App Service に関しては Key Vault がまだ存在しない時期からのサービスなので、統合がちょっと弱いです。

とりあえず簡単に使い方を書いておきます。デプロイすると少なめの Functions が表示されるはずです。

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

App Settings に今回 Key Vault の URL が必要になっているので、LetsEncrypt:VaultBaseUrl というキー名で Key Vault のアドレスを設定しておきます。

例によって ARM Template を使ってデプロイすると、自動的に Managed Service Identity が有効化されるので、それに対してアクセス権を設定していきます。DNS-01 を使っているので Azure DNS への IAM 設定と、Key Vault の Access policy の設定が必要です。

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

Access policy は Certificates に対して List / Get / Create / Update が出来れば良いはずです。

ここまでの設定で証明書の発行が行えるようになっているので、適当な設定済みドメインを使って AddCertificate_HttpStart を叩けば証明書の発行処理が走ります。

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

今のところ Key Vault が RSA にしか対応していないので、デフォルトでは RSA 2048bit な証明書となります。App Service の方は単純にするために ECC 256bit 固定にしていました。

今回は最初から SAN 証明書に対応しています。コピペで作ったので当然ですね。

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

証明書の自動更新にも対応しているので、App Service 版と同様に 30 日前になると更新が行われます。

複数の App Service に同じ証明書を当てる場合には、通常ならそれぞれのリージョンの Service Plan 向けに PFX をアップロードする必要がありますが、Key Vault を使うと 1 箇所から読み込んでくれるので便利です。

Azure DNS の Alias Records を使って Zone Apex で Traffic Manager + App Service な環境を作る

Ignite 2018 の前から Azure DNS を弄ってたら気づいた Alias Record Set が GA しました。*1

Traffic Manager か Public IP Address の場合のみ利用可能という制約がありますが、長らく Zone Apex なドメインは使えなかったという問題が解消です。

これまでは www を付けて CNAME という方法しかなかったので嬉しいです。

早速 App Service を Traffic Manager に追加し、世界中のリージョンに App Service をデプロイして負荷分散を試そうかと思ったのですが、Alias として Traffic Manager を追加しようとするとエラーになりました。

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

今のところ Traffic Manager に IP アドレスで追加していないと、Zone Apex なドメインに Alias は作れないらしいです。そして普通に Traffic Manager に App Service を追加すると、ドメイン名で追加されるのでエラーになるというわけです。

ならば IP アドレスとして追加してやればよいので、External Endpoint として追加します。App Service は A レコード向けに VIP が提供されているので、この IP アドレスを使います。

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

VIP をメモして Traffic Manager に追加します。そのままだとモニタリングが落ちるので、カスタムヘッダーとして Host を追加して SNI でちゃんと処理されるようにします。

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

将来的には IP アドレスとして追加する機能があれば嬉しいですね。比較的簡単そうですし。

これでもう一度 Alias Record を追加すると、今度は上手くいくはずです。

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

実際にブラウザでアクセスすると、ちゃんと App Service でホストしているページが表示されます。HTTPS の場合も SNI で App Service にバインドを追加しておけば問題ないです。

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

ちなみに nslookup を使って名前解決を試すと、以下のような結果になります。

www の方は Alias ではなく CNAME を使っているので、結果が少し異なっていますが同じ IP アドレスが返ってきていることが確認できます。

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

App Service は Run From Package で複数の App Service に対してのデプロイが非常に簡単かつ、一貫性のあるデプロイが行えるようになっているので、複数リージョンに展開することも簡単になってきています。

実際に運用する場合には証明書の扱いや、デプロイフローを多少考える必要がありますが、今度適当に試してまとめてみたいと思います。

*1:Azure DNS 系は突然出てきて GA というのが多い気がする

Azure Functions v2 が GA しました

Ignite 2018 で大方の予想通り Azure Functions v2 ランタイムが GA しましたね。

実際には先週に GA 版と思われるランタイムがデプロイされていたので、App Settings の設定を間違えていなければ既に GA 版ランタイムで動いているでしょう。

v2 では .NET Core 2.1 ベースになったり、拡張モデルが新しくなってパフォーマンスも改善したりと、色々とメリットが多いですね。99.95% の SLA も提供されています。

そして既存の Functions v1 アプリを v2 にアップグレードすることが推奨されています。新しく作る場合には v2 ベースで作る以外の選択肢はまあ無いでしょう。

We strongly encourage to start any new development on 2.0 and recommend that customers upgrade their existing 1.0 apps to the 2.0 version, to experience all the benefits of this new release.

まだ使われてないですが、.NET Core 2.2 がリリースされたら Tiered Compilation での Cold Start のパフォーマンス改善とかも期待できますしね。

GA に絡めて v2 周りの情報を軽くメモしておきます。

v2 の NuGet パッケージを更新

Functions v2 の GA に伴って、いろんなパッケージが正式版にアップデートされています。特に拡張周り。

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

なので既存の Functions v2 向けのアプリの場合は、各パッケージを最新に更新しておけば良いです。

v1 から v2 へのアップグレード

アップグレードが推奨されていますが .NET Framework から .NET Core で ConfigurationManager が無かったりするので、設定を読んでる場合とか少し面倒です。

ドキュメントが用意されてますが、割と気合で頑張るという感じがします。

難しいことしていなければ、csproj を弄って再ビルドするぐらいで問題なさそうですが、.NET Standard 2.0 非対応のライブラリを使っていたりすると更に手間がかかるでしょう。

C# は ANCM v2 In-Process で動く

ASP.NET Core 2.2 とリリース予定の ASP.NET Core Module v2 (ANCM v2) が、Functions v2 では先行してデフォルトで使われています。

w3wp を Kudu で見ると、モジュールが読み込まれていることが確認できます。

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

スタートアップ時のオーバーヘッドが減るので立ち上がりの高速化が期待できます。エラーハンドリング周りでも有利という話は前から出ていますが、あまり実感としてはないですね。

あくまでも .NET Standard 2.0

Functions v2 のランタイムは .NET Core 2.1 で動いていますが、開発する Functions App は .NET Standard 2.0 になるので、.NET Core 2.0 や 2.1 で新しく追加されたクラスは使えないです。

一応、中の人も認識はしているようで TFM を netcoreapp2.0 に変えることは出来ます。

これで .NET Core 2.0 の機能は使えるようになりますが、ローカルでのデバッグが出来なくなる問題があります。この辺りの優先度は高くないみたいなので、使う時は覚悟が必要となります。

Run From Package を使う

以前は Run-From-Zip と呼ばれていた機能ですが、GA の少し前に Run From Package に名前が変わりました。同時に App Settings のキー名も変わっています。

Azure Functions のストレージの遅さを補いつつ、一貫性のあるデプロイを提供してくれる機能です。

最新の Visual Studio 2017 を使っている場合はチェックボックス一つで有効化出来るので、あえて使わない理由はないです。CI からのデプロイも Zip Deploy で簡単になっています。

特に Functions v2 では MSDeploy を使っていると dll がロックされてしまうことが多いですが、Run From Package を使うと一気に解決します。

理由が無ければ 32bit で動かす

Functions v2 は 32bit と 64bit の両方に対応していますが、そもそもが Consumption Plan では公式には S1 が使われるとなってますし、64bit にしてもメリットはほぼ得られないでしょう。

ちなみに App Service には .NET Core の 64bit 版ランタイムが入っていないので、Self-contained deployment になっています。.NET Core のバージョンが 32bit と 64bit でずれる可能性がありそうです。

Azure Data Factory を利用して Cosmos DB のコレクションをお手軽にコピーする

Azure の Cosmos DB を使っていて非常に困るのが、コレクションのパーティションキー設計を変更したい場合になった時です。パーティションキーは作成時にしか設定できず、後から変えることは不可能です。

設計ミスと言われると何とも言い返せないのですが、こういうのは使ってから発覚するものなのです。一応ある程度までは RU を監視して、クエリを最適化することで改善は行えました。

しかし、大体はクロスパーティションなクエリのせいで問題が発生するので、必要ない場合はパーティションキーを使わないという選択肢を取った方が良いことが多いように思います。

なので、新しくコレクションを作成してデータを全て移行すれば一応は解決するのですが、Cosmos DB にはコレクションのコピーといった機能は用意されておらず、パーティションキーと RU が関わってくるので自分で用意するのは現実的ではないように思います。

そこで Azure Data Factory を使います。コピーアクティビティを使ってコピー元に古いコレクションを、コピー先に新しいコレクションの指定出来るので簡単にコピーが可能でした。

ドキュメントには Cosmos DB => Cosmos DB に関しては書かれていないですが、特に設定さえ行えば問題ないようです。RU に関してもリトライを適切に行ってくれるみたいです。

とりあえず実際に Cosmos DB のコレクションコピーを行ってみます。パーティションキーを設定しているコレクションから設定していないコレクションへのコピーです。データは適当に用意しました。

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

パーティションキーは GUID にしたので、クロスパーティションクエリを使わない限り、このデータは取り出せません。このデータをそのまま新しいコレクションにコピーするのが目的です。

次に Data Factory を Cosmos DB と出来れば同じリージョンに作成して、専用のポータルにアクセスします。Copy Data という非常に分かりやすいボタンがあるのでクリックして設定を進めていきます。

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

ウィザード形式でパイプラインの作成が進んでいくので、良い感じに設定していきます。

コピータスクは基本的には 1 回しか実行しないので "Run once now" を選択しておきます。

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

次はコピー元のデータストアを選びます。Cosmos DB の接続を新しく追加するだけです。

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

接続を作成すれば、Cosmos DB の情報が表示されるようになるので、そこでコピー元になるコレクションを選択します。データはプレビューされるので便利です。

データの変換が必要な場合は、ここでスキーマやクエリを変更することも出来るみたいですが、今回は全データのコピーなので下の方にあるチェックを入れました。これを有効にするとそのままの JSON としてデータをエクスポートしてくれます。

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

次はコピー先の設定です。同じように Cosmos DB の接続を選ぶと、どのコレクションにコピーするか選べるようになります。この辺りは単純なので迷うことはなさそうです。

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

これで設定完了なので、後はボタンをポチポチ押していくと、パイプラインのデプロイと実行が行われます。1000 ドキュメントのコピーでしたが、一瞬で完了しました。

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

新しいコレクションのデータを確認すると、ちゃんとそのままの形でコピーされています。

Cosmos DB が管理用に作成するデータは変わっているので、タイムスタンプなどは失われています。

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

パーティションキーに使っていたデータなどはそのままの状態で、コレクション側のパーティションキーが無くなっているだけなので、非常に扱いやすいデータになりました。

例えばカラムを typo していたとか、構造を少し変えたいといった場合にも Data Factory のコピーが使える気がします。スキーマレスといってもデータが既にある場合には構造を変えるのは難しいものです。