しばやん雑記

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

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 のコピーが使える気がします。スキーマレスといってもデータが既にある場合には構造を変えるのは難しいものです。

Azure Functions で SendGrid の Dynamic Templates を使う

この間書いた SendGrid の Dynamic Templates ですが、今朝に対応した SendGrid の C# クライアントがリリースされていたので、早速 Azure Functions の SendGrid バインディングで使ってみました。

Dynamic Templates 自体については前回のエントリを参照してください。テンプレートもそのまま使います。

SendGrid C# クライアントの 9.10.0 から Dynamic Templates に対応しました。Azure Functions での SendGrid バインディングに関してはドキュメントがあるので、それを見れば簡単です。

Azure Functions v2 を使っているので、3 系のパッケージを入れる必要があります。

ちなみにこのバインディングは SendGrid C# クライアントの 9.9.0 以上を参照しているので、プロジェクト側で明示的に新しいバージョンをインストールすれば、そのバージョンが全体的に使われます。

実際に 9.10.0 をインストールすると、依存バージョンが適切に選択されていることが分かります。

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

それでは実際にコードを書いていくのですが、今回はモデル用のクラスを適当に作りました。

こまめに JsonProperty で名前を指定していますが、この辺りは Json.NET の設定で自動 camelCase が出来たはずなので、好みの問題という感じもします。

public class TemplateModel
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("message")]
    public string Message { get; set; }

    [JsonProperty("items")]
    public TemplateItemModel[] Items { get; set; }
}

public class TemplateItemModel
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("price")]
    public int Price { get; set; }
}

テンプレートは前回と同じなので、必要な構造を単純にクラスに起こしただけです。

実際にメールを送信する関数は以下になります。テンプレートから生成したら SendGridMessage を return する形で作られたので、今回はその方式で書きました。SendGrid のバインディングには何種類か送信する方法が用意されていますが、Dynamic Templates を使う場合には SendGridMessage が必要になります。

今回は簡単にするために HttpTrigger を使いましたが、テンプレートでは QueueTrigger を使う形で生成されるので、非常に Dynamic Templates と相性が良いと思います。

public static class Function1
{
    [FunctionName("Function1")]
    [return: SendGrid]
    public static SendGridMessage Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
                                      HttpRequest req, ILogger log)
    {
        var model = new TemplateModel
        {
            Name = "kazuakix",
            Message = "8GB",
            Items = new[]
            {
                new TemplateItemModel { Name = "Item 1", Price = 1980 },
                new TemplateItemModel { Name = "Item 2", Price = 2980 },
                new TemplateItemModel { Name = "Item 3", Price = 3980 }
            }
        };

        var message = new SendGridMessage
        {
            From = new EmailAddress("from@example.com"),
            TemplateId = "TEMPLATE ID"
        };

        message.AddTo("to@example.com");
        message.SetTemplateData(model);

        return message;
    }
}

SendGrid のアクセスキーは AzureWebJobsSendGridApiKey という名前で App Settings に追加しておけば使われます。特に別名を付ける必要もないとおもうので、デフォルトで良いと思います。

この関数を実行すると、前回と同様にメールの文面がレンダリングされて届きます。

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

Azure Functions でメールを送信する場合にはテンプレートを用意するのが面倒かつ、RazorEngine を使った場合には新しくインスタンスが立ち上がる度にテンプレートのコンパイルが必要になるので無駄が多いです。テンプレートのコンパイルは結構なコストです。

その点、Dynamic Templates を使うと Azure Functions では Queue からデータを受け取って、SendGrid に流すという単純な処理になるのでスケーリングも行いやすくなります。

テンプレートの管理が必要無いという以外にもメリットは多いです。公式クライアントも出ましたし。

Azure Functions の Key Management について調べた

Azure Functions で HTTP Trigger や Webhook を使う時に指定する API Key ですが、主に Durable Functions を使う時に悩んだのでメモとして残しておきます。

Portal で適当な関数を選んで URL を取得する時に、どのキーを使うか選べます。

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

このキーはユーザーが自由に生成したり、削除や作り直しが出来ます。

Host Key を使うとどの関数に対しても同じキーでアクセスが出来るので、便利な反面危険でもあります。なので普通は Function Key を使うことになると思います。

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

Azure Portal 上では Function Key と Host Key しか確認出来ないですが、Azure Functions は内部で System Key も持っているので、実際には 3 種類のキーが存在していることになります。

ちなみに Azure Functions v2 から Key のデフォルト保存場所が共有ストレージから Blob に移動したので、Storage Explorer などを使うとファイルがあることを確認出来ます。

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

格納されているキーは暗号化されているので、そのままでは使うことは出来ません。

この辺りの変更に関しては Wiki にまとまっているので参照してください。

Changes to Key Management in Functions V2 · Azure/azure-functions-host Wiki · GitHub

App Settings の値を変えることで、これまで通りに共有ストレージに保存するように出来ます。

ここからが本題ですが、Durable Functions にはステータス確認やイベントを発生させるための API が用意されています。CreateCheckStatusResponse で返されるやつがそれです。

当然ながら管理用の API なので Key が必要になりますが、これら Durable Functions に用意されている各 API を実行するには System Key が必要になります。

このキーは Azure Portal からは確認出来ないため、CreateCheckStatusResponse を実際に叩くか Key Management API を利用して取得します。

ドキュメントには v1 と書かれていますが、今のところ v2 でも動作しました。

Key management API (V1) · Azure/azure-functions-host Wiki · GitHub

ぱっと見では Function Key と Host Key しか取得できなさそうですが、/admin/host/systemKeys にリクエストを投げると System Key の一覧が返ってきます。

当然この API も認証が必要なので、Authorization ヘッダーで Bearer Token を指定することになります。

curl -H "Authorization: Bearer TOKEN" https://***.azurewebsites.net/admin/host/systemKeys

ドキュメントには書かれていないのが罠なのですが、Bearer Token を取得する API が Kudu 側に用意されているのでそっちを先に叩いてトークンを取得後、Key Management API を叩くことになります。

取得方法は /api/functions/admin/token を叩くだけなので簡単です。

SCM 側なのでブラウザで直接アクセスしても取得できますが、コードからアクセスする場合には Basic 認証がこれまで通り使えます。このトークンの有効期限は短いので注意。

これで Key Management API を実行すると、Durable Functions 向けに自動生成されたキーを取得できます。

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

同じような名前のキーが 2 つあるのは、この間の Breaking Changes が関係しているのだと思いますが、System Key なので両方とも問題なく使えます。

これを利用して Durable Functions の実行状態を確認するためのダッシュボードを作ろうと思ったのですが、一瞬で飽きてしまいました。Rewind API が増えて需要はありそうなのですが残念。

Durable Functions v1.6 の Rewind API を使うと失敗した関数から再実行が可能に

Azure Functions v2 の Breaking changes に合わせて Durable Functions v1.6 がリリースされました。

今回の Breaking changes は Run From Package で配布している場合に地味に大変でしたが、その話はまた今後どこかで書こうかと思いますが需要なさそう。

オーケストレーションのパフォーマンスが改善してたりするみたいなので、とりあえずアップデートはしておいた方が良いでしょう。Azure Functions v2 を使ってる場合は必須ですよ。

さて、メインは新しい Functions Runtime への対応っぽいですが、リリースノートには非常に興味深い機能が追加されていると書かれていました。失敗したオーケストレーションをやり直す機能です。

Rewind failed orchestrations (preview, #301): A new API has been added which allows you to "rewind" failed orchestration instances back to their previously good state. This can be a life-saver when you have long-running orchestrations that fail in unexpected ways.

Release Durable Functions v1.6.0 Release · Azure/azure-functions-durable-extension · GitHub

まだプレビューなので不具合があるかも知れないですが、これは待望の機能です。

これまで Durable Functions は Event Sourcing で実行の履歴が全て管理されているのに、失敗した場合には新しく最初からやり直すしか方法が無い点に不満を持っていました。

Added rewind endpoint to HTTP client response links by cmkerner20 · Pull Request #341 · Azure/azure-functions-durable-extension · GitHub

ちなみにこの Rewind 機能を実装したのはインターンの方らしいです。素晴らしいコントリビューションとして言いようがないですね。

使い方はまだドキュメントが公開されてないですが、実装を見ればすぐにわかりました。普通にオーケストレーションを開始すると、レスポンスに rewindPostUri が追加されています。

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

オーケストレーションが失敗した場合、このエンドポイントを POST で叩くと再実行されます。

とりあえず実際に動作を試して見ました。サンプルなので適当なオーケストレーターとアクティビティを用意して、2 つ目のアクティビティはわざと例外を投げて落ちるようにしています。

[FunctionName("Function1")]
public static async Task RunOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var value = context.CurrentUtcDateTime.Ticks.ToString();

    await context.CallActivityAsync("Function1_Task1", value);
    await context.CallActivityAsync("Function1_Task2", value);
    await context.CallActivityAsync("Function1_Task3", value);
}

[FunctionName("Function1_Task1")]
public static void Task1([ActivityTrigger] string value, ILogger log)
{
    log.LogInformation($"Task1 running: Input {value}.");
}

[FunctionName("Function1_Task2")]
public static void Task2([ActivityTrigger] string value, ILogger log)
{
    throw new Exception();
    //log.LogInformation($"Task2 running: Input {value}.");
}

[FunctionName("Function1_Task3")]
public static void Task3([ActivityTrigger] string value, ILogger log)
{
    log.LogInformation($"Task3 running: Input {value}.");
}

アクティビティが失敗して、オーケストレーションが Failed になったのを確認後、Rewind API を叩いて再実行をリクエストします。reason には適当に理由を入れておけば、ログに出力されるので分かりやすいです。

実際にどのように実行されているかはログを見ないと理解しにくかったので、Application Insights でクエリを書いて分かりやすく並べました。まずは初回の失敗したログです。

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

Function1_Task2 が例外を投げたので、オーケストレーターも Failed していることが確認できますね。

次に Rewind API の実行後のログです。Instance Id は Rewind 後も変化しないので追跡が可能です。

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

オーケストレーターのステータスが Rewound に変化して、再実行が行われています。

前回の実行で成功した Function1_Task1 は再実行されずに Function1_Task2 から実行されていることが確認できます。もちろん状態も前回のままなのでオーケストレーターの作法に従って CurrentUtcDateTime などを使っていれば、安全に再実行が行えます。

アクティビティのコードに不具合があった場合には、修正をデプロイ後 Rewind API を使えば失敗した処理から続きを実行出来るので、リカバリの選択肢が広がりそうです。もちろん外部の API を叩いている場合も同様に使えるでしょう。

個人的にはこれで Durable Functions は完成形になったのではないかと思っています。使わないと損します。

Let's Encrypt 周りの自動化を行う Azure Functions の正式版をリリースした

前に作った Let's Encrypt の自動化を行う Azure Functions ですが、あれからもちょいちょい弄り続けた結果、先日やっと正式版を出せました。

いろんなバグやよくわからない Azure REST API の仕様などを乗り越えてきました。

今回の正式版リリースでは以下のような機能を実装しています。

  • 全ての App Service に対応
    • Web Apps / Functions / Web App for Containers など(Windows と Linux に対応)
    • Linux と Containers は Azure DNS が必要
  • ワイルドカード証明書の発行
    • これも Azure DNS が必要
  • wwwroot の readonly モードに対応
    • Run From Package を使った場合に readonly になる
  • RBAC で権限を付与すれば 1 つの Function App で全てに対応

前回のリリースからいろんな部分が大きく変わったのと、HTTP-01 だけじゃなく DNS-01 にも対応したので使い方も簡単に紹介しておくことにします。

必要なロールの変更

以前のバージョンでは Website Contributor だけ追加すれば動作していましたが、今のバージョンでは App Service が含まれているリソースグループには Website Contributor が、App Service Plan が含まれているリソースグループには Web Plan Contributor が必要になりました。

App Service と App Service Plan が同一リソースグループに含まれている場合は、両方のロールをリソースグループに付与します。別々のリソースグループに含まれている場合は少し注意。

Deploy to Azure Button の追加

以前は手動でデプロイしてくださいという形にしていましたが、今は ARM Template と Run From Package を利用して簡単に Function App のデプロイと初期設定が行えるようになっています。

リソースグループ、アプリ名、メールアドレスを入力すれば完了です。

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

サブスクリプション ID はデフォルトでデプロイ先のものが App Settings に設定されます。

Run From Package を使っているので、新バージョンへのアップデートは再起動するだけで終わります。

ワイルドカード証明書と Linux / Containers

Let's Encrypt ではワイルドカード証明書の発行には DNS-01 を使う必要があるので、同じサブスクリプションに設定済みの Azure DNS が必要です。そして IAM で DNS Zone Contributor の付与も必要です。

同様に発行したいドメインを App Service に追加さえしておけば、Function の実行で自動的に ACME Challenge 用の TXT レコードが作成されます。

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

カスタムドメイン用の A / CNAME レコードは事前に設定しておく必要があります。

Windows 以外の App Service や Web App for Containers の場合は HTTP-01 が使えないので、その場合も DNS-01 を使うことになるため Azure DNS が必要となります。

古い証明書の削除

証明書の期限まで 30 日を切っていて、どの App Service にもバインドされていない証明書を定期的に削除する Function を追加しています。

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

動作時刻は証明書の更新からずらしているので、同時には動かないようになっています。

おまけ

Let's Encrypt のアカウントキーは Function App の D:\home\.acme 以下に保存されています。

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

問題が発生した場合は削除すれば、また次回起動時に新しいアカウントが作成されます。

Azure Web Apps でデフォルト以外のパフォーマンスカウンターを Application Insights に送信する

Azure App Service では実行ユーザーの権限に制限がいろいろとかかっているために、Application Insights などを使ったパフォーマンスカウンターの送信はそのままでは出来ません。

しかし、App Service は環境変数を使ってパフォーマンスカウンターの値を渡してくれるので、それを使って Application Insights に値を送信できるようになっています。App Service 向けに専用の Collector が実装されているので、デプロイすればすぐに使えます。

対応しているカウンター名は実装から読み取った方が間違いが無くて正確です。

ASP.NET / Process / .NET CLR 周りのカウンターを公開してくれているので、Application Insights に送信したい場合には、ApplicationInsights.config に追加するだけで使えます。

例えば .NET CLR 周りのカウンターを送信する場合には以下のように書きます。

<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
  <Counters>
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 0 Collections" ReportAs="# Gen 0 Collections" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 1 Collections" ReportAs="# Gen 1 Collections" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 2 Collections" ReportAs="# Gen 2 Collections" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\Gen 0 heap size" ReportAs="Gen 0 heap size" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\Gen 1 heap size" ReportAs="Gen 1 heap size" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\Gen 2 heap size" ReportAs="Gen 2 heap size" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\Allocated Bytes/ sec" ReportAs="Allocated Bytes/ sec" />
    <Add PerformanceCounter="\.NET CLR Memory(??APP_CLR_PROC??)\% Time in GC" ReportAs="% Time in GC" />
  </Counters>
</Add>

アプリケーションをデプロイして暫く待つと、Metrics から設定したパフォーマンスカウンターが見れるようになっているはずです。

後はグラフを描画したり、Analytics でクエリを投げたりといろいろと活用できます。

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

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

動作を確認していた時に気が付いたのですが、昔は表示されていなかった Available Memory が最新の Application Insights をインストールすると確認出来るようになっていました。

少し前のパッケージでは警告が出ていたので、比較的最近に対応が行われていたようです。

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

ちなみに現在プレビュー中の Web App for Containers の Windows 版では、現状は何故かカウンターの値が読めないです。報告済みなので GA までには何とかなると信じてはいます。

Windows Containers は Docker Image を作成する時には Container Administrator という管理者ユーザーとして動くので、GA すれば割と使い勝手は良くなると思います。