しばやん雑記

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

Azure App Service の Windows Containers 対応が Public Preview になったので試した

前からちょいちょいと情報が出ていた Web App for Containers の Windows 対応が Public Preview としてリリースされました。これで App Service は Windows / Linux で全て出そろった形になります。

基本的な情報は公式ブログと App Service Team のブログを読めば良いです。GAC や GDI など制限なしに扱えるようになるのがメリットとして挙げられてますが、Server Core というのだけは注意ですね。

https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-windows-container-support-in-azure-app-service/

対応しているイメージは LTSC 2016 限定なので、開発環境が 1709 や 1803 の場合は latest ではなく明示的にタグでバージョンを指定する必要があるので注意。

恐らくは本番は Windows Server 2019 がリリースされた時だと思っています。Semi-Annual Channel は使いにくいので、2019 になったタイミングで様々な恩恵を得られるはずです。

とりあえず実際に App Service を作成して試します。Web App for Containers の作成を行うと、新しく Linux と Windows が選べるようになっています。

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

公式ブログにあったように App Service Plan は Windows Containers 用に Premium Container というプランが新しく追加されています。Premium V2 が Dv2 系だったのに対して、Premium Container は Dv3 です。

Premium V2 よりもメモリが倍以上になっています。Windows Containers はメモリを食うので仕方ないですが、裏側が Hyper-V Containers になっている気もします。

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

気になる価格ですが、8 月中は無料になっているみたいなのでまだ公開されてなさそうです。9/13 から 50% 引きのプレビュー価格が始まるようです。

コンテナイメージの設定は Linux と同じです。ACR / Docker Hub / その他が選べます。

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

CI/CD の場合は例によって Docker Image だけ入れ替えれば良いので楽です。

設定時に対応していないベースイメージかどうかの検証を行ってくれるので、間違って 1709 や 1803 のイメージを設定するような事故は起こりにくそうです。

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

設定完了後、デプロイ自体は 20 秒ぐらいで完了しますが、それからコンテナが立ち上がってページが見えるようになるまで数分かかります。地味に時間がかかるのが罠です。

コンテナの起動中は以下のようなページが表示されるので、本番で運用する場合は別スロットを使ってスワップが必要になりそうです。

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

起動処理は Docker Image の pull も行っているので、キャッシュが効くイメージを使うと改善します。

数分待つとアプリケーションが立ち上がります。普通の ASP.NET アプリケーションが動きました。

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

Docker Image を作るのは AppVeyor や VSTS を使って行えば、簡単に CI/CD が組めるのはメリットです。規模が大きくなって AKS に移行する場合も、同じ Docker Image を使えるので楽です。

ちなみに Kudu はちゃんと生きています。Debug Console を使うと Docker のログを簡単に確認できるので便利です。Log stream は開いておかないと見れないですが、こっちはいつでも確認できます。

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

ただし Process Explorer や Site Extension はほぼ無意味なので、使うことは少ないでしょう。

Windows Containers な Web App for Containers は最初から Deployment Slot が使えます。

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

Linux の時と同様に、デプロイに関しては Deployment Slot を使うのはほぼ必須でしょう。

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

スワップを実行すればスロットが入れ替わってくれますが、今の実装ではスワップ実行時に差分が無くても必ずコンテナが再起動してしまうようで、全く持ってメリットがありません。

普通は再起動しないので、今後改善されるとは思いますが、現時点では本番向けには使えませんね。

Linux のように SSH は使えませんが、代わりに Win-RM を使って実行中のコンテナに入って操作することが出来ます。On にすると PowerShell を使ってログインできます。

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

実行中のコンテナにログインする必要をほぼ感じないので、正直使うことはないかなと思っています。むしろ使ったら負けだと思っています。

通常の App Service では実行できなかったアプリケーションも、Windows Containers を使うと制約がほぼ無くなるので移行パスとして良い選択肢になるかと思います。AKS は流石に大げさというケースも多いはず。

Azure Functions と Application Insights を同時に ARM Template でデプロイする

今の Azure Functions は Application Insights が無いとモニタリング周りが成り立たないですが、ARM Template を使うと単独でデプロイされるので同時に Application Insights を作るようにします。

Azure Functions の Consumption Plan で作る場合のサンプルは用意されているので、これをベースに Application Insights のデプロイを追加します。

ARM Template の apiVersion を新しいバージョンに変更すると作成時にエラーになったりするので、非常に扱いにくい項目です。新しいバージョンを選べないのは謎ですね。

まずは Application Insights を作成してからじゃないと Instrumentation Key が取得できないので、リソースを作成する順番も重要です。実際の定義を順に見ていきます。

Application Insights のリソース定義

作成に必要な項目は名前ぐらいなので、非常にシンプルです。大体は App Service と同じ名前でリソースを用意すると思うので、同名で作るようにしておきます。

{
  "type": "Microsoft.Insights/components",
  "name": "[variables('functionAppName')]",
  "apiVersion": "2014-04-01",
  "location": "[resourceGroup().location]",
  "tags": {
    "[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', variables('functionAppName'))]": "Resource"
  },
  "properties": {
    "applicationId": "[variables('functionAppName')]"
  }
}

apiVersion によっては項目が変わっているみたいですが、ひとまず動作したものを紹介しておきます。

Azure Functions のリソース定義

上で作成した Application Insights を使う側になる Azure Functions のリソース定義です。Function Runtime のバージョンは v2 を指定してあるので、必要であれば変更してください。

{
  "type": "Microsoft.Web/sites",
  "name": "[variables('functionAppName')]",
  "apiVersion": "2016-03-01",
  "location": "[resourceGroup().location]",
  "kind": "functionapp",
  "dependsOn": [
    "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
    "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
    "[resourceId('Microsoft.Insights/components', variables('functionAppName'))]"
  ],
  "properties": {
    "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
    "siteConfig": {
      "appSettings": [
        {
          "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
          "value": "[reference(resourceId('Microsoft.Insights/components', variables('functionAppName')), '2015-05-01').InstrumentationKey]"
        },
        {
          "name": "AzureWebJobsDashboard",
          "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'),'2015-05-01-preview').key1)]"
        },
        {
          "name": "AzureWebJobsStorage",
          "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'),'2015-05-01-preview').key1)]"
        },
        {
          "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
          "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'),'2015-05-01-preview').key1)]"
        },
        {
          "name": "WEBSITE_CONTENTSHARE",
          "value": "[toLower(variables('functionAppName'))]"
        },
        {
          "name": "FUNCTIONS_EXTENSION_VERSION",
          "value": "beta"
        }
      ],
      "clientAffinityEnabled": false
    }
  }
}

重要なポイントは dependsOnAPPINSIGHTS_INSTRUMENTATIONKEY の定義です。dependsOn を使って Application Insights が作成されるまで待つようにしつつ、App Service 作成タイミングで Instrumentation Key を参照するように書きます。

実際に作成した ARM Template は以下で全体を公開しています。

Azure Portal の Template Deployment へのリンクを貼れば、あっという間に必要なリソースを作成できるボタンが完成するので非常に楽です。

Run-From-Zip を組み合わせるとデプロイも簡単なので最高です。

Azure App Service 向けに Let's Encrypt 周りの自動化を行う Azure Functions を作った

タイトルの通りなんですが、App Service 向けに自動で Let's Encrypt の証明書を更新してくれる Azure Functions を作りました。Durable Functions と ACMESharp Core を使っています。

とりあえずベータリリース的な感じで出してみることにしました。

これまでも Site Extensions と WebJob を使って Let's Encrypt の証明書を更新してくれるものはありましたが、1 サイトに 1 つ仕込む必要があり、結構な確率で失敗することが多かったので不便だと思ってました。サービスプリンシパルを作って設定する必要があったのも面倒でした。

なので、今回の azure-appservice-letsencrypt は以下のような問題を解決するために作りました。

  • 1 つの Azure Function で複数の証明書を更新できるように
  • サービスプリンシパルではなく Managed Service Identity を利用
  • とある API 呼び出しが失敗してもリトライで継続できるように
  • モニタリングが容易に行えるように

Let's Encrypt で証明書を発行するために使う ACME は状態を保持したまま、発行フローを書く必要があったので Durable Functions に最適な場面だと考えていました。

Azure Function をデプロイ

適当に Consumption プランで Azure Functions を作成して、GitHub からクローンしたプロジェクトをデプロイしておきます。*1デプロイすると、Functions が大量に表示されるはずです。

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

そして Application settings から必要なキーを追加していきます。以下のキーを登録する必要があります。

  • LetsEncrypt:Contacts
  • LetsEncrypt:ResourceGroupName
  • LetsEncrypt:SubscriptionId

この中でも LetsEncrypt:ResourceGroupName は将来的には必要なくなりますが、今は Azure Management API 側の不具合で仕方なくリソースグループを貰う必要が出ています。

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

これで基本的な設定は完了です。上で指定したメールアドレスは Let's Encrypt のアカウント用なので、証明書の期限が近くなってくるとメールが届いたりします。

Managed Service Identity を有効化

Azure Function の設定が終わったので、Managed Service Identity を有効化していきます。Platform features から選んで、On にするだけなので簡単です。

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

有効化し終わったら、証明書を更新したい App Service が含まれているリソースグループにて Website Contributor 権限を付与します。この辺りに関しては前に書いた記事があるので、そっちを参照してください。

これで Functions が Managed Service Identity を使って Azure Management API を叩けるようになりました。準備も完了したので、実際に適当な App Service を用意して証明書を発行してみます。

新しく証明書を発行する

手持ちのドメインはほぼ HTTPS 化が完了していたので、テストのために利用価値のないドメインを App Service に追加して証明書の新規発行を試します。

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

新規発行のために AddCertificate_HttpStart という Function を用意しているので、この Function にパラメータを与えて実行すれば証明書の発行とバインドまで行います。

パラメータは JSON で以下のように渡します。

{
    "ResourceGroupName": "RESOURCEGROUP_NAME",
    "SiteName": "APPSERVICE_NAME",
    "Domain": "DOMAIN_NAME"
}

存在しない App Service や追加されていないドメインの場合は、ログを出して処理が終了します。

簡単に実行するためのページなどは用意できていないので、適当なクライアントで上の Function をキックしてあげれば、証明書の発行処理が開始されます。

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

実際に上のリクエストを実行して、20 秒ぐらい待つと App Service に新しく SSL バインドが追加されていることが確認できます。新規の場合は SNI SSL 固定になっています。

Azure Portal はキャッシュが結構行われているので、リロードしないと表示されないことが多いです。

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

実際に HTTPS でアクセスしてみると、ちゃんと Let's Encrypt で発行された証明書が確認できます。

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

既に 4 ドメインの証明書更新をこの Function 1 つだけで行っているので、サイト単位で WebJob を使う方法に比べて非常に簡単になりました。

ちなみに ACME Challenge の応答に仮想アプリケーションを使っているので、未検証ですが Azure Functions に対しても適用出来るはずです。

期限が近い証明書を更新する

デフォルトで RenewCertificates_Timer という Function が 1 日に 1 回動作するようになっています。

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

その時に証明書の発行者名が Let's Encrypt Authority になっていて、期限まで 30 日を切っているものに対して更新を行います。まだ更新に関しては検証が足りていないので、バグが残っている可能性があります。

期限切れの証明書を削除する処理はまだ用意できていないので、今は手動で消す必要がありますが、今後は更新と同様にタイマーなどで削除するように対応予定です。

*1:今は Deploy to Azure Button を使ってもデプロイ出来ます。

バックオーダーを使ってドメインを取得してみた

個人的に App Service の現時点でのランタイム情報とか、プラットフォーム依存の情報を知りたかったので App Service Info というサービスを作っていて、ドメインが欲しかったのですが既に取られていたのでバックオーダーを使った話です。

2 年前から試していて、今月ついに取得できた記念にメモとして残します。ちなみにお名前.com です。

メール通知を設定

お名前.com はドメインを検索して既に埋まっている場合には、利用可能になった時にメールで通知するというサービスを設定できます。

まずは、それを設定するところから始めることになります。ちなみにここまでは無料です。

申請可能メールが届く

通知を設定しても、全然メールが届かなかったのでほぼ存在を忘れかけていましたが、急に以下のようなメールが届きました。一度取得されたドメインというのは、中々開かないという現実ですね。

バックオーダーの申し込み時にクレジットカード決済の設定が必要です。

───────────────────────────────────
■事前予約(バックオーダー)のご案内■
───────────────────────────────────
ドメイン名..................:appservice.info
お申込み期限日..............:2018年7月9日 19:00
───────────────────────────────────

お名前.comをご利用いただき、まことにありがとうございます。

メール通知サービスをお申込みいただいたドメイン名【appservice.info】の
事前予約(バックオーダー)のお申込みが可能になりました事をお知らせいたします。
以下の詳細をご確認ください。

ちなみに費用は 11,060 円でした。新規で .info を取ると数百円なのに対して、バックオーダーを使うと数十倍に跳ね上がってしまいましたが、経験してみたかったので支払うことにしました。

取得に失敗した場合は費用は掛からないので、決済が走った時は成功と考えて良さそうです。

決済完了のメールが届く

申し込みから 1 週間後ぐらいに決済完了のメールが届きました。

───────────────────────────────────
■バックオーダー(gTLD)料金ご請求明細/領収書■
───────────────────────────────────
お名前.comをご利用いただき、まことにありがとうございます。

バックオーダー(gTLD)料金につきまして、ご利用のクレジットカードへの課金が
完了いたしました。下記ご請求明細をご確認ください。

課金されたすなわち取得に成功したということになるっぽいので、後はドメインの管理が出来るようになるのを待つだけですが、ここからが地味に時間かかりました。

バックオーダー完了メールが届く

料金を支払ってから 2 週間ぐらいすると、ようやくバックオーダー完了メールが届きました。時系列が分かりにくそうなので、バックオーダー履歴載せておきます。

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

これで正式にドメインが自分のものとなったわけですが、バックオーダーで取得したドメインは自動的にお名前.com で管理できるようになるわけではなく、別のサービス上で初期設定を行わないといけませんでした。

今回は Network Solutions という会社でしたが、最初は社名的にフィッシングかと思いました。

地味に手続きは手間がかかりましたが、要約するとアカウントの所有権を自分に移す作業が必要でした。確認メールからリンクを踏んだり、パスワードを再発効したりしました。

その後、ようやくドメインの管理画面にログイン出来ました。なので、速攻で Azure DNS に変更します。

Azure DNS で管理

適当に Azure DNS で新しいゾーンを作って、NS を変更するだけなので特に書くこともないですね。

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

管理画面が地味に使いにくかったので、さっさと Azure DNS に移しつつ、ドメイン自体の移管も 60 日後には行ってしまおうかと思っています。

Azure DNS では App Service 用のレコードを追加しました。最近の App Service は A レコードの検証用に TXT を使うようになってるんですね。

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

昔は CNAME で awverify というサブドメインを振る必要がありましたが、TXT の方がこういったドメイン所有者を証明するための用途では筋が良い感じがします。

既に作業済みなので http://appservice.info/ でアクセス可能となっています。HTTPS にはしていないですが、それは後日 Let's Encrypt でサクッと対応する予定です。

Azure Functions v2 で .NET Core (netcoreapp2.0) が使いたい

絶賛プレビュー中の Azure Functions v2 では .NET Standard 2.0 を使って開発が出来ますが、最近は .NET Standard 2.0 では API が不足していて困るケースが出てきました。

具体的には TFM として netcoreapp2.0 となる .NET Core App を使いたいわけです。

プロジェクト作成ダイアログでは、昔は .NET Core でしたが今は .NET Standard と表現が変更された点でも、あまり積極的に対応するつもりはないのかなと思っています。

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

GitHub ではそれなりに Issue が上がっていますが、どうやらあまり動きはなさそうです。

ちなみに Visual Studio から Azure Functions v2 を作成して、単純に TargetFramework を netstandard2.0 から netcoreapp2.0 へ変更するとビルド時にエラーが出るようになります。

エラー内容的には Azure Functions の実行に必要なメタデータを生成する部分で失敗しています。

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

Functions 自体のビルドには成功していますが、ビルド後のアセンブリを読み込んで属性から json を作成する部分で、上手くアセンブリを読めていない状態です。

原因は割と単純で、Visual Studio でビルドを行うと .NET Framework 向けの MSBuild が使われるので、netcoreapp2.0 向けのアセンブリが読み込めないという落ちでした。

なので、この場合は .NET Core 向けの MSBuild すなわち dotnet cli を使うとビルド出来ます。

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

残念ながら netcoreapp2.1 向けにすると dotnet cli を使ってもエラーになってしまうので注意。

以下のコマンドで発行用にビルドが行われるので、適当に呼び出してビルドさせました。

dotnet publish -c Release

生成されたファイルは、とりあえず zip にしてから Kudu を使って直接 wwwroot 以下に配置しました。普通なら CI を使うと思うので、その場合は zipdeploy を使ってあげれば良いと思います。

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

折角なので Durable Functions を追加した状態で netcoreapp2.0 としてビルドしました。元々は .NET Standard 2.0 向けに作られているので、問題なく netcoreapp2.0 でもインストールできます。

Azure Functions のランタイムバージョンは予め 2.0 に変更しておかないと詰むので注意。

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

ランタイム側でもちゃんと Functions として認識されています。読み込みに失敗する場合は大体ランタイムバージョンの設定を忘れているはずです。

適当に Durable Functions のエントリポイントを叩いて、実行結果を取得してみました。

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

テンプレ通りの結果ですが、ちゃんと netcoreapp2.0 向けにビルドした Azure Functions が実行出来ることを確認出来ました。問題点としては Visual Studio を使って開発できないことですね。

現状としては Visual Studio Code や Azure Functions CLI などを使うしかなさそうです。

今回の問題は .NET Framework 向け MSBuild 自身が netcoreapp2.0 向けのアセンブリを読み込もうとしているのが原因なので、メタデータの生成部分をアウトプロセスに出来れば解決しそうです。

ちなみに macOS / Linux 上で開発している場合には、最初から特に問題なく netcoreapp2.0 としてビルド出来たかも知れませんね。

追記 - Visual Studio でもビルド可能

アウトプロセスに出来れば解決しそうだと思って Azure Functions SDK のソースを適当に読んでいたら、既に設定を行えばアウトプロセスで動くようになっていました。

なので、Visual Studio でも csproj に UseNETCoreGenerator を追加すればビルド出来ます。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <AzureFunctionsVersion>v2</AzureFunctionsVersion>
    <!-- これを追加 -->
    <UseNETCoreGenerator>true</UseNETCoreGenerator>
  </PropertyGroup>
</Project>

Issue も上がってましたが、完全に見落としていました。まだ解決していない扱いみたいですが。

これで Visual Studio 上でも netcoreapp2.0 が使えて幸せになれそうですが、今のところはデバッグ実行しようとするとエラーになってしまうので、中々厳しいですね。

残念ながら Generator は netcoreapp2.0 としてビルドされているので、例によって netcoreapp2.1 としてビルドしたアセンブリは読み込むことが出来ないです。

Managed Service Identity を利用して Kudu に用意された REST API を実行してみる

あまり弄ってなかったのですが、Managed Service Identity を使うと ARM の API とかいろいろと使いやすくなって素晴らしいという話をぶちぞう RD のブログで読んでました。

そういえば、ちょっと前に GA になったみたいなので安心して使えるようになりました。

Managed Service Identity (Preview) | ブチザッキ

ARM を使うと App Service のいろんな設定を変更したり、新しくリソースを作ったりも出来ますが Kudu に用意されている REST API は直接扱うことは出来ないみたいでした。

Kudu の REST API は結構な数が用意されていて、ARM の API と組み合わせることで何でも出来るようになるので夢が膨らみます。

ARM では実行できないファイルシステム系の API も用意されていて使ってみたかったので、Managed Service Identity で取得した認証情報を使って叩けるようにしました。

とりあえず Azure Functions と Web App を作成して、Azure Functions 側の Managed Service Identity を有効化するところから始めます。

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

有効化すると Access control から権限を割り当てることが出来るようになります。この辺りは UI が MSI 向けに最適化されていて非常に楽でした。

今回は Web App 向けの API を叩きたいので、Website Contributor を割り当てます。

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

これで Azure Functions から MSI を使って ARM の API を叩けるようになりました。

Kudu の API を叩くためには 2 種類の認証情報が用意されていますが、Site Credentials に関しては ARM を使って取得できるので、それを利用して Kudu の API を利用します。

Deployment credentials · projectkudu/kudu Wiki · GitHub

今回はサンプルとして VFS API を利用して、Azure Functions からファイルを作成してみます。

実際に用意した Azure Functions のコードは以下のようになります。やっていることは極めて単純です。

public static class Function1
{
    [FunctionName("Function1")]
    public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
    {
        var subscriptionId = "***";
        var resourceGroupName = "***";

        // ARM 向けに AccessToken を取得する
        var tokenProvider = new AzureServiceTokenProvider();

        var accessToken = await tokenProvider.GetAccessTokenAsync("https://management.azure.com/");

        var websiteClient = new WebSiteManagementClient(new TokenCredentials(accessToken))
        {
            SubscriptionId = subscriptionId
        };

        // Site credentials を取得する
        var credentials = await websiteClient.WebApps.ListPublishingCredentialsAsync(resourceGroupName, "kudu-api-test");

        // Site credentials を使って Kudu REST API を利用する
        var httpClient = new HttpClient();

        httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{credentials.PublishingUserName}:{credentials.PublishingPassword}")));

        // VFS API を使ってファイルを生成してみる
        await httpClient.PutAsync("https://kudu-api-test.scm.azurewebsites.net/api/vfs/site/wwwroot/samplefile.txt", new StringContent(DateTime.Now.ToString()));

        return new OkObjectResult($"OK");
    }
}

AzureServiceTokenProvider を使って AccessToken が取れてしまえば、後は対象の Web App の Site credentials を取得して Kudu REST API を叩くだけです。API に関しては Wiki にまとまっています。

作成した Function を実行すると、Web App に新しくファイルが作成されているはずです。

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

VFS API 以外にも Zip Deploy やコマンド実行など、割と何でも出来るようになるので使い勝手がありそうです。ただし、くれぐれも認証情報の扱いには注意したいところです。

Managed Service Identity のおかげで、ようやく ARM API を使ってみようという気力が湧いてきました。

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 でも使えるようになっているはずです。