しばやん雑記

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

Azure Functions への Zip Deploy を Publish Profile を使って行う方法

普段は Visual Studio からデプロイ先の Azure Functions を選んで Zip Deploy 用のプロファイルを作成していますが、たまに Visual Studio で Publish Profile を使った Zip Deploy を行う場合に、その手順を毎回忘れてしまうのでメモとして残します。

おまけとして Publish Profile についても少し触れています。Zip Deploy については何回も書いているはずなので、以前書いたエントリを参照してください。公式ドキュメントもあります。

最近は Visual Studio に色々統合されているので Publish Profile を使う機会が減ってしまったので、基本的なところも軽く紹介しておきます。

ダウンロードボタンは App Service / Azure Functions の Overview ツールバーにあります。

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

Publish Profile に含まれている認証情報は再生成も出来ますが、場所が昔から大きく変わっています。今は Deployment Center (Preview) の Manage publish profile から行います。

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

ここまで理解しておけば大体 OK です。特に Publish Profile の再生成は分かりにくいので注意。

Publish Profile の仕組み

ダウンロードした Publish Profile は Minify されていて読みにくいのですが、フォーマットしなおすと以下のような構造になっています。

昔は Web Deploy と FTP しかなかったのですが、最近は Zip Deploy と ReadOnly FTP が増えていました。

<publishData>
  <publishProfile profileName="serverlesstest-1 - Web Deploy" publishMethod="MSDeploy" publishUrl="***.scm.azurewebsites.net:443" msdeploySite="***" userName="$***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - FTP" publishMethod="FTP" publishUrl="ftp://***.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="***\***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - Zip Deploy" publishMethod="ZipDeploy" publishUrl="***.scm.azurewebsites.net:443" userName="***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
  <publishProfile profileName="serverlesstest-1 - ReadOnly - FTP" publishMethod="FTP" publishUrl="ftp://***.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="***\***" userPWD="***" destinationAppUrl="http://***.azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
    <databases />
  </publishProfile>
</publishData>

Publish Profile の中身としては userNameuserPWD が重要な情報です。それ以外はどうでも良いです。

この userNameuserPWD は Site Credential というものになり、FTP の接続情報として確認できるものと同じです。大昔に書いた気がしますが、App Service は User Credential と Site Credential が存在しています。

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

Site Credential を持っていると Kudu の API を自由に叩けるようになるので、当然ながら扱いには注意が必要ですが、App Service のデプロイ API は Kudu 側に用意されているので便利です。

コマンドを使ってデプロイ

あまり使わないと思いますが、API を知るには都合が良いので curl を使って Zip Deploy を行ってみます。

デプロイ API は /api/zipdeploy という分かりやすいエンドポイントにあるので、そこに Site Credential で Zip ファイルをアップロードするだけです。

curl -X POST -u "$serverlesstest-1" --data-binary @"FunctionApp16.zip" https://serverlesstest-1.scm.azurewebsites.net/api/zipdeploy

他にも WAR 向けエンドポイントもあるのですが、将来的にはこの辺りが統合されそうです。今後は POST 時に特殊なヘッダーを付けることで、Deployment Center 向けのメタデータを送信できるようになりそうです。

Visual Studio を使ってデプロイ

Azure Functions のみで利用できる方法になりますが、Visual Studio を使ったデプロイ時に Azure サブスクリプションを紐づけておけば Zip Deploy を行えるようになっています。

しかし Publish Profile をインポートする場合には Web Deploy しか認識してくれません。

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

Publish Profile 自体には Zip Deploy の定義は含まれていますが、今の Visual Studio では正しく認識してくれないため、手動で pubxml を弄って Zip Deploy 向けに書き換えます。

Visual Studio で Publish Profile をインポートすると Properties の中に pubxml が作成されます。単なる MSBuild 定義ですが、中にはデプロイに必要な情報が記載されています。

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

この pubxml を以下のように修正することで、Web Deploy から Zip Deploy へ変更できます。

  • WebPublishMethodZipDeploy
  • PublishUrl に SCM の URL*1 を指定して追加
  • DeployIisAppPathMSDeploy が含まれている項目を一応削除

修正後にデプロイを行うと、以下のように Zip Deploy が行われていることが確認できます。

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

多少手間はかかりますが、Visual Studio に複数の Azure サブスクリプションを追加したくないケースもあるので、Publish Profile でサクッとデプロイ出来るようになっておくと便利です。

GitHub Actions を使ってデプロイ

おまけ的に GitHub Actions を使う時の情報も載せておきます。とは言っても、以前に書いてあるエントリを紹介するぐらいに留めておきます。

App Service へのデプロイに使う azure/webapps-deploy@v2 は最初から Publish Profile に対応しているので、複数アプリケーションへのデプロイが必要ない場合には簡単に始められます。

Deployment Center (Preview) が良くなっている話

あまり内容とは関係ないですが、Deployment Center (Preview) が良い感じにアップデートされて、デプロイ履歴もしっかり確認出来るようになっています。

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

GitHub Actions との連携が推されていましたが、個人的には履歴の確認やメタデータから該当のコミットに簡単に飛べる機能などのがありがたいので、今後もアップデートに期待しています。

ちなみにこのデプロイ履歴は Kudu の API で取れるので、Site Credential があれば自由にアクセス出来ます。

ASP.NET Core / Azure Functions アプリケーションの発行後サイズを削減する

Azure Storage を使った Run From Package で公開している Azure Functions 向けパッケージの転送量が、最近になって急激に増えたのでパッケージのサイズを削減して、Azure Functions で実行するための最適化を行ったのでメモとして残します。

何故マネージコード中心のアプリケーションでパッケージサイズが増えたのか調べると、使っている NuGet パッケージがネイティブ向けライブラリを含んでいるのが大きな原因でした。

.NET Core では単純に dotnet publish を実行すると、ポータブルな形でアプリケーションが発行されます。ここでいうポータブルというのは .NET Core がインストールされている場合、ファイルだけ持っていけば OS や CPU 関係なく動くという状態です。Framework Dependent モードでの発行と呼ばれます。

Framework Dependent で発行されたアプリケーションはどのプラットフォームで実行されるのか分からないため、出力には全てのプラットフォームで必要なライブラリが含まれています。

本筋とは関係ないですが、各プラットフォーム向けのライブラリを含んだ NuGet パッケージの作成方法はぼんぷろ師匠が昔に書いていたので、仕組みが気になる方はこっちを参照してください。

プラットフォーム依存のライブラリは bin\runtimes の中に RID 単位で入っています。例として Acmebot を発行して試すと以下のような構造です。このアプリケーションは Windows の Azure Functions 向けなので Linux や OS X 向けのライブラリは完全に不要です。

ましてや ARM 向けのライブラリは完全に発行後は無駄な容量になっています。

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

.NET Core の開発が進むにつれ対応するプラットフォームが増えた結果、インストールする NuGet パッケージによっては無視できないサイズになりつつあります。

RID を指定して発行されたアプリケーションは、ビルド時にプラットフォームが限定出来るので必要最小限のライブラリのみが含まれますが、同時に Self Contained をオフにしないとランタイム自体が含まれてしまうので、明示的にオフにします。

# Windows x86 向け
dotnet publish -c Release -r win-x86 --self-contained false

# Windows x64 向け
dotnet publish -c Release -r win-x86 --self-contained false

今回の例では zip 圧縮後で 4.6MB から 2.9MB に削減出来たので、発行後アプリケーションの 40% 近くが不要なライブラリで占められていたことになります。毎回不要なものを一緒にデプロイするのはストレージやトラフィックの無駄でしかないです。

微妙に関係ない話題ですが dotnet publish 時に zip を同時に作成することも出来るので、複数アプリケーションをデプロイする場合には便利です。詳細は以下のエントリを参照してください。

GitHub Actions や Azure Pipelines を使ってビルドする場合には、上のコマンドを組み込めば発行後サイズを削減できます。ちなみに Ubuntu 上でビルドしたとしても win-x86win-x64 向けに発行出来ます。*1

RID の一覧は何回か紹介した気がしますが、以下に全て記載されています。ASP.NET Core や Azure Functions 向けで使う場合は win-x86 / win-x64 / linux-x64 といったところでしょう。

Azure Functions の Consumption Plan の場合はスペック的に 64bit で動かすメリットが無いので、基本的には win-x86 を使うのが正解です。なので固定してもデメリットがありません。

Acmebot には 12/10 ぐらいにこの対応を入れてリリースしたところ、転送量が大幅に削減できました。リクエスト数はほぼ変わらなかったので、純粋にパッケージサイズの削減が効いていることになります。

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

今回は利用していたパッケージの数が少なかったので 1MB ちょいの削減になりましたが、もう少し規模の大きなアプリケーションでライブラリをたくさん使っていた場合にはかなりの差がつく可能性があります。

GitHub Actions や Azure Pipelines といった CI SaaS では Artifacts をアップロードする処理が大体挟まるので、パッケージサイズを小さく保てればビルド時間の短縮にもつながります。もちろん App Service にデプロイする時にも差は出ますし、実際に出ました。

Visual Studio を使ったデプロイ向け設定

ここまでは手動や CI を使う前提でコマンドベースで書いてきましたが、Visual Studio を使ったデプロイ時にもターゲットのプラットフォームを指定してビルド可能です。

Visual Studio で作成された App Service や Azure Functions のプロファイル設定を開くと、ビルドに関連する設定を選べるようになっています。重要なのは配置モードとターゲットランタイムです。

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

デフォルトではそれぞれフレームワーク依存と移植可能即ちポータブルになっていますが、ターゲットランタイムを実際にデプロイする先のプラットフォームに合わせた RID を選択するだけです。

ARM64 向けの RID は選べませんが、App Service と Azure Functions では使わないので問題ないです。

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

この設定を保存してデプロイを実行すると、指定した RID 向けのライブラリのみが含まれたパッケージが生成されて、App Service や Azure Functions にデプロイされます。

今は Visual Studio を使ったデプロイは開発の極々初期か趣味アプリぐらいでしか行わないと思いますが、検証向けとしては手軽なので一応残しておきます。

*1:ただし ReadyToRun はクロスコンパイル未対応なので無理

Azure App Service と Regional VNET Integration を使ったセキュアなアプリケーションの実装パターン

2020 年は Azure App Service にとってはネットワーク周りの新機能やアップデートが非常に多い年になりました。そろそろ今年も終わりなので App Service の定石アーキテクチャをアップデートする良い機会です。

まずは今年のネットワーク関連アップデートについておさらいしておきます。Regional VNET Integration はプレビューの時から使ってきたので、最近 GA したという気が個人的にはしていません。

  • GA
    • Regional VNET Integration (NSG / Route Table)
    • Private Link (Private Endpoint)
    • NAT Gateway
    • Hybrid Connection for Linux App Service
    • Premium V3 / VMSS Worker
  • Preview
    • Azure Functions restricting storage

今年のアップデート総まとめという気持ちでまとめています。個人的にはもうマルチテナントの App Service で十分すぎるので、ASE はもう使うことはないだろうという思っています。

最後にある見出しが一番言いたいことという感じですね。1 年使ってはまった点も入れています。

恐らく App Service に関しては今年最後になりそうな気がします。今後もアーキテクチャに関してはアップデートしつつ共有していきたいと思います。

Regional VNET Integration を使う利点

既にいろいろな人が話しているはずですし、ドキュメントにもまとまっているので何回も書く気はないのですが、軽く触れておきます。一般的には以下のような点が利点として挙げられています。

個人的には Regional VNET Integration を使うことで、これまでフワッとしがちだった PaaS / Serverless のアーキテクチャを、VNET という枠にはめて管理できるという点が気に入っています。別にフワッとしていることに問題はありませんが、アクセス制限のしやすさは段違いです。

  • VNET 内にデプロイされたリソースへのアクセス
  • Service Endpoint を使ったリソースへのアクセス制限
  • Private Endpoint を使ったプライベートかつセキュアなアクセスの実現(閉域化)
  • Express Route や VNET Peering 経由でリソースへのアクセス
  • NAT Gateway / Azure Firewall / NVA を使った Outbound 通信の制御
  • VNET での分離が社内規則・コンプライアンスが必要な場合への対応

もちろん Service Endpoint や Private Endpoint を使ったアクセスは常に使っているので、とっくの昔に IP アドレスでのアクセス制限という世界からは脱却しているのですが、Managed Identity と組み合わせることでよりセキュアな環境を簡単に実現できるというのが大きなメリットです。

地味に大きいのは VNET が必須となっているケースでも対応できるというのがあります。ASE で良いという話もありますが、あれは IaaS に近いので個人的には使う気になりません。

制約・注意点

基本的な制約については公式ドキュメントが良い感じにまとまっているので、実際に触ってみて挙動に疑問があれば読み直すのがお勧めです。自分も何回も読み直しています。

マルチテナントで PaaS な App Service 上で実現しているので、正直なところそれなりに制約は存在しますが、昔に比べるとかなり改善されました。

1 年以上色々と使ってきましたが、特に問題は出てきていません。安定して利用出来ています。

最初に Premium V2 / V3 / Elastic Premium の Service Plan を作る

既に何回も書いている気がしますが、Regional VNET Integration が利用可能なのは Premium V2 / V3 / Elastic Premium と新しい Scale unit 上で動く Standard です。

自分はたまに Scale unit ガチャと呼んでいますが、最初に Standard 以下を選んで App Service Plan を作成してしまうと、古い Scale unit 上にデプロイされることがあります。その場合は Gateway が必要な VNET Integration しか利用できません。

作成時に Premium V2 / V3 を選ぶと必ず新しい Scale unit にデプロイされるので、その後は Standard に下げても Regional VNET Integration が使えるままになります。

Terraform や ARM Template で構築する際には後から App Service Plan の Tier を変更するのは手間だとは思いますが、後で必要になった場合には非常に苦労するので先にやっておきましょう。

複数の App Service Plan を同じ Subnet に追加は出来ない

Azure Portal から Regional VNET Integration の設定を行うと、そもそも既に App Service Plan を追加済みの Subnet はグレーアウトで表示されて選べないので安心ですが、ARM REST API を使った場合には何故か追加が出来てしまったので事故りました。

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

具体的には Terraform で管理していたリソースで、新しく App Service Plan を作成した時に Subnet を作成するのを忘れて、そのまま既存の App Service Plan が追加されている Subnet に追加したのがきっかけです。

特にエラーが出ることもなく、正常に追加出来てしまったので気が付かなかったのですが、1 か月後ぐらいに App Service OS のアップデートが行われたタイミングで、急に VNET 接続が失われてしまいました。

Subnet のアドレス空間に注意

ドキュメントにも最小でも /28 のアドレス空間を持った Subnet が必要と書いてありますが、App Service はアップデートなどで定期的にインスタンスが入れ替わるので、一時的にインスタンス数が増えます。

そして使えないアドレスも存在するので余裕を持った設計をしましょう。

When you provision a subnet, the Azure subnet loses 5 IPs for from the start. One address is used from the integration subnet for each plan instance. If you scale your app to four instances, then four addresses are used.

Integrate app with Azure Virtual Network - Azure App Service | Microsoft Docs

特に Elastic Premium を使う場合には予想以上にインスタンス数が増える可能性もあるので、アドレス空間の確認と最大スケーリング数の設定で制限を超えないようにしましょう。

Azure Functions と NSG の組み合わせには注意

Regional VNET Integration と WEBSITE_VNET_ROUTE_ALL を組み合わせると Outbound では NSG が使えるようになるので、インターネットへの接続を全て遮断するようなルールを追加することも出来ます。

全遮断はしないと思いますが、Azure Functions はデプロイ中に自分自身に用意された管理用 API をインターネット経由で実行しようとするので、NSG で App Service への接続を遮断するとデプロイが常に失敗するようになります。自分に Private Endpoint を追加すると通るようになりますが、NSG には注意しましょう。

これは Route Table と NVA を組み合わせた時にも発生する可能性があります。

ネットワーク設計だけに頼らない

App Service と Service Endpoint / Private Endpoint をフルに利用したアーキテクチャの場合は、そもそも VM のようにセキュリティ上脆弱になりやすい部分が少なくなるので多少はましですが「VNET や NSG / Private Endpoint で制限しているから安全です」みたいな考えは捨てましょう。

ネットワーク設計で不必要なトラフィックを遮断しつつ、リソースへのアクセスに関しては Key Vault を使った認証情報の管理やローテーション、Managed Identity を使ったロールベースでのアクセス制限を組み合わせていきましょう。どちらかではなく両方を少ない労力で実現できる時代です。

実際のアーキテクチャ例

ここからは実際に自分が作ったことのあるアーキテクチャについてまとめています。

Express Route を使ったオンプレとの連携例がないのは、そう簡単に Express Route を用意できなかったということになりますが、Regional VNET Integration では当然対応しているのでいつか試したいところです。

VNET + Service Endpoint でアクセス元制限

これが超ド定番かつ基本的な使い方ですね。Azure Storage や SQL Database などのアプリケーション開発に必須と言えるサービスへのアクセス制限を、VNET の Subnet 単位で簡単に行えます。

対応しているサービスが最近は増えていないですが、定番どころは抑えてあります。

Subnet 単位での許可は大きく感じますが、1 つの App Service Plan は 1 つの Subnet を占有するのでつまり App Service Plan 単位での制限となります。設計次第では Application Security Group のように扱えます。

アーキテクチャを図にしてみると面白いことに VNET には Subnet 以外存在しません。

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

Regional VNET Integration は VNET 内にリソースをデプロイするのではなく、あくまでも統合なので VNET と Subnet は単なる通り道として使われます。土管的な感じです。

VNET + Private Endpoint で閉域化

Cognitive Search や SignalR Service は Private Endpoint は対応していますが、一方で Service Endpoint は対応していないのでアクセス元を制限するには Private Endpoint を使う以外に方法はありません。

最近は Service Endpoint よりも Private Endpoint のがサポートするリソースが多いです。

Service Endpoint とは異なり、Private Endpoint は Subnet に Network Interface がデプロイされて、それぞれが Private IP を持ちます。大抵は Private Endpoint と同時に Private DNS Zone もデプロイされるので、ホスト名はそのまま Private IP として解決されます。

App Service が不必要なリソースへアクセスしないように NSG を使って Outbound を制限することも出来ますが、前述したように制限しすぎには注意です。

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

この例では 1 つの VNET に対して Regional VNET Integration と Private Endpoint をデプロイしていますが、それぞれを別の VNET にデプロイしてピアリングで繋ぐことも出来ます。ただし Private DNS Zone をピアリング先にも関連付けておかないと Private IP での名前解決は出来ません。

Azure Monitor Private Link Scope を使うと Application Insights や Log Analytics も Private Endpoint 経由でのアクセスに出来るので、テレメトリを VNET 外に出すことなく収集できます。

大量の Private DNS Zone が作成されますが、こればかりは仕方ないのかもしれません。名前付けに一貫性がないのが大体の原因です。

VNET + NAT Gateway で Outbound IP の固定化

たまにある外部連携先から接続する IP アドレスを要求されるケースですが、App Service では Scale unit 単位で共通の Outbound IP が使われているので、同じ Scale unit 上の App Service からはアクセス可能です。*1

そもそも Outbound IP の数が多いので登録が大変という話もありましたが、NAT Gateway を組み合わせることで専用の Outbound IP を使うことが出来るようになりました。

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

上の例では Subnet に NAT Gateway を設定していますが、Route Table と Azure Firewall や NVA を組み合わせることも出来ます。考え方は大体同じです。

NAT Gateway の設定方法については以前のエントリに書いておいたので、こっちを参照してください。

NAT Gateway は追加で課金されますが、これまでは ASE が必要だったと考えると確実に有利です。

Azure Functions + VNET + Private Endpoint で閉域化したサーバーレス環境

閉域化した状態でサーバーレスのスケーラビリティの恩恵を受けることは少し前では考えられないことでしたが、Azure Functions の Premium Plan と Regional VNET Integration の組み合わせで可能になりました。

Azure Functions の特徴である高速なイベントベースのスケーリングも、追加の設定は必要ですが利用することが出来ます。詳しくは以下のドキュメントを参照してください。

それによって以下のように Azure Functions の Trigger 自体を Private Endpoint 経由に出来ます。もちろんイベントベースでのスケーリングが自動的に行われます。

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

今のところは Azure Functions が裏で使うストレージアカウントはパブリックのままです。

現在は早期プレビュー中ですが Azure Functions のストレージを含んだ全てを VNET 経由でアクセスする機能も実装されています。これによって Private Endpoint で制限をかけることが出来ます。

これで社内規則やコンプライアンス対応で VNET 経由でのアクセスが必須の場合にも対応できます。将来的には全てのパブリックリージョンで Azure Functions の全てを VNET 経由に出来るようになるはずです。個人的には Gov Cloud 向けに実装されている気がしています。

まとめと関連エントリ

長々と書いてきましたが、PaaS / Serverless が持つマネージドかつスケーラブルという特徴を保ったまま、更に IaaS が持つネットワーク周りの機能が簡単に使えるようになったというのは革命的だと思います。

しかし App Service がリリースされてすぐの牧歌的なアーキテクチャとは異なり、必要なリソースと設定が増えてくるので Terraform や ARM Template などを使ったコード化は必須になりつつあります。

最後に貼る場所がなかった関連エントリを載せておきます。来年の App Service にも期待しています。

*1:とはいえ何らかの認証はしているはず

App Service Authentication が OpenID Connect Provider をサポートするようになった

タイトルの通り App Service の Authentication (Easy Auth) で OpenID Connect に対応したプロバイダーを追加出来るようになりました。プレビュー公開されたのは数か月前だったのですが、諸々の事情によって動かないパターンのが多かったためお蔵入り状態になっていました。

設定がまだ若干特殊なのでドキュメントを読みこんで作業する必要がありますが、将来的には Azure Portal や ARM Template からの設定も出来るようになりそうです。*1

ドキュメントにもサンプルで書かれているように Sign in with Apple 向け機能という感じです。むしろ当初は Apple でしか動作確認をしていなかったのでは疑惑まであります。

現在分かっている制限は以下のような感じです。普通の OpenID Connect Provider なら使えるはずです。

  • 認可コードフローのみの対応
  • response_modequeryform_post に対応
  • id_token の署名は RS256ES256 に対応
    • 恐らく RS384 / RS512 / ES384 / ES512 にも対応している
  • PKCE には非対応
  • User Info API の面倒までは見ない

App Service Authentication の実装的には PKCE はサポートしていなくても良いかなという感はあります。id_token の署名については HMAC を使った HS には非対応です。

実際に OpenID Connect Provider を App Service Authentication に追加して動作を確認してみました。手順が若干面倒なので、先に新しい設定を使う方法から書きます。

OpenID Connect Provider を使う時の共通手順

OpenID Connect Provider の設定は Resource Manager レベルでの対応が正式にはまだ行われていないので、以下のドキュメントにあるようにファイルベースの設定を有効にする必要があります。

アプリケーションと一緒に管理出来ますが、本来なら ARM Template や Terraform で管理するべき項目だと思います。Static Web App の場合にはどうなるのか気になるところです。

既に Auth0 での手順を Azure Functions チームの Anthony Chu 氏が書いているので、こちらも見つつ進めた方が良いですね。特に設定の有効化は ARM Explorer か Azure CLI が必要になるので若干分かりにくいです。

App Service Authentication の設定が定義されたファイルは auth.json という名前で wwwroot 以下に保存しておきます。ファイル名やパスは変更出ますが、ドキュメント通りにしておいた方が安全です。

wwwroot の下にあるのでアプリケーションと一緒にデプロイが出来るという仕組みです。

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

ファイルを置いたら設定を有効にするコマンドを実行します。とりあえず自分は ARM Explorer で行いましたが、以下のように既存のプロパティをほぼ全て消して上書きするので若干抵抗があります。

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

一目でわかると思いますが isAuthFromFileauthFilePath が今回追加になっているプロパティです。

この設定後は Azure Portal から App Service Authentication の設定が出来なくなるので、もし戻す場合は同じように ARM Explorer か Azure CLI を使う必要があります。

Azure AD B2C を使う

説明と準備が長かったですが、実際に OpenID Connect Provider を追加して試していきます。まずは使う機会が地味に多い Azure AD B2C からです。

中々このエントリを公開できなかった理由が Azure AD B2C だと動かないことにありました。フィードバックをし続けた結果、最近のアップデートで使えるようになりました。

それ以外は設定としては他の OpenID Connect Provider の時と代わり映えしません。

wellKnownOpenIdConfiguration に User Flow 毎に用意されているメタデータの URL を設定して、ClientId と Client Secret を追加すれば大体終わりです。

{
  "platform": {
    "enabled": true
  },
  "globalValidation": {
    "requireAuthentication": true,
    "unauthenticatedClientAction": "RedirectToLoginPage",
    "redirectToProvider": "aadb2c"
  },
  "identityProviders": {
    "openIdConnectProviders": {
      "aadb2c": {
        "enabled": true,
        "registration": {
          "clientId": "00000000-0000-0000-0000-000000000000",
          "clientCredential": {
            "secretSettingName": "AADB2C_CLIENT_SECRET"
          },
          "openIdConnectConfiguration": {
            "wellKnownOpenIdConfiguration": "https://***.b2clogin.com/***.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_signup_signin"
          }
        },
        "login": {
          "nameClaimType": "name",
          "scope": [ "openid" ],
          "loginParameterNames": []
        }
      }
    }
  },
  "login": {
    "tokenStore": {
      "enabled": true
    }
  }
}

ClientId は JSON にそのまま書きますが、Client Secret は App Settings 経由で読み込むので JSON にはキー名を書いて、実際の値は App Settings に追加する形になります。

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

これで設定はすべて完了なので、ファイルをアップロードした後に App Service を再起動して反映すれば、ページへアクセスしたタイミングで Azure AD B2C のログイン画面へリダイレクトされるはずです。

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

App Service の再起動ではなくワーカープロセスが再起動出来ればよいので、アプリケーションのデプロイと同時に行うと常に再読み込みが行われるので安全ではあります。

ログイン後に /.auth/me へアクセスすると id_token が取得できるので、適当に jwt.ms などに張り付けると含まれているクレームを確認できます。

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

ちゃんと Azure AD B2C が発行した id_token であることが確認できました。

Yahoo! ID連携 v2 を使う

Azure AD B2C は User Flow 単位の関係上、特殊な形式になっていたので他の OpenID Connect Provider でも試します。Auth0 は既に試されていたので、調べていて目についた Yahoo! ID連携 v2 を試してみました。

ドキュメントを読む限りは極めて普通な OpenID Connect のようなので動くはずです。

Azure AD B2C は id_token に含めるクレームを設定可能でしたが、Yahoo! ID連携 v2 では必要最低限のクレームのみ含まれていて、ユーザー情報は User Info API を叩く必要があるようです。

App Service Authentication は User Info API までは面倒を見てくれないので、この辺りの情報が必要な場合は自前で実装する必要があります。

{
  "platform": {
    "enabled": true
  },
  "globalValidation": {
    "requireAuthentication": true,
    "unauthenticatedClientAction": "RedirectToLoginPage",
    "redirectToProvider": "yahoo"
  },
  "identityProviders": {
    "openIdConnectProviders": {
      "yahoo": {
        "enabled": true,
        "registration": {
          "clientId": "*******************************",
          "clientCredential": {
            "secretSettingName": "YAHOO_CLIENT_SECRET"
          },
          "openIdConnectConfiguration": {
            "wellKnownOpenIdConfiguration": "https://auth.login.yahoo.co.jp/yconnect/v2/.well-known/openid-configuration"
          }
        },
        "login": {
          "nameClaimType": "name",
          "scope": [ "openid", "profile" ],
          "loginParameterNames": []
        }
      }
    }
  },
  "login": {
    "tokenStore": {
      "enabled": true
    }
  }
}

id_token には名前は返ってきませんが、一応 nameClaimType は適当に残しておきます。

例によって Client Secret は App Settings に追加した後にアクセスしてみると、Yahoo! のログイン画面にリダイレクトされました。ログインすると App Service に戻ってきてページが正しく表示されます。

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

リクエストヘッダー経由で id_tokenaccess_token が渡されるので、必要であれば User Info API を実行してユーザー情報を取得すれば良いと思います。

Sign in with Apple は要注意

Sign in with Apple に関しては最新バージョンで専用の設定が用意されましたが、中身は他の OpenID Connect Provider と同じなので設定方法で悩むことはないでしょう。

ドキュメントも OpenID Connect 関連で Apple だけはちゃんと追加されています。

結論から言えばドキュメントの通りに設定すれば動きますが、Sign in with Apple は他のプロバイダーと異なり Client Secret を 180 日毎に更新する必要があるので、恐らく確実に 180 日後に事故ります。

この辺りは Azure AD B2C にも言えることですが、Client Secret の生成まで実装されるのを待つか、CI などで定期的に作って更新するかなどの方法が必要になります。

LINE Login は使えない

App Service Authentication でどうしても使えなかったのが LINE Login でした。理由としては LINE Login の id_tokenHS256 で署名されているという 1 点だけです。

ID の偉い人が言っているように LINE Login はメタデータでは ES256 と宣言しつつも、実際の id_token では HS256 で署名されるというよく分からない仕様です。

実装して理解するLINE LoginとOpenID Connect入門

一応 App Service チームの人と話はしましたが「なんで宣言通り ES256 じゃないの?」みたいな返事だったので LINE Login はちょっと期待薄かもしれません。

*1:authsettingsV2 というリソースがひっそりと増えている模様

Cosmos DB のインデックスポリシーの基本と最適化の方法

Cosmos DB は RDB のようにインデックスを個別で付ける必要がなく、デフォルトのままでも十分なパフォーマンスが出るようになっていますが、最適化を行うとスループットの向上やコスト削減に繋がります。

特に最近は大量データの処理基盤として Cosmos DB を使うことが多くなってきたのですが、こういった大量データを処理するケースではインデックスポリシーの最適化はかなり効果が出ます。

例によって Cosmos DB の公式ドキュメントは充実しているので、基本から読んでおくと安心です。

ちなみにインデックスポリシーの最適化のを行う目的は、書き込みのコストとレイテンシを下げるためです。

読み込みに関しては不足しているインデックスを追加した場合には効果が出ますが、必要となる場面は限定的でしょう。最近だと複雑なクエリが必要なら Change Feed か Synapse Analytics を使うことが多いです。

良く使われるであろうインデックスポリシーの例は公式ドキュメントでも紹介されているので、最低ここを読んでおくだけでもかなり役に立つはずです。

とは言え突然サンプルが出てきても混乱の元なのと、自分のインデックスポリシーへの理解の整理も兼ねてメモしておきます。きっかけは Change Feed だったので若干偏ってはいます。

大前提:デフォルトは全プロパティにインデックスを作成する

Cosmos DB の特徴の一つとしてはデフォルトで全てのプロパティに対してインデックスが自動で作成されるので、id 以外でのプロパティでフィルタを実行しても高速という点があります。

The default indexing policy for newly created containers indexes every property of every item and enforces range indexes for any string or number.

Azure Cosmos DB indexing policies | Microsoft Docs

インデックスの作成はデフォルトでは即座に行われるので、少ないデータ量の場合はインデックスを気にせず使っても問題ありません。デフォルトの設定のままでも大抵の場合は十分です。

インデックスモードには consistentnone の設定が存在しています。昔は lazy がありましたが、今は設定できないようになっているので忘れていて良いです。

indexingMode = consistent

Cosmos DB で Container を作成した時のデフォルトが consistent になります。名前の通り Container デフォルトの Consistency Level と同じ挙動になります。

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/\"_etag\"/?"
        }
    ]
}

この時 _etag は常に自動的に除外されるようになっていて、消しても追加されます。デフォルトで includedPaths/* が追加されているので、全てのプロパティに対してインデックスが作成されます。

indexingMode = none

あまり使う機会は無いと思いますが、インデックスの作成を完全にオフにすることも出来ます。

大量データのインポート時にはインデックスを同時に作成する必要はないので、公式ドキュメントにはインポート前にオフにして完了後に戻す方法が紹介されています。

{
    "indexingMode": "none",
    "automatic": false,
    "includedPaths": [],
    "excludedPaths": []
}

後は id のみを使うアプリケーションの場合はオフに出来ますが、Cosmos DB を使うメリットがかなり減ってしまうので、データのインポート時や後述する Change Feed から使う場合ぐらいかなと思います。

ここまでを頭に入れておきつつ、アプリケーションからどのように使っているかを考えると、インデックスポリシーの最適化が行えるようになるはずです。

必要なプロパティへのインデックスだけ作成する

インデックスポリシー最適化の基本は必要なプロパティに対してのみインデックスを付けるという、極々当たり前の方法となります。クエリで使われないプロパティに対してインデックスを作成するのは、RU とストレージの無駄になるので避けた方が良いです。

公式ドキュメントのパフォーマンス Tips にも書かれているように、インパクトはかなり大きいです。

Cosmos DB はスキーマレスで、データを JSON として扱うストレージなので RDB とは異なり、オプトインとオプトアウトを組み合わせてインデックスを作り上げていく形になります。

オプトインする場合の例

特定のプロパティしかクエリで使われないことがわかっている場合は、オプトインでインデックスを定義するのが簡単です。プロパティを 1 つだけ含める、特定のプロパティ以下を含めるといった書き方が出来ます。

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/user/?"
        },
        {
            "path": "/items/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/*"
        }
    ]
}

書き方は若干特殊な感じはしますが /? で終わる場合はそのプロパティだけ、/* で終わる場合はそれ以下をすべて含むという意味になります。

オプトアウトする場合の例

逆に除外したいプロパティが明確に定まっている場合には、オプトアウトでインデックスを定義します。

例えばセンサーなどから取得された計測値などインデックスが完全に不要なデータは、プロパティを一つ追加してルート直下から外すようにしておくと簡単です。

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/values/*"
        },
        {
            "path": "/\"_etag\"/?"
        }
    ]
}

この場合は values プロパティ以下を全て除外しています。計測値はデータ量が多くなりがちなのに対し、それ以外のデータは少ないことが多いので、インデックスに含めても影響は無視できる範囲でしょう。

複合インデックスの追加を検討する

個人的には最近 Cosmos DB の SQL で複雑なクエリを書くことがないのであまり使わないのですが、複数のプロパティに対して ORDER BY や WHERE でのフィルタリングを実行している場合には、複合インデックスを追加すると RU を下げられる可能性があります。

Queries that have an ORDER BY clause with two or more properties require a composite index. You can also define a composite index to improve the performance of many equality and range queries. By default, no composite indexes are defined so you should add composite indexes as needed.

Azure Cosmos DB indexing policies | Microsoft Docs

デフォルトで作成されるインデックスは単一のプロパティに対してのものになるので、アプリケーションからフィルタリング対象となるプロパティや並べ替えが行われることがわかっている場合は、複合インデックスの追加を検討するのが良いでしょう。

公式ブログでも以前にどういった場合に複合インデックスを使うのが効果的か、という点で紹介されています。目を通しておくのをお勧めします。

複雑なクエリに関しては Change Feed で最適な形を持った別 DB を作成するか、Synapse Analytics を使って実行する方が良いという点は変わりません。

TTL を使う場合にはインデックスが必要

一定期間過ぎると自動で項目を消してくれる TTL は便利なので結構使っていますが、Cosmos DB の TTL 機能はインデックスに依存しているらしく、利用する場合には必ずインデックスが必要となります。

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

インデックスポリシーが indexingMode = none の場合には TTL を設定しようとしてもエラーとなります。

なので TTL を使う必要はありつつ、インデックス自体は必要ない場合には excludedPaths/* を追加して対応します。これはドキュメントにも記載されている方法となります。

For scenarios where no property path needs to be indexed, but TTL is required, you can use an indexing policy with an indexing mode set to consistent, no included paths, and /* as the only excluded path.

Azure Cosmos DB indexing policies | Microsoft Docs

TTL を使いつつインデックスは必要ない場合のインデックスポリシーは以下のようになります。

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [],
    "excludedPaths": [
        {
            "path": "/*"
        },
        {
            "path": "/\"_etag\"/?"
        }
    ]
}

イベントで何回か紹介したことはありますが、大体はデモ中の説明だったのてテキストとして残します。

Change Feed 向けにインデックス設定を最適化する

ここまでクエリを使う前提でインデックスポリシーの最適化について書いてきましたが、Change Feed の場合は若干考え方が異なります。Change Feed ではクエリやインデックスを使うことなく、継続トークンだけを使って処理を行っているのでインデックスは完全に不要です。

この辺りは確証が持てなかったので Cosmos DB チームの Mark に聞いたところ、Change Feed のみで使う場合にはインデックスを完全にオフにすることが出来るとの回答を貰いました。

ただし Change Feed と TTL を組み合わせて使っている場合には、前述した制限によってインデックスが必要になるので、excludedPaths/* を追加したインデックスポリシーを使います。

ちなみに Change Feed の読み込みコストはドキュメントの読み込みと同じらしいです。クエリではないので RU の消費の予測が立てやすいのは良いですね。

これまで紹介した内容を実際に稼働中のアプリケーションに適用していますが、結果として書き込み RU を 1/5 ほどに削減しつつ、同時にスループットも大幅に改善することが出来ました。全体の RU は変えていないのでコストはそのままです。

ぶっちゃけ Change Feed 部分のインデックス最適化について書きたいだけでしたが、実際のアプリケーションでパフォーマンスが思ったより出なかった場合に参考にしてもらえればと思います。

WPF と WebView2 で MSAL を使わずに Azure AD 向け認可コードフローを実装してみた

普段は App Service の Easy Auth を使ってサクッと済ませるのですが、ちょいちょい MSAL を使った Azure AD や Azure AD B2C へのログイン処理をアプリケーションに組み込むことがあります。

特にネイティブアプリケーションの場合が多いので、.NET Core の WPF でサクッと書こうとするのですが、MSAL.NET は .NET Core の場合は利用が若干面倒です。具体的にはシステムのブラウザを使う必要があるので、リダイレクト先として localhost を準備しないといけないようです。

ちなみに .NET Framework の WPF では WebBrowser コントロールが使われたポップアップ内でログインが行えるので、通常使っているブラウザでのセッションを共有は出来ませんが、簡単に実装が出来ます。

MSAL 系のドキュメントは若干分かりにくいものが多いですが、チュートリアルは用意されています。

.NET Core への対応がイマイチなので .NET Framework の WPF を使っても WebBrowser は IE11 相当のエンジンが使われるので、Google など外部の IdP を追加すると以下のように動作が怪しくなります。

ログイン画面自体も古いバージョンになっていたので、古いブラウザ用のページに飛ばされているようです。

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

最近 Chromium Edge ベースの WebView2 がリリースされたので、この辺りの問題は解消されるでしょう。

しかし直近ではどうしようもなくなってしまうので、あえて MSAL を使わずに .NET Core の WPF と WebView2 を使って、自前でログイン処理を実装してみることにしました。

昔なら Implicit Flow でお茶を濁していたところですが、最近は Authorization Code Flow + PKCE が常識になっているので、こっちのフローを実装していきます。

認可コードフローの実装

WPF と WebView2 の導入に関しては今回は本質的な部分ではないのでまるっと省略します。適当に .NET Core な WPF プロジェクトを作成して、WebView2 をインストールするだけです。

認可コードフローについては Azure AD のドキュメントに説明があるので参考にしてください。そんなに難しくないのですぐに理解できると思います。

今回は Azure AD B2C を使うのでエンドポイントが異なりますが、フロー自体は同じなので違いは僅かです。

Azure AD へのアプリケーション登録時に Mobile and Desktop Client を選択してリダイレクト URI を追加しますが、今回は MSAL only となっている方を使ってみます。結局のところリダイレクトされた URL を WebView のイベントで取るので、判別できるものなら何でも良いです。

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

Win32 のデスクトップアプリケーションではリダイレクト先を用意できないので、WebView + 特殊なリダイレクト先を使っているという流れです。UWP や iOS / Android アプリケーションではリダイレクト先を用意できるので、システムブラウザからリダイレクトして戻ってくることも簡単に実装可能です。

早速実装に入っていきますが、最初からコードを全て載せてしまうことにします。

やっていることは単純で authorize エンドポイントへの URL を組み立てて、WebView を使ってアプリ内からアクセスしつつ、ナビゲーションイベントで特殊なリダイレクト先の場合は token エンドポイントを叩いて id_token を貰っているだけです。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private const string TenantBase = "https://***.b2clogin.com/***.onmicrosoft.com";
    private const string ClientId = "00000000-0000-0000-0000-000000000000";
    private const string PolicyId = "B2C_1_SignUp_SignIn_v2";
    private const string RedirectUri = "msal00000000-0000-0000-0000-000000000000://auth";

    private readonly HttpClient _httpClient = new HttpClient();

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var authorizeUri = new StringBuilder();

        authorizeUri.Append(TenantBase)
                    .Append("/oauth2/v2.0/authorize")
                    .Append($"?p={PolicyId}")
                    .Append($"&client_id={ClientId}")
                    .Append($"&nonce=defaultNonce")
                    .Append($"&redirect_uri={WebUtility.UrlEncode(RedirectUri)}")
                    .Append($"&scope=openid")
                    .Append($"&response_type=code")
                    .Append($"&prompt=login");

        webView2.Source = new Uri(authorizeUri.ToString());
    }

    private async void WebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
    {
        if (!e.Uri.StartsWith(RedirectUri))
        {
            return;
        }

        var code = ExtractAuthCode(e.Uri.Replace(RedirectUri, ""));

        var content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            { "grant_type", "authorization_code" },
            { "code", code }
        });

        var tokenUri = new StringBuilder();

        tokenUri.Append(TenantBase)
                .Append("/oauth2/v2.0/token")
                .Append($"?p={PolicyId}");

        var response = await _httpClient.PostAsync(tokenUri.ToString(), content);

        var result = await response.Content.ReadAsStringAsync();

        MessageBox.Show(result);
    }

    private string ExtractAuthCode(string query)
    {
        return query.TrimStart('?').Replace("code=", "");
    }
}

一番長いのは URL を組み立てている部分なので、それを除くとコード量は大したことは無いですね。

WebView2 固有の挙動としては、表示するページ URL は Source プロパティで指定するのが安心というものがあります。良くある Navigate メソッドは CoreWebView2 に用意されていますが、遅延初期化されているようでタイミングによって null が返ってきます。

CoreWebView2Ready イベントを待ってから実行すれば良いですが、Source プロパティを使うとその辺りを気にしなくても良い感じに扱ってくれます。実行するとお馴染みのログイン画面が表示されます。

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

メールアドレスとパスワードを入力してログインすると、認可コードフローが実行されて id_token が取得されました。リダイレクトのイベントによって処理を行っているのでぱっと見は良く分からないと思いますが、ブレークポイントを仕掛けると理解しやすいはずです。

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

今回は WebView2 を使っているので、Google などの IdP を選んだ場合もちゃんとページが表示されます。

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

認可コードフローの基本的な実装はこれで完成ですが、最近は認可コードの横取り対策を入れることが推奨されているので追加で実装していきます。

PKCE に対応させる

ネイティブアプリケーションの場合は Web アプリケーションとは異なり、カスタム URI スキームを定義して起動できるので、認可コードの横取りが簡単に行えます。

この辺りの理解には RFC を読んでおきました。元ソースに当たるのは重要ですね。

仕様としてはログインを開始したアプリケーションしか知らない値を都度ランダム生成し、それぞれのリクエストに渡すだけなので簡単です。

具体的には authorize エンドポイントには code_challenge code_challenge_method を追加して、token エンドポイントには code_verifier を渡します。

ちなみに code_verifier はランダムで生成した文字列、code_challenge は一般的には SHA-256 を計算した値です。以下のメソッドを見てもらった方が理解は早そうです。

private string GenerateCodeVerifier()
{
    var buffer = new byte[32];

    _random.NextBytes(buffer);

    return ToBase64Url(buffer);
}

private string ComputeCodeChallenge(string codeVerifier)
{
    var sha256 = SHA256.Create();

    var hash = sha256.ComputeHash(Encoding.ASCII.GetBytes(codeVerifier));

    return ToBase64Url(hash);
}

private string ToBase64Url(byte[] input)
{
    return Convert.ToBase64String(input)
                    .TrimEnd('=')
                    .Replace('+', '-')
                    .Replace('/', '_');
}

処理としてはログイン開始時に code_verifier を生成して、更に code_challenge を計算してクエリパラメータとして追加します。今回は SHA-256 を使うので code_challenge_method には S256 を設定します。

この時に code_verifier をセッション中は保持しておかないといけません。

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _codeVerifier = GenerateCodeVerifier();

    var codeChallenge = ComputeCodeChallenge(_codeVerifier);

    var authorizeUri = new StringBuilder();

    authorizeUri.Append(TenantBase)
                .Append("/oauth2/v2.0/authorize")
                .Append($"?p={PolicyId}")
                .Append($"&client_id={ClientId}")
                .Append($"&nonce=defaultNonce")
                .Append($"&redirect_uri={WebUtility.UrlEncode(RedirectUri)}")
                .Append($"&scope=openid")
                .Append($"&response_type=code")
                .Append($"&prompt=login")
                .Append($"&code_challenge={codeChallenge}")
                .Append($"&code_challenge_method=S256");

    webView2.Source = new Uri(authorizeUri.ToString());
}

ログインが完了し特殊なリダイレクト先に認可コード付きでリダイレクトされた後、token エンドポイントに code_verifier を追加で渡すようにします。

PKCE 付きで実行していた場合は省略できませんし、一致しない場合はエラーとなります。

private async void WebView2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
    // 省略

    var content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        { "grant_type", "authorization_code" },
        { "code", code },
        { "code_verifier", _codeVerifier }
    });

    // 省略
}

これで認可コードフローと PKCE を自前で実装出来ました。思ったより簡単でした。

MSAL などのライブラリを使った方が楽ですが、WPF の WebBrowser 問題のようにどうしようもない時もあるのと、フローを知っておくのは良いことなので勉強になります。

Azure App Service のネットワーク関連アップデート (NAT Gateway / NSG / Access Restriction)

ASE ではないマルチテナントの App Service では Outbound IP は同じ Stamp に乗っている他の App Service と共有されていて、外部連携などで IP を使ったアクセス制限が必要な場合には若干の問題がありましたが、NAT Gateway を使うことで任意の IP に固定できるようになりました。

少し前から検証用の環境で NAT Gateway を使った Outbound IP の固定が行えるようになっていることは確認していましたが、正式にアナウンスがあったので安心して使うことが出来ます。

アナウンスでは NAT Gateway にのみ触れられていますが、ついでに色々確認したところ NSG での Service Tag を使ったフィルタリングも正しく動作するようになっていたので、それについても書いています。

あまり関係ないですが Access Restriction でも Service Tag が使えるようになっていたので一緒に書きます。

NAT Gateway サポート

もはや説明は不要な気もしますが、App Service は Stamp や Scale unit と呼ばれる VM の集合体の単位でホストされているため、Outbound IP は 1 つではなく 2~8 ぐらいが割り当てられています。

ドキュメントにもあるように、この Outbound IP は App Service Plan の Tier 変更によって変化します。

最近では Regional VNET Integration や Service Endpoint / Private Endpoint によって Azure 内では IP アドレスを使った制限は必要なくなってきましたが、外部連携などで Outbound IP を固定したいこともあります。

このようなケースでは NAT Gateway を用意するのが定石ですが、Regional VNET Integration が GA した時には動きませんでした。しかし今回のアップデートでその辺りが正しく動作するようになったので、また 1 つ App Service の制約が取り払われました。

NAT Gateway を利用するためには App Service で Regional VNET Integration と WEBSITE_VNET_ROUTE_ALL を有効にする必要があります。要するに Private Endpoint を使う時と同じです。

これから実際に試すわけですが、NAT Gateway を追加する前に Outbound IP を確認しておきます。

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

NAT Gateway は適当にパブリック IP を作成して、App Service や VNET と同じ場所にデプロイしておきます。IP Prefixes も動作すると思いますが、今回は 1 つの IP を割り当てました。

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

このパブリック IP アドレスが App Service の Outbound IP として使われるようになります。

デプロイが完了すれば、後は App Service を参加させている Subnet へ NAT Gateway を割り当てます。試した限りでは反映されるまで少しかかるようでしたので、焦らずに待ちます。

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

実際の Outbound IP を確認するにはアプリに組み込む方法もありますが、自分は手軽に済ませたいので Kudu から curl を使って IP を返す API を適当に呼んでいます。

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

返ってきた IP は NAT Gateway に割り当てたパブリック IP と同じであることが確認できます。設定と動作としてはこれだけですが、これまで色々と言われていた部分なので解消されたのは喜ばしいです。

アナウンスでは Outbound IP の固定化だけではなく、専用の NAT Gateway を使うことで SNAT ポート周りの制約も大きく緩和されたとありました。個人的にはあまり困ったことは無いのですが、SNAT でのポート枯渇で苦しんでいた場合には有効でしょう。

NSG での Service Tag サポート

Regional VNET Integration の GA 時に NSG のサポートも同時に行われましたが、以前に自分が試した時は NSG の Service Tag を使ったフィルタリングは正しく動作していませんでした。

詳細は以下のエントリに書いていますが、全てが Any として扱われているようでした。

これが何時からか分かりませんが、気が付いたら正しく動作するようになっていました。NAT Gateway のサポート時に修正が行われたのかもしれません。

例えば以下のように Japan East の App Service へのアクセスをブロックするルールを追加してみます。これまでは全ての Outbound 通信がブロックされてしまいました。

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

しかし今は Japan East の App Service への通信だけがブロックされるようになりました。それ以外のリージョンにデプロイされている App Service への通信は問題なく行えます。

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

冷静に考えると特定サービスへのアクセスだけ許可・拒否する NSG ルールを追加するより、全てを Private Endpoint 経由にしてインターネットへのアクセスをブロックした方がシンプルな気がしますが、何かしらのユースケースに合うかもしれません。

Access Restriction での Service Tag サポート

あまり前の 2 つとは直接は関係ないのですが、App Service の Access Restriction で Service Tag を条件に指定出来るようになっていました。これまでは IP アドレスと VNET が指定可能でした。

ARM REST API 的には Service Tag が追加されていたのは知っていましたが、Azure Portal から設定出来るようになりました。アナウンスされるのかは微妙です。

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

VM の場合は Network Interface に NSG を追加出来るので、アクセス元をサービス単位で細かく制限可能でしたが、これで App Service でも同じようなアクセス制限を実現可能です。

Azure Portal から選択できる Service Tag は全体の一部で、Event Grid や Logic Apps といった App Service / Azure Functions 上の API を実際に利用するサービスが用意されていました。

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

特に便利だと思うのは Front Door Backend の Service Tag だと思います。これまでは IP の範囲を追加していましたが、変更される可能性はあるとドキュメントに明記されていました。しかし Service Tag で指定すると IP 範囲の変更の影響を受けません。

今回のアップデートで App Service のネットワーク周りでイマイチだと思っていた部分が一気に解消されました。これまでなら ASE が必要なことが多かったのですが、マルチテナントの App Service で実現できるアーキテクチャが増えたので本当に嬉しいです。

.NET Conf 2020 Online - .NET 5 リリース記念パーティートーク フォローアップ

金曜の夜に .NET 5 のリリース記念と称して、.NET 好きメンバーを集めてアップデートや新機能について話をしました。考えていたタイムスケジュールが完全に崩壊してしまいましたが、2 時間で何とか収まりました。

スピーカーは綺麗に興味が異なっていたので、言い出しっぺかつ進行役の自分としても学びが多かったです。

最近はフォローアップばかり書いている気がしますが、自分が関わったイベントについては今後も頑張ってツイートまとめの作成とフォローアップを書いていきたいと思います。

書くにあたっては Twitter と YouTube から独断と偏見でいくつか拾ってきました。

元々は Facebook で適当に書いたのがきっかけでしたが、多くの方に喜んでいただけたようなので嬉しいです。また機会があればこういう形式で楽しく話したいと思っています。

使用したスライド

Speaker Deck にもアップロードしていますが、スライド中のリンクが全て失われてしまったので Azure Storage にアップロードしたものを iframe で張り付けておきます。

今回は初めてスライドの作成に Marp を使いました。何も考えなくてもいい感じのスライドが出来上がったので便利でした。Windows で書きましたが問題なく動きました。

Twitter まとめと YouTube アーカイブ、文字起こし

開催中のハッシュタグ付きツイートに関してはまとめています。日本語でフィルタリングしたので若干抜け漏れはあると思いますが、グローバルイベントなので仕方ない部分もあります。

配信のアーカイブも既に YouTube で見られるようになっているので、当日都合が悪くて見られなかった方や見るのを忘れてしまった方はこちらからどうぞ。

この若干の圧を感じるサムネはみつばたんに作ってもらいました。素材が少なくて苦労していました。

そして id:orangeclover さんが 2 時間のトークを文字起こししてくださったので、感謝と共に紹介しておきます。かなり喋っていたはずなので大変だったと思いますが、テキストで読むのはまた違って良かったです。

どこまで使えるのかは分からないですが Teams にも文字起こしの機能があるようなので、機会があれば試してみたいと思いました。個人的には字幕よりも記事として読む方が好みでした。

.NET ランタイムのサポートポリシー

.NET 5 はリリースされましたが、Build 2020 の時の発表にもあったように LTS は 1 年後の .NET 6 まで待つ必要があります。正直なところ 1 年毎にアップデートが決まっている状態で、LTS と Current はあまり意味を持たないのかもしれません。

サポートポリシーは松村さんが Qiita で都度アップデートされています。ありがたく参照させてもらいます。

基本的には 1 年毎に Current を追いかけていくのが最適な選択なのではないかと考えています。

GitHub Actions / Azure Pipelines での .NET 5 サポート

コメントがあったので拾ってきました。結論から言うと GitHub Actions と Azure Pipelines ではプレビューの時から .NET 5 を使うことは出来ていました。ただし正式版までは VM にインストールされません。

VM にインストールされているソフトウェアの情報は以下のリポジトリから入手できます。

しばらく前から GitHub Actions と Azure Pipelines はこのリポジトリで作成された Image が利用されているので、ここだけを確認しておけば問題ありません。

当然ながら高頻度で Image を作成して更新することは難しいので、新しい SDK がインストールされるのは時間がかかりますが、それぞれの Action / Task を使うことで実行時にインストールが可能です。

Image のアップデートによって CI が不安定になるのを避けるために、常に使用するバージョンを明示的に指定するようにしています。Node.js などでも同じことがいえるので注意が必要です。

Blazor WebAssenbly

本編では基本的に坂本さんに全てを任せる形でしたが多少補足しておきます。.NET 5 に置ける ASP.NET Core のアップデートは Blazor がメインだったので、リリースノートはしっかり確認しておいた方が良いです。

dll がそのまま落ちてくるのがあまり好きではなかったのですが、最近は Brotli での圧縮を行うのが定石のようですね。アセンブリは Application Cache に保存されるというのは知りませんでした。

ダウンロードサイズが飲めない場合は Blazor WebAssembly は諦めという話はありましたが、少し前からアセンブリの Lazy Loading にも対応しているようなので、改善していきそうです。

WinForms や WPF アプリと同様に IL Trimming を利用することで、不要なコードを削減してダウンロードサイズを減らせるようです。個人的には動作が壊れるのが心配なので様子見です。

YARP のベンチマーク

配信中は YARP のベンチマークが存在しないという話をしましたが、ASP.NET Core で共通のベンチマークに組み込まれていたので、以下の Power BI から YARP と他のメジャーなリバースプロキシとのパフォーマンス比較が出来るようになっていました。

14 ページ目にある Proxies が YARP のベンチマーク結果になっています。

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

ベンチマーク結果から明らかにおかしい部分を削りましたが、まだ YARP は nginx より若干遅いようです。とは言え nginx は ARR の 2 倍以上早いので、プレビューにしては良い結果が出ているとも言えます。

Envoy や HAProxy とは大差を付けられていますが、素の HttpClient を使ったシンプルな Proxy は早いので、ルーティングや書き換え周りの最適化がまだ進んでいないようです。

MAUI / Uno Platform / WinUI など

紆余曲折あるクライアントアプリケーション開発については、今回の話を聞いた限りでは Uno Platform がかなり有望ではないかと感じました。MAUI を評価するには時期尚早ですが、若干の不安はあります。

Uno Platforrm は .NET 5 リリースとほぼ同時に対応を表明している点も評価が高いです。

そして配信中にも少し出たと思うのですが、WinUI 3.0 については元々来年リリースだったという話です。そして XAML Island は生きているらしいです。

この辺りは正直どうなるのか想像がつかないですが、正直なところ .NET Framework 4.8 の WinForms / WPF が長生きしそうだなと感じています。Project Reunion も若干心配です。

.NET の ARM64 対応

.NET Conf 2020 の前週に Apple Silicon の Mac が発表されて盛り上がっていましたが、現時点では .NET 6 でのサポートが予定されていました。TFM は osx-arm64 になるようです。

既に基本的な ARM64 のコードジェネレータなどは実装されているので、進捗はかなり良さそうです。

そして WPF の ARM64 対応は 21H1 となっているので、来年には .NET 5.0.1 とか 5.1 がリリースされるのかもしれません。バージョンの上げ方については続報を待ちたいです。

あまり関係ないですが、この Issue は Pro X を所有する人柱が集まっているので熱意が凄いです。

.NET 5 Early Access と Self-contained

最後は既に Self-contained を使って App Service に .NET 5 アプリケーションをデプロイしている場合について、軽く補足しておきます。.NET 5 Early Access に関しては以前書いたのでこちらを参照してください。

.NET 5 の Self-contained は実行に必要なライブラリなどを全て含んだ形になっているので、App Service の Runtime Stack を変更しても影響ありません。なので以下の手順で Early Access に変更すると安全です。

  1. .NET 5 Early Access を有効にする (この時点では Self-contained なアプリケーションが動作)
  2. Framework Dependent に切り替えて再デプロイ

Early Access が提供されるようになったので、Self-contained を使う場面はクライアントアプリケーションが多くなりそうです。App Service ではパッケージのサイズがパフォーマンスへ影響します。

Azure App Service の .NET 5 Early Access を一通り試してみた

.NET Conf 2020 で .NET 5 が正式リリースされましたが、同じタイミングで App Service での .NET 5 Early Access 機能が発表され、多少の制約はありますがリリースとほぼ同時に利用可能になりました。

App Service Teams のブログと GitHub にドキュメントが用意されています。これまでは App Service のイメージ更新を待つ必要がありましたが、Early Access 機能により即座に利用可能です。*1

制約としては App Service Plan の初期化時に必要なランタイムをインストールするので、コールドスタートと Kudu を利用したビルド時に影響が出ると書かれています。ビルド時に影響が出る理由は、.NET Core をインストール後初めて実行すると初期化が走るからでしょう。

実際に Windows の App Service に対して .NET 5 を使えるようにしてみましたが、特殊な場所にインストールされるといった挙動は無く、これまでのようにイメージ更新された時と同じように使えました。

System Drive 以下には JitInstallMarker というディレクトリが作成されて、JIT インストールしたコンポーネントのマーカーファイルが保存されていました。MSBuild 16.8 も同時に入るようです。

実行時にランタイムをインストールするのでコールドスタートへの影響は絶対に避けられませんが、App Service Plan の初期化時に 1 回だけなので、発生するのは Tier の変更、スケールアウト、インスタンスのリタイア時ぐらいでしょう。個人的には許容範囲内かと考えています。

ちなみに Linux の場合は .NET 5 用の Docker Image が App Service にキャッシュされていないだけなので、初回に Docker Image の pull コストがかかります。その後は App Service Plan が同じの間キャッシュが効くはずなので、Windows よりもオーバーヘッドが小さい可能性がありそうです。

ここから先は .NET 5 Early Access を利用する App Service をデプロイする各種方法について、実際に試した結果を載せています。Visual Studio では作成できないので、Azure Portal や Azure CLI を使いましょう。

Azure Portal を使ってデプロイする

Azure Portal から App Service を作成すると、Runtime Stack の .NET グループに .NET 5 が出てくるので、これを選択してデプロイすれば有効になります。紹介の必要がないぐらい非常に簡単でした。

Windows と Linux が選べますが、両方の OS で .NET 5 の Early Access が選べるようになっています。

既存の App Service を .NET 5 に変更する

これからは .NET Core 3.1 から .NET 5 へのアップグレードが多くなると思いますが、もちろん問題なく移行可能でした。App Service 上は前述したように Runtime Stack としては .NET Core ではなく .NET 扱いになっているので少し戸惑いました。

Windows の App Service の場合は以下のように Stack として .NET を選べば選択肢に出てきます。

Linux でも同様ですが Windows とは異なり Major / Minor Version まで選べるようになっています。今のところは 1 つしかありませんが、今後選択肢が増える可能性が高そうです。

この辺りは Java に合わせてあるように感じました。自動アップデートの設定も追加されるでしょう。

Azure CLI を使ってデプロイする

App Service の利用可能な Runtime Stack の一覧を表示する az webapp list-runtimes を実行すると、明らかにそれっぽい DOTNET|5.0 という Runtime Stack が見つかりました。

この Runtime Stack は Windows と Linux 共通だったので、以下のコマンドで Windows と Linux の両方に対応できます。違いは App Service Plan がどちらの OS 向けに作られているかに依存します。

az webapp create -g rg-net5-example -p plan-net5-example -n app-net5-example --runtime "DOTNET|5.0"

Azure Portal を使っていると Runtime Stack の指定方法にあまり馴染みがありませんが、基本は az webapp list-runtimes を実行して調べるのが一番早いです。

ARM Template を使ってデプロイする

Azure Portal と Azure CLI でデプロイできるということは、ARM REST API や ARM Template でも問題なくデプロイできるということです。特に Azure Portal はリソース作成時に ARM Template をダウンロードできるので、簡単にパラメータを調べることが出来ます。

そうして違いを確認してみると Windows の App Service の場合は netFrameworkVersionv5.0 を設定することで、.NET 5 の Early Access が利用可能になるようでした。

同様に Linux の App Service に対しても確認してみると、こちらは linuxFxVersionDOTNETCORE|5.0 を設定することで、.NET 5 の Early Access が利用可能になるようでした。

最悪という感想しか出ませんが、Azure CLI での Runtime Stack 指定とは若干異なるので注意が必要です。

今後変更される可能性はありそうです。特に Linux の App Service はこういうのが多い印象があります。

Terraform を使ってデプロイする

最後は Terraform を使ったデプロイを試しましたが、現時点の Terraform Provider for Azure v2.35.0 では Windows の .NET 5 Early Access のデプロイはエラーになります。

単純に dotnet_framework_version のバリデーションが原因なので Issue を上げたところ、あっという間に対応されました。次の v2.36.0 から利用可能になるはずです。

とりあえず自前で Provider をビルドして動作を確認しておきました。Windows と Linux で .NET 5 の Early Access を利用するための Terraform の定義は、以下のように非常にシンプルです。

resource "azurerm_app_service" "windows" {
  name                = "app-net5-win"
  location            = azurerm_resource_group.windows.location
  resource_group_name = azurerm_resource_group.windows.name
  app_service_plan_id = azurerm_app_service_plan.windows.id

  site_config {
    dotnet_framework_version = "v5.0"
  }
}

resource "azurerm_app_service" "linux" {
  name                = "app-net5-linux"
  location            = azurerm_resource_group.linux.location
  resource_group_name = azurerm_resource_group.linux.name
  app_service_plan_id = azurerm_app_service_plan.linux.id

  site_config {
    linux_fx_version = "DOTNETCORE|5.0"
  }
}

こうしてみると DOTNETCORE|5.0DOTNET|5.0 に統一してほしさしかありません。作成した App Service 全てに ASP.NET Core 5 のアプリケーションを Zip Deploy してみましたが、全て問題なく動作しました。

JIT インストール結果は App Service Plan で共有

Linux の場合は Docker が使われているので、特に Early Access だからと言ってキャッシュの有無以外に違いはありませんが、Windows の場合は System Drive にインストールされるので挙動が少し異なります。

具体的には JIT インストールされたコンポーネントは App Service Plan を共有する App Service 全てに反映されるということです。以下は .NET 5 を使わない設定にしている App Service ですが、App Service Plan を共有しているので .NET 5 SDK がインストールされています。

GitHub Actions や Azure Pipelines でビルドしたアプリケーションをデプロイする場合では特に影響はありませんが、Kudu を使ってビルドする場合には予期せぬ SDK バージョンが使われることが出てきそうです。

その場合は global.json を使って必要な .NET Core SDK バージョンを指定する必要が出てくるでしょう。

*1:今回の JIT インストール機能は、以前からちょいちょい Java で使われていた気がする

Hack Azure! #4 - Synapse と Cosmos で実現するサーバーレスデータ分析 フォローアップ

Hack Azure! #2 の Cosmos DB 回の時から何時かはやりたいと言っていた Synapse Analytics の回を開催しました。別名 Microsoft 畠山さんに何でも聞ける会という感じです。

少し前に Synapse Link for Cosmos DB を Synapse SQL Serverless で使う機能が Public Preview になったので、主にその辺りを取り上げました。名前が SQL on-demand から SQL Serverless に変わりつつあるという話もありました。

今回はやばいぐらい濃い話が多かったのでまとめるのに苦労しました。最後の方はよく分からないけどめっちゃ凄そう(こなみかん)という感じでした。

そろそろ App Service / Azure Functions の回をやりたいような気もしますが、話すネタがありそうで無い気もします。VNET 周りのアナウンスがあるまで難しそうな予感です。

Twitter まとめと YouTube アーカイブ

例によってツイートのまとめと YouTube のレコーディングは既に公開しています。今回は畠山さんのデモとトークに価値があるので、YouTube でのレコーディングは必見ですね。

今回は内容が濃かったのでツイートは少なめでしたね。自分も最後の方は「わからん」しか書いていません。

Synapse Analytics + Cosmos DB = 強い

Cosmos DB はスキーマレスで JSON での構造を持っていて、I/O に非常に強いデータストレージですが、それと引き換えにクエリには割と制約が多いです。最近は色々なクエリが使えるようになってきましたが、RDB には太刀打ちできません。

その辺りの制限を Synapse Analytics と Analytical Store を使うことで、解消できるというのが今回のメイントピックでした。Analytical Store はコストが安く、Synapse Link 経由でクエリを投げることが出来ます。

Synapse Analytics と Cosmos DB を組み合わせることで、サクッとデータ分析が行えるのはかなり強いですね。ちょまどさんのこのツイートが面白かったので貼っておきます。

Analytical Store 関連

Cosmos DB の Analytical Store は Synapse Link 経由でしか触れないのと、Analytical Store を使わないとコスト的にもやばいことになるので必須です。Cosmos DB はドキュメントがよくまとまっています。

いろいろな話が出てきましたが、気になった部分とはまりそうな部分についてピックアップしました。

データ・スキーマの自動同期

個人的に一番衝撃的だったのが Analytical Store へのデータ同期は大体 2 分以内で、特定のケースでは 5 分近く遅延が発生するということでした。大量データ分析目的では気にならない遅延だとは思いますが、てっきり数秒で同期されるものだと期待していたので少し残念でした。

Auto-sync latency is usually within 2 minutes. In cases of shared throughput database with a large number of containers, auto-sync latency of individual containers could be higher and take up to 5 minutes.

What is Azure Cosmos DB Analytical Store (Preview)? | Microsoft Docs

この後に出てくる裏側の話にもつながりますが、Cosmos DB はスキーマレスなので 1 つのドキュメントに後からプロパティが追加されることも良くあります。

そういった時に Analytical Store ではどのように扱われるのか気になりましたが、プロパティが追加された場合には自動的にスキーマを統合して更新してくれるようです。この辺りはドキュメントに詳しく書いてありますが、多少スキーマに対する制約があります。

As your schema evolves, and new properties are added over time, the analytical store automatically presents a unionized schema across all historical schemas in the transactional store.

What is Azure Cosmos DB Analytical Store (Preview)? | Microsoft Docs

スキーマレスとは言いつつも、ある程度のスキーマは必要になってくるのでデータモデリングには時間をかけたいところです。これは Analytical Store 関係なく Cosmos DB 利用時の全般に言えますが。

裏側は Azure Storage + Parquet らしい

Synapse Analytics から使うだけの場合は気にする必要はないのですが、Analytical Store の裏側は Azure Storage と Parquet 形式のファイルになっているらしいです。ライブではムッシュが実際にエラーを出して証明してくれました。

Synapse Link for Cosmos DB では、Cosmos DB の分析ストアにアクセスされますが、このストアは BLOB 上に保存されている snappy の圧縮形式が使用された Parquet ファイルのようです。

Synapse Analytics の Serverless SQL Pool (SQL on-demand) でテキストを参照する際の文字コードの設定 (おまけで Synapse Link for Cosmos DB) at SE の雑記

Parquet はスキーマを持ったカラムナフォーマットなので、前述したとおり Cosmos DB から Parquet を作成する場合には何らか方法でスキーマを作る必要があります。そして追記も基本は難しいので、2 分間の遅延というのはバッファリングしているということなのでしょう。

これでストレージ容量が安い理由は納得できました。Change Feed 至上主義者なので、場合によっては独自に ADLS に書き込む処理を書いた方が良い気もしています。ユースケースに合わせて選択しましょう。

照合順序には要注意

今のところ Synapse Link for Cosmos DB を触った人の 100% がはまっている気がする文字化け問題ですが、畠山さんの記事とムッシュのブログを読んでおけば完璧に理解できるはずです。

デフォルトを UTF-8 対応にしておいて欲しさしかないですが、歴史的経緯がありそうな気がします。

Spark pool も結構良さそうな話

Synapse SQL Serverless で Analytical Store を触れるようになった関係上多く取り上げましたが、後半では Spark pool を立ち上げて Notebook からクエリを書いてメタデータテーブルを作成していました。

専用の Pool を立ち上げるのは微妙だと思っていましたが、必要な時だけ立ち上げて自動で落とせるらしいので、割とありかなと思い始めました。

Notebook 上で Python ではなくて C# と LINQ で書きたい気持ちが高まってますが、今のところは出来なさそうです。この辺りは MS と .NET for Apache Spark の頑張り次第な気がします。*1

Query Acceleration との関係

Ignite 2020 のセッションでは Synapse Analytics と Deeply integrated と言われていた Query Acceleration との関連も少し話題に出ました。Synapse Analytics はブラウザからサクサク弄るのがメインですが、Query Acceleration はアプリケーションから使う前提になっています。

対応するファイルフォーマットは CSV / JSON で、単一の Blob に対してしかクエリを書けないですがアプリケーションから使う分にはこれで十分だと思います。Blob Index Tags と組み合わせて検索出来ればかなり強いと思っています。

共有メタデータと仮想 DWH の話

この辺りから理解が追いつかなくなってきましたが、頑張って適当にまとめます。Synapse SQL Serverless で扱われるテーブルはデータ自体は持っておらず、必要なメタデータだけを保持しているらしいです。

そして Spark もテーブルを作成出来ますが SQL Serverless から利用することが出来るので、1 つのクエリ内で複数データベースを同時に参照できるようです。正直 Spark 力が無さ過ぎて混乱の極みです。

畠山さんは最後の方で Power BI と Synapse SQL Serverless を使って、DWH を用意するのではなくオンデマンドで利用する仮想 DWH という話をされていました。この辺りも理解が正しいのか不安です。

Power BI から利用する場合には Import と DirectQuery の 2 つのモードがあるようですが、まずは Import を使うのが良いそうです。Power BI 力も低いので混乱の極みです。

個人的には SQL pool ってほとんど必要ないのだなぁという感想でした。ML 周りでは必要らしいですが。

Synapse で使われている分散 SQL エンジンの論文

最後の最後で英語の論文が出てくるとは完全に思っていませんでしたが、きっとムッシュが詳しい解説をしてくれると信じて論文のリンクだけを貼っておきます。

POLARIS: The Distributed SQL Engine in Azure Synapse

色んなソースからのデータをハッシュで分散して 1 つの Data Cell として扱うことで、効率的かつデータソースに寄らない分散クエリを実現しているっぽいのですが、正直よくわかりませんでした。