しばやん雑記

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

Synapse Link for Cosmos DB が Synapse SQL Serverless から使えるようになったので試した

期待していた Synapse Link for Cosmos DB ですが、これまで Spark でしか扱えなかったのでスルーしていましたが、やっと SQL on-demand や Synapse SQL Serverless と呼ばれるサーバーレスモデルから扱えるようになったので気になった部分を試しました。リージョンが限定されてるので注意。

Ignite 2020 の時に再度発表されて、その勢いで Synapse Analytcis Workspace を作ったのに対応していなかったという悲しみがありました。最後の方にこっそり数週間後みたいに書くのは止めてほしいと思います。

今回も例によって気になった部分だけなので、基本は Cosmos DB の Analytical Store を Synapse Link + Synapse SQL Serverless の組み合わせだけを試しています。

Synapse SQL Serverless はインスタンスの管理が必要なく、課金体系も処理した分だけという分かりやすいものになります。SQL や Spark のプールを用意する方法にもパフォーマンスやスケール面でのメリットはありますが、正直プールの管理とかはしたくないです。

Synapse Analytics Workspace と Synapse Link については世界のムッシュが既にまとめてくれているので、こっちを読んでおくと良いと思います。

Analytical Store を有効にした Cosmos DB を用意する

Synapse Link を使うためには、Synapse Link を有効化した Cosmos DB と Analytical Store を有効化した Container が必要になるので、ポチポチと Azure Portal から作成しました。

この辺りはドキュメントが比較的充実しているので、はまるポイントはないでしょう。

Container の作成よりも中に入れるデータの方が大変なので、適当に Data.gov をうろついて面白そうなデータを探してきました。今回は以下の SFO への航空機着陸データを Cosmos DB に投入してみました。

ドキュメントでは Covid-19 絡みのデータセットですが、飛行機が好きなのでこっちを使います。

何故か Azure Portal 上は Analytical Store を有効化したかの判別方法は、Analytical Storage TTL の設定が出てくるかぐらいしかない気がします。

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

データは適当に Cosmos DB SDK を使ってバルクで投入しました。Data Factory を使っても良かったのですが、リソースを作る方が面倒だったので C# で解決しています。

適当に 1 つを開いてみると、以下のような形式になっています。月毎に集計されたデータです。

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

面白そうなプロパティがいくつもあるので、これらに対して Synapse Link を使ってクエリを実行します。

Synapse Analytics Workspace に Cosmos DB をデータソースとして追加することは出来ますが、今のところ追加したデータソースからサクッとクエリを各機能が提供されていないので、ADLS に保存された CSV / JSON を読み取るときのように OPENROWSET を直接書いてクエリを実行します。

アクセスキーの設定はなかなかに怠いので、RBAC + Managed Identity を使いたい気持ちになります。

SELECT TOP 10 *
FROM OPENROWSET( 
       'CosmosDB',
       'account=shibayantest;database=AnalyticalTest;region=westus2;key=ACCESS_KEY',
       AirTraffic) AS documents

このクエリで Analytical Store に自動同期されたデータを対象に出来ます。とりあえずサンプル通り 10 件だけ取ってくるシンプルなクエリなので、Cosmos DB の SQL とあまり差を感じません。

実行すると Cosmos DB に保存されたデータからスキーマが自動的に認識されてデータが返ってきます。Query Acceleration もスキーマは自動認識だったので楽ですね。

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

Cosmos DB が内部で利用している _ts_etag も含まれるのが少し邪魔だなとは思います。そして接続先は SQL on-demand になっているので、Pool を用意することなくクエリが実行できています。

次はもうちょっと複雑なクエリを書いてみました。2020 年を対象に航空機のメーカー毎に着陸した回数を集計するものですが、Cosmos DB の SQL では集計関数が不安定なので確実に書けないです。

SELECT
    AircraftManufacturer,
    SUM(LandingCount) AS TotalLandingCount
FROM OPENROWSET( 
       'CosmosDB',
       'account=shibayantest;database=AnalyticalTest;region=westus2;key=ACCESS_KEY',
       AirTraffic) AS documents
WHERE
    ActivityPeriod LIKE '2020%'
GROUP BY
    AircraftManufacturer
ORDER BY
    SUM(LandingCount) DESC

Synapse Link を使うと普通の T-SQL と同じ感覚で書けるので、集計が圧倒的に楽ですね。

このクエリも実行して、今度はチャートとしてレンダリングしてみました。Power BI を使わずともビジュアライズが簡単に出来るのは便利です。

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

同じように今度は国際線の月毎の合計着陸回数を 10 年間分集計してみます。元データセットには国内線と国際線が混ざっているので、フィルタリングしつつ月毎に集計をすれば良いです。

SELECT
    ActivityPeriod,
    SUM(LandingCount) AS TotalLandingCount
FROM OPENROWSET( 
       'CosmosDB',
       'account=shibayantest;database=AnalyticalTest;region=westus2;key=ACCESS_KEY',
       AirTraffic) as documents
WHERE
    ActivityPeriod > '201006' AND GeoSummary = 'International'
GROUP BY
    ActivityPeriod
ORDER BY
    ActivityPeriod

2020 年 6 月のデータまでが含まれているので、Covid-19 の影響を受けていることが可視化できました。

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

ここまで色々なクエリを投げて試してみましたが、Monitor からは Analytical Store へのリクエスト数やデータサイズなどを確認することは出来ませんでした。

課金に影響する部分なので、何らかの確認方法が欲しいですが Preview 中の制約っぽいです。

View を作成してからクエリを実行

正直なところ毎回 OPENROWSET を使うのは面倒だと思っていましたが、Power BI から利用する方法が書いてあるドキュメントには View を作成して使う方法が紹介されていたので、これを使ってみました。

View の作成のためにはまず Database から作成しないといけないので、以下のようなクエリを流して Database と同時に Synapse Link を使う View を作成しました。

CREATE DATABASE AirTrafficCosmosDB
GO

USE AirTrafficCosmosDB
GO

CREATEVIEW AirTraffic
ASSELECT *
FROM OPENROWSET( 
       'CosmosDB',
       'account=shibayantest;database=AnalyticalTest;region=westus2;key=ACCESS_KEY',
       AirTraffic) AS documents
GO

一度 View を作成して Database を master から作成したものに切り替えれば、後は簡単な FROM 指定でクエリを書けるようになります。接続文字列的なものがクエリに出てこないので分かりやすくなりました。

SELECT
    ActivityPeriod,
    GeoRegion,
    SUM(LandingCount) AS TotalLandingCount
FROM
    AirTraffic
GROUP BY
    ActivityPeriod,
    GeoRegion
ORDER BY
    ActivityPeriod DESC

当然ながら実行結果は OPENROWSET を直接使ったものと同じです。当たり前すぎて面白みはありません。

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

View では不要なカラムの除外も出来るので、基本は作成しておくのが良さそうだと感じました。

日本語が文字化けするのを直す

これまで使ってきたデータセットはアルファベットと数字しか出てこないため問題になりませんでしたが、Cosmos DB は UTF-8 な JSON を扱うストレージなので、当然ながら日本語などの非 ASCII 文字がデータに含まれている可能性があります。

試しに Hack Azure で使った適当なデータセットを使ってみると、日本語は見事に化けました。

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

これだから ASCII 圏はという感じがしますが、ドキュメントにも記載のあるようにデフォルトの照合順序が UTF-8 向けになっていないのが原因らしいので、指示に従って LATIN1_GENERAL_100_CI_AS_SC_UTF8 に変更しておきます。

If you see unexpected characters in your text like Mélade instead of Mélade then your database collation is not set to UTF8 collation. Change collation of the database to some UTF8 collation using some SQL statement like ALTER DATABASE MyLdw COLLATE LATIN1_GENERAL_100_CI_AS_SC_UTF8.

Query Azure Cosmos DB data using SQL serverless in Azure Synapse Link (preview) - Azure Synapse Analytics | Microsoft Docs

ちなみに master に対しては実行できないので Database を作成してから実行する必要があります。以下のようなクエリを流して Database の照合順序を UTF-8 向けに変更しました。

CREATE DATABASE TodoItemCosmosDB
GO

USE TodoItemCosmosDB
GO

ALTER DATABASE TodoItemCosmosDB COLLATE LATIN1_GENERAL_100_CI_AS_SC_UTF8
GO

これで再度クエリを実行すると、日本語が化けることなく返ってくるようになりました。

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

もっと日本語に適した照合順序はあると思いますが、基本は UTF-8 向けのものを選べば問題なさそうです。この辺りはきっとムッシュが書いてくれると思うので期待しています。

追記

安心と信頼のムッシュが書いてくれました。日本語を扱う前にはまずこちらを確認しましょう。

SQL の照合順序周りは難しいなと感じる日々です。全て UTF-8 にすると楽できる世界になって欲しい。

ネストされたデータの扱いは少しめんどくさい

非リレーショナルで JSON を採用した Cosmos DB では、1 つのドキュメントにオブジェクトがネストされていることが多々あります。というかデータモデリングの時に大体はネストする構造を選ぶはずです。

そのようなデータに対してクエリを投げると、ネストされた部分は JSON のまま返ってきました。これは Synapse Link だからという訳ではなく、JSON を読み込んだ時にも普通に発生するようです。

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

対応方法としては OPENROWSET 時に WITH を使ってスキーマと該当するプロパティのパスを指定したり、JSON_VALUE などの関数でパスを展開したりと色々な方法があるようです。

1,2 個のプロパティだけ必要な場合は JSON_VALUE、もっと多くのプロパティが必要なら WITH でスキーマ定義、ネストされた配列を使う場合は OPENJSONCROSS APPLY というような使い分けになりそうです。

今回は 1 つのプロパティだけが欲しかったので JSON_VALUE でサクッと終わらせました。

SELECT TOP 10
    Title,
    Body,
    JSON_VALUE([User], '$.id') AS UserId
FROM OPENROWSET( 
       'CosmosDB',
       'account=shibayantest;database=AnalyticalTest;region=westus2;key=ACCESS_KEY',
       TodoItem) AS documents

実行すると JSON から指定したパスのプロパティだけが返ってくることが確認できます。

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

JSON なストレージから RDB へのマッピングが現実的にしんどいのは容易に想像がつくので、Synapse Link を使うことが分かっている場合にはデータモデリング時に考慮した方が良さそうです。

価格について

最後に気になる価格ですが、Cosmos DB の Analytical Store に関してはデータ容量と読み書きの 1 万操作ごとの金額が記載されていますが、現在はモニタリング方法が無いので金額を推定することすら困難です。

Synapse Analytics の SQL Serverless も現在は ADLS 向けの記載になっているので、Synapse Link での扱いは分からないままです。恐らくは処理した分だけの課金でしょうが単価は変わるかもしれません。

ADLS を使うよりは Synapse Link 経由で Cosmos DB を使う方が高くなるのは間違いないでしょうが、もうちょっと金額に関しては待つ必要がありますね。かなり使い勝手は良かったので期待しています。

Azure App Service の新しい Premium V3 インスタンスが使えるようになった

Ignite 2020 で発表された Premium V3 が最近になってようやく試せるようになったので、気になっていた点を実際にデプロイして一通り試したので残します。

9 月末から限られたリージョンでは Azure CLI から試せるようになっていましたが、利用可能なインスタンス数の制限や Azure Portal でのサポートが遅れたため結局は今日まで待つ必要がありました。

まだ多少の表示上の問題*1はあるようですが、デプロイ自体は問題なく行えます。

以下のように App Service の作成時に Premium V2 と Premium V3 から選べるようになっているはずです。そろそろ Windows の Recommended pricing tiers から S1 を外して欲しい気持ちが凄くあります。

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

Pricing ページがまだ更新されていないので、ひとまず Azure Portal から金額を確認しておきます。Ignite 2020 で公開された時間単価を使って確認しましたが、この金額は正しそうです。

10/8 追記

Pricing ページが更新されて Premium V3 のリージョン毎価格が確認出来るようになりました。

Azure Portal で確認できる金額と同じですが。計算ツールを使うことで Dev / Test 向け Pricing の確認も行えるようになっていました。

先に言っておくと Premium V3 は良い意味で普通だなと思いました。Premium V2 からの順当なアップグレードなので当たり前ではありますが、コストパフォーマンスは良くなっていてインスタンス世代も最新です。

App Service としてのインフラも最新の VMSS ベースになっているので、新機能の追加も期待できます。*2

Premium V3 の特徴

後半の裏側を弄った結果が長すぎるので、先に Premium V3 と Web App for Containers (Windows) の特徴をまとめておきました。ここを読んでおけば Premium V3 を理解した状態になるはずです。

基本的なこと

  • 全ての App Service で Premium V3 を利用可能
    • Windows / Linux / Containers (Linux, Windows) / Functions (ASP) の全て
  • Premium V3 の稼働する Stamp は VMSS ベースになった
    • Windows / Linux / Containers (Linux, Windows) の全て
    • Stamp 単位の対応なのでスケールダウンしても VMSS のまま
    • Additional Outbound IP Addresses の数がかなり多いのでスケールダウンには注意
  • 利用可能なリージョンは限定的
    • Japan East は対応、Japan West は Dv4 がそもそも使えないので非対応
    • 一覧は Azure CLI で az appservice list-locations --sku P1V3 を実行すると取得可能

金額について

  • Linux の Premium V3 はかなり安い (Windows 比 50% ぐらい)
    • 2 コア以上を使うなら Premium V3 を選ぶべき
    • 長期で使うことが確定しているなら Reserved Instance の利用も検討したい
  • Windows の Premium V3 もそこそこ安い (同コア数比 20% ぐらい)
    • 2 コア以上を使うなら Premium V3 を選ぶべき
    • 長期で使うことが確定しているなら Reserved Instance の利用も検討したい
  • Windows Containers は普通の Windows よりも 10% ほど高い
    • PC2-4 の頃を考えるとお得になったとは思う
    • 使い捨て用途ではないと思うので Reserved Instance を使いたい
  • Visual Studio Subscriber 向けの価格が凄い
    • P1V3 が S2 より安くなっている衝撃
    • 予告通りとにかく安くなっているので最高

デプロイ・アップグレード

  • 古い App Service Plan をデプロイ済みリソースグループにはデプロイ出来ない
    • 新しいリソースグループを作成してデプロイする必要がある*3
    • Retrofit は基本的に行われないと思っておいて良さそう
  • 既存の App Service Plan は Premium V3 へのアップグレードは出来ない
    • 新しい Stamp が必要なので新しいリソースグループへのデプロイが必要
    • 本当に極めて運のよいケース*4に限りアップグレードが可能
  • Windows と Linux の App Service Plan の混在は出来ないまま
    • ただし Windows と Windows Containers の混在は出来る(前から?)

プラットフォーム

  • App Service (Windows) は Windows Server 2016
    • 現行インスタンスから変更なし
    • インストールされているパッケージ類も全く同じ
  • Web App for Containers (Windows) は Windows Server, version 2004
  • Web App for Containers (Windows) から Private Endpoint が使える
    • つまり WEBSITE_VNET_ROUTE_ALL が動作するということ

Premium V2 の時にも発生しましたが、既存の App Service Plan のアップグレードや、同一リソースグループへの新規デプロイはほぼエラーになります。例外的に VMSS ベースの Stamp を掴んだ時だけ可能ですが、この 1,2 か月以内にデプロイしていて凄く運が良ければというレベルだと思います。

VMSS ベースの App Service について興味がある極々一部の方は、以前に書いたエントリを参照ください。

Windows / Linux / Containers 全てが VMSS ベースになったのと、今後リリースされる ASE v3 も VMSS ベースになることが確定しているので、何年越しか分かりませんが Cloud Services とはおさらばです。

さようなら Cloud Services、さようなら何故か D:\ 以下にいた Windows。

Premium V3 の裏側を調べた話

ここから先は普通に App Service で Premium V3 を使う場合には必要ない情報ばかりなので、裏側に興味がある人以外は読み飛ばしてよい内容です。

App Service (Windows)

流石に App Service (Windows) 側の OS バージョンは WIndows Server 2016 から上がることはありませんでした。既存のインスタンスが 2019 にアップデートされない限り、同じバージョンのままになるでしょう。

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

プラットフォームとしては VMSS 由来のドライブレター以外は全く変わっていないので、アプリケーションの互換性について気にする必要はないです。

CPU はドキュメント通り Xeon Platinum 8272CL が使われていました。ベースクロックが Dv2 でよく使われている Xeon E5-2673 v3 より少し高いですが、Hyper Threading が有効になった仮想コアなので、ACU は Dv2 より若干低めになっているのは前回書いた通りです。

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

全コア 3.4 GHz までブーストするらしいので、単純なシングルスレッド性能は Dv2 より高そうです。

Turbo Boost が有効になっているからか、App Service Plan を選んだ時に表示される ACU は minimum 付きです。本当にブーストするのかを確認する手段が App Service だとありませんが、ここは信用しましょう。

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

もちろん Azure Functions も Premium V3 な App Service Plan にデプロイ出来ます。これまで通りですね。

プレビュー中にも行えたのかは確認していませんが、App Service (Windows) と Web App for Containers (Windows) は同じリソースグループ内にデプロイすることが出来ました。

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

Windows と Linux を同じリソースグループにデプロイ出来ないのはこれまで通りです。

App Service (Linux), Web App for Containers (Linux)

Docker ベースの App Service (Linux) では Web Worker の調査をするのが非常に難易度が高いというかほぼ無理ですが、マシン名や DNS から引いた名前から VMSS ベースになっていることは確認できます。

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

IMDS v2 を使って情報を取れないか試しましたが、当然ながら塞がれていたので分かりませんでした。

去年に Linux に関しては Premium V2 の大幅な値下げが行われていますが、Premium V3 はそれよりもさらに値下げされているのでかなりコストパフォーマンスが良くなっています。かなり驚いています。

Premium V2 よりもメモリが増えているので、これまでよりも Container 向けではあると思います。32GB メモリまで選べるという余裕は重要だと思いました。

Web App for Containers (Windows)

2 年間のプレビューを経て GA となった Web App for Containers (Windows) ですが、プレビュー中を含め OS バージョンが 2 回上がっています。最終的には LTSC ではない SAC な 2004 が使われるようになりました。

Windows Containers はそれなりにリソースを食うので、最新世代 VM が使われている Premium V3 の中でも 8 コア 32GB メモリの P3V3 が、本番環境向けでの現実的な選択肢になると思います。

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

2004 の Docker Image はプレビュー開始当時の 2016 に比べるとかなりコンパクトになっているので、ようやく Windows Containers に周りの環境が追いついたという感じすらあります。

Docker Image サイズの縮小と .NET Framework での対応については結構面白い話が多いので、参考までにリンクを共有しておきます。NGEN 周りで本当に苦労していたようです。

最近は Docker すら全く使っていなかったので、Windows Containers を触るのは久しぶりでしたが、WinRM を使ったリモート接続が Cloud Shell から行えるようになっていたのが便利でした。

クライアント側で WinRM の起動やパスワードなどを扱うことなく、Cloud Shell からであればリソースグループ名と Web App 名を指定するだけで接続できます。

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

Private Endpoint へのアクセスが行えるのかが気になっていたので、適当に Private Endpoint を有効にした Storage Account を作成して WinRM 経由で通るか確認したところ、問題なく接続できました。

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

これで機能としては Windows Containers は Linux Containers とほぼ同等になったと考えて良さそうです。

*1:Premium V3 に対応していない App Service Plan でもスケールアップが可能なように見える

*2:Regional VNET Integration は新しい Stamp にしか対応していなかった

*3:これは Premium V2 や Regional VNET Integration の時にも発生した

*4:VMSS ベースの Stamp にデプロイされていたケース

Azure Functions で ReadyToRun を有効化してコールドスタートの高速化を図る

以前に Azure Functions のアップデートで ReadyToRun が使えるようになったと書きましたが、64bit OS 上では win-x64 向けの ReadyToRun コンパイルしか行えなかった問題が直ったので真面目に計測しました。

ReadyToRun の説明は既に何回か書いたので省略しますが、単純に言うと AOT コンパイルです。

Tiered Compilation や ReadyToRun の効果があるかどうかを Azure Functions で計測するのは簡単ではないのですが、コールドスタートにかかる時間を短縮する方法としては有効だろうと思っていたので試しています。

単純な Function では差が付きにくいことは分かっていたので、依存関係が比較的多くなる Durable Functions と DI の組み合わせで試します。2020 年の Function 開発では DI は当たり前に使われるようになっています。

ReadyToRun を有効化する

最近行われた修正によって win-x86 向けでの ReadyToRun のコンパイルでエラーが発生しなくなっていますが、Function SDK 自体はまだアップデートされていないので、更新された Extensions Metadata Generator を直接プロジェクトに追加して対応します。

これでコンパイルが出来るようになります。SDK がアップデートされたら消してよいです。

公式ドキュメントでは直接 csproj に ReadyToRun の設定を追加していましたが、デプロイ時のみ実行すればよいので Visual Studio からデプロイする場合には pubxml に追加すれば実現できます。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <WebPublishMethod>ZipDeploy</WebPublishMethod>
    <PublishProvider>AzureWebSite</PublishProvider>
    <!-- 省略 -->
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
    <PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>
</Project>

GitHub Actions や Azure Pipelines からデプロイする場合は .NET Core CLI にパラメータを追加します。

dotnet publish -c Release -r win-x86 -p:PublishReadyToRun=true

.NET Core CLI の -p オプションは MSBuild のプロパティの指定なので覚えておいて損はないです。

今回は Visual Studio を使って Zip Deploy + Run From Package を行っているので、ReadyToRun の設定は pubxml に追加しました。Function App 自体を 2 つ作成して、それぞれに ReadyToRun を有効・無効にしたものをデプロイすることで比較します。

計測に使用した環境

計測に使用した環境は以下の通りになります。ReadyToRun 設定の有無以外は全く同じです。

  • Azure Functions Runtime: v3.0.14492
  • リージョン: East US 2
  • インスタンスの種類: Consumption
  • デプロイ方法: Run From Package
  • OS: Windows
  • プラットフォーム: 32bit (win-x86)
  • テストに使った Function: Durable Functions v2.3.0 と DI
  • その他: 同一リソースグループ、同一 Webspace

今回 win-x86 を選んでいる理由は Consumption は 1 コア 1.5GB メモリにリソースが制限されるため、64bit を使うメリットが基本的に存在しないからです。当然ながら 32bit の方がメモリ使用量も少なくなります。

計測結果

Application Insights の Webtest 機能を使って、一定間隔でそれぞれの Function の HttpTrigger に対してリクエストを投げて、HTTP のレスポンス時間と Function Host の起動時間を調べています。

ウォームスタートにならないように大きく時間を空けて、1 リージョンからのみ送信します。

HTTP レスポンス時間

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

Function Host 起動時間

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

グラフを見ると分かるように、ReadyToRun の有無ではっきりと差が出ました。多少の外れ値はありますが、Consumption は共有型のホスティングかつファイルシステムを Azure Files を SMB でマウントしているので、その辺りの影響をダイレクトに受けるはずです。

これだけだとたまたま早いインスタンスに当たっただけという可能性もあるので、ReadyToRun の設定を逆転させてデプロイした結果が以下になります。

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

切り替えを行った後には値も逆転しているので、ReadyToRun が影響していることは明らかです。

Azure Functions はコールドスタートへの最適化が継続的に行われているので、1,2 年前と比べると何もしなくてもかなり早くなっていますが、ReadyToRun はそこから更に一押しする効果はあるようです。

物事には当然ながらメリットとデメリットが存在するので、最後にその辺りを軽くまとめます。

メリット

  • 良いケースでは 20% 近くコールドスタートにかかる時間を削減できる
    • Function Host だけを見ると 35% 近く削減できている
  • 規模の大きな Function App ではさらに効果が期待できる
    • 逆に依存関係が少なく、アセンブリサイズが小さい Function App では効果は薄そう

デメリット

  • AOT コンパイルを行うためにビルド時間が若干が無くなる
  • AOT コンパイル後のアセンブリサイズがかなり大きくなる
    • アセンブリに IL とネイティブコードの両方を含むため
  • Function App のプラットフォーム (32bit / 64bit) を変更するとリビルドが必要

検証のまとめ

ReadyToRun を有効にするデメリットは確かに存在していますが、大体は AOT コンパイルに関連する内容なので、Azure Functions がスケールアウト時に毎回 JIT で行っていたことを削減していると考えると、AOT で行うのは効率的だと言えます。

一番のデメリットはアセンブリサイズがかなり大きくなることだと思います。依存するライブラリにもよりますが、圧縮前で平均して 2 倍ぐらいになるようです。.NET Native や CoreRT とは仕組みが異なるので仕方ないですが、URL を設定する Run From Package の場合は問題になりやすいでしょう。

個人的には Consumption と Pre-Warming を無効化した Premium Plan の時に有効化すると思います。Pre-Warming が有効の場合はコールドスタートが原理上発生しないので、有効化しても意味がないです。

App Service から Azure Monitor Private Link Scope 経由で Application Insights を利用する

先日 Workspace ベースの Application Insights を弄っていた時に Network Isolation という設定が目に入り、そういえば Private Link Scope ってよく分かっていないと思ったのでデプロイして試しました。

単純に Azure Monitor へのアクセスを Private Endpoint 経由するためのサービスですが、Azure Monitor は他のサービスと異なり色々なもの組み合わせで作られているので、この Private Link Scope で境界を定義する必要があるようです。微妙に分かりにくい感あります。

ドキュメントでは使うためにサインアップが必要とありましたが、手持ちのサブスクリプションでは何もせずにデプロイが出来ました。片倉さん曰く GA しているらしいので、全てで使えるようになっていそうです。

一応は今回紹介する機能は Workspace ベースの Application Insights 限定のようです。*1

Private Link Scope と Private Endpoint のデプロイ

Private Link Scope のデプロイについては片倉さんのブログに丸投げします。ターゲットは Linux の VM ですが、構築の手順が順を追って分かりやすく書かれています。

Private Link Scope のデプロイ後、Private Endpoint のデプロイも必要になるのを少し忘れそうでした。

実際に Private Link Scope に対して Private Endpoint を追加すると沢山の Private DNS Zone がデプロイされます。ドメイン名の一貫性のなさに闇を感じつつ、Private Link Scope が必要な理由を垣間見られます。

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

この VNET には Cosmos DB と Key Vault の Private Endpoint をデプロイ済みなので更に増えています。

後は Private Link Scope に対して Private Endpoint 経由でアクセスしたい Application Insights や Log Analytics Workspace を追加します。ちなみに 1 つの Application Insights / Log Analytics Workspace に対して、複数の Private Link Scope を紐づけることも出来るようです。

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

これで Private Link Scope の設定は完了しましたが、最後に Application Insights の Network Isolation から Public アクセスを有効にするかどうかを設定します。この設定をしないとロックダウン出来ません。

今回は Ingest だけ Public アクセスをオフにしました。Query に対してオフにすると Azure Portal からの確認するために VNET 経由で行う必要があり、流石に面倒だったからです。

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

Application Insights に紐づけている Log Analytics Workspace に対しても、同じように Public アクセスをオフにしておきます。何故か Azure Portal には Network Isolation の設定が見当たりませんでしたが、ARM Explorer を使うことで変更できました。

ここまでの作業で Application Insights へのテレメトリ送信は Private Endpoint 経由でしか出来なくなっているはずです。動作確認は App Service や Azure Functions からいつも通りテレメトリを送信すれば良いです。

App Service / Azure Functions からテレメトリを送信

当然ながら Private Endpoint 経由で送信するためには、App Service では Regional VNET Integration の追加かつ WEBSITE_VNET_ROUTE_ALLWEBSITE_DNS_SERVER の設定が必要になります。

この辺りは何回も書いているので以前のエントリを参照してください。再起動が必要な時もあります。

無事に Private Endpoint を使う準備が出来ていれば、Kudu の Debug Console などを使って Application Insights の IngestionEndpoint への IP アドレスを引いてみると、Network Interface に割り当てられた IP アドレスが返ってくるはずです。

nslookup を使うと 2 回タイムアウトしましたが、App Service 向けに用意されている nameresolver を使うと綺麗に IP アドレスを引けます。

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

この状態で Azure Portal から Live Metrics を確認しつつ、適当なページへのアクセスや Function を実行してテレメトリを送信すると、これまで通りリクエスト数などの情報を確認できます。

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

確認できたので App Service / Azure Functions 側で WEBSITE_VNET_ROUTE_ALLWEBSITE_DNS_SERVER の設定を落として再度試すと、今度は全くテレメトリが流れてこないことが確認できます。

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

これで Application Insights へのテレメトリを Private Endpoint 経由で、これまで以上に安全に送信できるようになりました。Query の Public アクセスについては本番でら閉じましょう。

今回は Azure Functions だったので試していませんが、Profiler や Snapshop Debugger を使う場合には、以下のように独自のストレージアカウントを持ち込んで設定する必要があるようです。

正直なところあまり使う機会は無いかもしれませんが、Private Link Scope が必要なケースではこの辺りもしっかり押さえておく必要があるでしょう。

そもそもログにセンシティブなデータを書き出すなという話ですが、メモリダンプには何が含まれるのか分からないのでかなり注意深く扱う必要があります。*2

*1:Classic でも使えそうな気はしたけど試してません

*2:本番で Snapshot Debugger を使うかどうかはまた別の話

既存の Application Insights を Workspace ベースに移行した

これまでのリソースが Classic 扱いになって今後が気になる Application Insights ですが、実際に Log Analytics Workspace ベースに移行してみないとよく分からないと思ったので、手持ちの Azure Functions と組み合わせて使っていたリソースをいくつか移行してみました。

Ignite 2020 合わせで Workspace ベースが GA していたことにブチザッキで気が付きました。

Log Analytics Workspace ベースに移行してメリットがあるのかと言われると、正直なところ劇的なものは無さそうでした。移行ドキュメントに新機能としてまとめられているので、目を通しておくと良いです。

新機能として挙げられている Private Link や CMK / BYOS などは完全にエンタープライズ向けの機能といった感じです。確かに Snapshot Debugger で収集されたデータはメモリダンプが含まれているので、セキュアに扱いたいものですが本番ではまあ使わないです。

Azure Functions との組み合わせでカジュアルに使っている場合には、何も変わらない代わりにメリットもあまり無いように見えます。一応データインジェストが高速化されるとありますが、数値として出ていないため改善されたかどうかの把握が難しいです。

とは言え最近は RBAC を使ったアクセスコントロールが必要な場面があったり、App Service / Front Door などのサービスが Log Analytics へアクセスログなどを転送できるようにもなっているので、Application Insights のデータも同じ Log Analytics に送信できると確かに便利そうです。

1 日に100GB 以上といった大量のデータを扱う場合には 20% 以上の割引も効いてくるので、お得なケースもあるでしょう。Consumption でサクサク作っていた場合には、やはりマッチしないなという印象はあります。

とりあえず実際に Workspace ベースに移行してみます。当然ながら Log Analytics Workspace を作成しておく必要がありますが、移行作業自体は非常にシンプルです。Azure Portal を使う場合は Properties から "Migrate to Workspace-based" を選ぶだけです。

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

移行を選択すると送信先の Log Analytics を選ぶ画面と元に戻せないという警告が表示されますが、分かっていることなので作成した Log Analytics を選んで保存します。

ちなみに Workspace ベースに移行すると、後から送信先の Log Analytics を変更も出来ます。

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

今回は Azure Functions と使っていた Application Insights なので、適当に Function を実行してログを送信しました。Log Analytics でクエリ可能になるまで 1,2 分という感じだったので、5 分程度の遅延と言われている Application Insights よりは速くなっている気がします。

Workspace ベースになると Table の名前とスキーマが変わるので、既にクエリを書いている場合には注意が必要になりそうです。既存のアラートなどは Application Insights 側で問題なく動作するようです。

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

Prefix として App が付いているものは大体 Application Insights と考えておいてよいです。

Table 名とスキーマの差分についてはドキュメントが公開されているので、これを見て読み替えればよいです。Application Insights の Logs から扱う分にはこれまで通りのスキーマで扱えるので、サポートが終わるまで使うというのもありな気はします。

KQL で書くのは変わらずにスキーマが変わっているぐらいなので、適当にデータを見てクエリを書きなおすというのも良いかもしれません。こちらの方が一般的なスキーマになっているので、他のログと組み合わせやすいというメリットがあります。

すぐに違いが分かるのは camelCase から PascalCase になっている点だと思います。地味にはまりそう。

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

個人的には timestampTimeGenerated に統一されたのが嬉しいです。割と混乱するので。

これまでの Application Insights から唯一落ちた機能として連続エクスポートがありますが、これは Diagnostic settings を使うことでほぼ同じことが実現できるようです。

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

ここまで Workspace ベースに移行した Application Insights を触ってきましたが、ぶっちゃけた話どっちでも良いなと思いました。今後を考えると Workspace ベースの一択ではありますが、今すぐ既存のリソースを移行する必要性をあまり感じませんでした。

ただし既に Log Analytics を使っている場合には Application Insights のログも統合した方が楽になりそうです。移行自体はとても簡単なので、サクサク終わるはずです。

結局は Application Insights の機能はほぼ全て使えるのと課金体系も同じなので、新しくアプリケーションを作る時には Log Analytics を中心に置いた設計にすればよいかなという結論です。Application Insights 自体を分けるべきか、それとも統合するべきかはまた別途悩むポイントです。

Azure Data Lake Storage の Query Acceleration が GA になったので試したら最高だった

ブチザッキによると Build 2019 での Mark Russinovich 御大のセッションで発表されていた Blob の内容に対してクエリを実行できる、当時は Quick Query と呼ばれていた機能が Ignite 2020 前に GA していたようです。

今年の 5 月ぐらいから名前が Quick Query ではなく Query Acceleration に変わっていたようですが、長いし typo しそうなので Quick Query と言ってしまいそうです。

AWS の Athena に似た機能のように見えますが、Query Acceleration は事前にテーブルを作る必要がなく、クエリを投げるだけで使えるので簡単です。データフォーマットとしては CSV*1 と JSON を扱えます。

Ignite 2020 の "What’s New in Azure Storage" というセッションでも GA の発表がありました。Microsoft によると読み取られたデータの 20% 以下しか実際の分析クエリに使われないらしく、データに近い部分でフィルタリングを行えるようにすることで全体的な最適化を図る仕組みです。

セッション曰く "Deeply integrated into Azure Synapse Analytics" らしいのでかなり期待が持てます。Azure Storage 周りのアップデートは多すぎなのでセッションを見ておくと良いです。

機能名は Query Acceleration for Azure Data Lake Storage となっていますが、Hierarchical namespace が無効な GPv2 のストレージアカウントに対しても利用できるので、若干の分かりにくさを生んでいます。

SDK 的には Blob Storage と Data Lake Storage それぞれに名前は違いますが同じインターフェースで用意されているので、特に悩むことは無いでしょう。ぶっちゃけ Blob Storage の SDK で ADLS も使えます。

Query Acceleration の機能と価格

日本語版は GA 向けに更新されていないので、これまで通り基本は英語版を参照するようにすると間違いがありません。特に NuGet パッケージの扱い周りが全然違うので、日本語版を見ているとはまります。

ドキュメントは比較的充実しているので、一通り目を通しておきたいですね。特に SQL に関しては T-SQL とはある程度の互換性はありますが、当然ながら使えない機能が多いです。

フィルタリングを行うチュートリアルも用意されています。ドキュメントにも手順として記載されていますが、使うためには BlobQuery の Resource Provider を登録する必要があるので注意してください。

今後は自動で登録されると思いますが、少なくとも手持ちのサブスクリプションでは登録が必要でした。

新しい Azure SDK から 4 つの言語で使えるようになっています。PowerShell を使う例も載っていますが、Azure CLI でも az storage blob query コマンドが実装されているので、サクッと試す分には使えます。

そして気になる課金体系ですが、ドキュメントにはスキャンされたデータとクライアントに返されたデータに対して課金されるとあります。Pricing にも一応書かれていますが、単位が謎なので計算が出来ません。

こっちはまだプレビューの情報のままのようなので、更新されるのを待ちたいと思います。

Synapse Analytics の SQL on-demand や AWS の Athena を見ると課金体系としてはよくあるものなので、飛びぬけて高いということにはならないでしょう。

ここからは実際にアプリケーションから Query Acceleration を使ってみます。CSV と JSON で若干使い方が異なっているので、それぞれのフォーマットで試しています。

CSV に対してクエリを実行する

実際に Query Acceleration を C# SDK を使って試してみます。今回は Blob Storage SDK を使っていますが、前述の通り ADLS SDK では名前が変わっているだけなので適宜読み替えてください。

試すにあたってはそれなりのデータ量のファイルを用意しないと面白くないので探し回ったのですが、以下のサイトで国勢調査ベースの人口統計が CSV で手に入ったのでこれを使いました。

C# SDK は NuGet で公開されている 12.6.0 以上をインストールします。これが対応バージョンです。

最初はコンテナー単位でクエリが書けるのかと期待しましたが、Blob 単位でクエリを実行する必要があったので、大量のファイルからフィルタリングする場合には少し手間がかかります。

正直なところワイルドカードで Blob を指定できるとかなり良さそうでしたが、今回は 1 つのファイルに対してなので単純なコードで実現することにします。本質的な部分は BlobQueryOptions の用意と QueryAsync の実行だけです。それ以外はいつも通りのコードです。

var connectionString = "DefaultEndpointsProtocol=https;AccountName=***;AccountKey=***;EndpointSuffix=core.windows.net";

var blobServiceClient = new BlobServiceClient(connectionString);
var containerClient = blobServiceClient.GetBlobContainerClient("sampledata");

var blobClient = containerClient.GetBlockBlobClient("c03.csv");

var options = new BlobQueryOptions
{
    InputTextConfiguration = new BlobQueryCsvTextOptions
    {
        HasHeaders = false
    }
};

var result = await blobClient.QueryAsync("SELECT * FROM BlobStorage WHERE _7 < 1000", options);

var content = await new StreamReader(result.Value.Content).ReadToEndAsync();

Console.WriteLine(content);

InputTextConfiguration には BlobQueryCsvTextOptions を指定しています。このクラスはデリミタやエスケープシーケンスのプロパティを持っているので、必要に応じて変更しましょう。

本来なら HasHeaderstrue に設定すると SQL からカラム名でアクセス出来るので便利ですが、後述する問題によって今回は false を設定しています。

カラムに対して名前でアクセスは出来ませんが _1, _2, _3 のようにインデックスでアクセス出来るので、これを使ってフィルタリングのクエリを書いています。今回の例では _7 は人口に該当します。

このコードを実行してみると、人口でフィルタリングされたデータが返ってくることが確認できます。

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

もちろん SELECT を使って必要なカラムに絞り込んだり、集計関数を 1 つだけ使うことも出来ます。CSV には型が無いので、集計関数を使うときはキャストが必要で少し面倒でした。

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

現時点で把握している問題としては、SQL 内でマルチバイト文字を使うとエラーになるという点です。従って CSV のヘッダーやフィルタリングする値が日本語の場合はエラーになります。

今回使用した国勢調査の人口統計はヘッダーや値に日本語が含まれているので、ヘッダーなしのデータとして扱うしか方法がありませんでした。フィードバック済みなので対応待ちです。

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

この程度の問題であれば Public Preview に気が付いていれば、GA する前に報告が出来たと思うので残念です。とは言えマルチバイト文字を使うケースは少ない思うのであまり実害はないでしょう。

JSON に対してクエリを実行する

次は JSON に対してのクエリを試します。対応するデータは 1 行が 1 つの JSON として表現されているものになるので、Azure Monitor が出力するログなどが分析対象としては面白いと考えるでしょう。

残念ながら Azure Monitor からのログは Append Blob が使われているので Query Acceleration は使えません。

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

もちろん何らかの方法で Block Blob としてコピーしなおせば使えますが、正直かなり面倒なので分析用は Log Analytics に入れて KQL を書いた方が良い気がします。

今回はデータを用意するのが面倒だったので App Service が Azure Monitor 経由で出力するアクセスログを、Block Blob としてコピーしなおしたものを使います。ファイルが 300 近くあるので Blob の一覧を取得して順次クエリを実行しています。

var connectionString = "DefaultEndpointsProtocol=https;AccountName=***;AccountKey=***;EndpointSuffix=core.windows.net";

var blobServiceClient = new BlobServiceClient(connectionString);
var containerClient = blobServiceClient.GetBlobContainerClient("httplogs");

await foreach (var blobItem in containerClient.GetBlobsAsync(BlobTraits.Metadata))
{
    var blobClient = containerClient.GetBlockBlobClient(blobItem.Name);

    var options = new BlobQueryOptions
    {
        InputTextConfiguration = new BlobQueryJsonTextOptions(),
        OutputTextConfiguration = new BlobQueryJsonTextOptions()
    };

    var result = await blobClient.QueryAsync("SELECT CsMethod, UserAgent FROM BlobStorage[*].properties WHERE CsUriStem = '/'", options);

    var content = await new StreamReader(result.Value.Content).ReadToEndAsync();

    Console.Write(content);
}

JSON を対象にする場合は InputTextConfiguration に明示的に BlobQueryJsonTextOptions を設定しないと、CSV として読み込もうとしてエラーになります。同様に OutputTextConfiguration にも指定しないと CSV として返ってきます。*2

クエリの説明はあまり要らないと思いますが、サイトのルート / へのログだけをフィルタリングして、HTTP Method と User-Agent だけ出力しています。

FROM の書き方があまり見ない形ですが、Azure Monitor からの JSON は以下のように properties でネストされた形になっているので、そのプロパティだけに絞り込むという指定をしています。

{
  "time": "2020-08-27T00:52:17.7598888Z",
  "resourceId": "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/DEFAULT-WEB-JAPANEAST/PROVIDERS/MICROSOFT.WEB/SITES/APPSERVICEINFO",
  "category": "AppServiceHTTPLogs",
  "properties": {
    "CsMethod": "GET",
    "CsUriStem": "/api/siteextension",
    "SPort": "443",
    "CIp": "0.0.0.0",
    "UserAgent": "Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/86.0.4223.0+Safari/537.36+Edg/86.0.608.2",
    "CsHost": "appservice.info",
    "ScStatus": 200,
    "ScSubStatus": "0",
    "ScWin32Status": "0",
    "ScBytes": 1188,
    "CsBytes": 1205,
    "TimeTaken": 46,
    "Result": "Success",
    "Cookie": "-",
    "CsUriQuery": "X-ARR-LOG-ID=00000000-0000-0000-0000-000000000000",
    "CsUsername": "-",
    "Referer": "https://appservice.info/"
  }
}

このコードを実行してみると、順次該当するログが出力されていきます。

Blob 単位でのクエリになるので、良い感じのパス規約や Blob Index Tags と組み合わせると、必要なものだけ取れるようにするとさらに効率的でしょう。

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

最近は Synapse Analytics の SQL on-demand で Azure Data Lake Storage に保存した CSV に対してクエリを書きたいことが多かったのですが、複雑なクエリが必要なくアプリケーションから使いたい場合に Query Acceleration はかなり便利だと思います。

実際に仕事でも早速 Query Acceleration を使いたい場面があるので、更に利用できる SQL の検証や ADLS の設計に励んでいきたいところです。

*1:デリミタなどはクエリ時に指定できるので TSV なども可能のはず

*2:CSV => JSON / JSON=>CSV にも使えそうな感じがする

Hack Azure! #3 Catch up Ignite 2020 Azure updates フォローアップ

第 3 回目になりますが、Hack Azure! ということで Ignite 2020 の Azure に関するアップデートについて話す会をやりました。Ignite 2020 開催の 1 週間前という突然の募集でしたが、多くの方に参加いただき感謝です。

今回も Q&A を行っていますが、独断と偏見で選ばれた一部の内容についてフォローアップしておきます。

前回の Cosmos DB 編とは異なり、ちょっと面白系の話をピックアップしている気がします。

App Service (Premium V3 / Windows Containers / ASE v3) に関しては後日またいろいろ試して書きます。気分が乗れば試しているのを、適当に配信するのも面白いかもと思っています。

Twitter まとめと YouTube アーカイブ

例によって Togetter でツイートまとめと、YouTube でアーカイブを公開しています。時間が合わなくて見られなかった方や、さとうなおきさんの神補足ツイートはこちらから確認ください。

前後の余計な部分だけカットしているという話なので、本編は基本そのままです。

森羅万象について書かれるブチザッキ

今回もとりあえずブチザッキを読んでおけば、一通りは把握できることが証明されたと思います。

そろそろ画像の貼りすぎで WordPress の容量がやばいらしいので、ブチザッキの継続性のために GitHub Sponsor 経由でポチっと支援しておきました。

今後もブチザッキに期待していきましょう。我々の睡眠時間のためにも。

App Service の Go 対応

Twitter ではめちゃくちゃ Go 対応について書かれていましたが、本編ではその時に ACS の話をしていたので、App Service での対応ではなく SDK レベルでの話をしてしまいました。

古くから App Service を使っていた方なら知っているかもしれませんが、Go がプリインストールされていて Git を使った CI/CD まで対応していた時代がありました。

その頃は Go で Web アプリケーションを作る世界になっていなかったので、使用率が非常に低く削除されたという経緯です。今なら Docker で何でもできるというスタンスです。

Docker 以外で使う方法も存在していて、アプリケーションをビルドして exe 単体で実行可能な状態かつ、環境変数などで渡されたポート番号で HTTP を受けることが出来れば App Service 上で実行できます。

実際に App Service で非対応の Deno を Windows の App Service で動かすことも簡単です。

これらは HttpPlatformHandler によって実現されています。Azure Functions の Custom Handler と同じような形で Web アプリケーションを任意のランタイムで実行できる仕組みです。

Azure Functions v2 のプラットフォームアップデート

.NET Framework 4.8 がほぼ全ての Azure のパブリックなリージョンにデプロイされましたが、次には Azure Functions v2 のプラットフォームアップデートがやってきます。詳細は以前書いた通りです。

Azure Functions Runtime v3 の v2 互換モードで実行されるようになるので、実際には .NET Core 3.1 への強制以降となります。ぶっちゃけ難しくないので、さっさと v3 へのアップデートを行った方が健全です。

Web App for Containers の Multi Container 対応は GA する?

既に Multi Container のプレビューが始まってから 2 年以上が経過しているので、このままずっと Shared Plan のようにプレビューのままになるか、もしかしたら廃止される可能性もあると思っています。

プレビュー中のサービスはフィードバックが重要になるので、基本はさとうなおきさんの仰る通りです。

古いプレビューに関してはフィードバック先が分からないという問題点はありますが、User Voice や公式ドキュメントから GitHub 経由でフィードバックが送れるようになっているので、その辺りで書いておくと分かっている人にある程度ルーティングしてくれるはずです。

Azure Orbital について

Ignite 2020 のアップデートで一番無縁なサービスが Azure Orbital だと思います。またそのままの名前です。

利用可能な衛星は GEO 以外になっているので、GEO で運用している人は諦めましょう。

Orbital can be used to schedule Contacts with Non-Geostationary Earth Orbit satellites (NGSO) which include Low Earth Orbit (LEO) and Medium Earth Orbit (MEO) Satellites.

パートナーとしては SES が有名ですね。Space X で良く打ち上げられている衛星のオペレーターです。

実際のところは一生使うことはないサービスかなと思います。衛星は流石にハードルが高すぎる。

Classic と付くと終了フラグなのか?

これまでも Azure では Classic と付くようになったサービスがいくつかあります。

例えば Azure Resource Manager 以前の API は Classic Deployment Model と呼ばれましたし、VM も第一世代は Classic と呼ばれていました。それ以外に ML Studio も Classic があったようですね。

これらのサービスは一定の移行期間を置いて、強制的に新しいサービスへの移行や廃止が行われてきました。

話に出た Application Insights も Workspace-based ではないこれまでのリソースは Classic と付くようになったので、将来的には全て Workspace-based になる可能性が高そうです。

デプロイに時間のかかるサービスは…

大体インフラの仕組みが古いので良くないサービスが多いです。以下、代表的な例。

  • API Management (Consumption 以外)
  • Azure Cache for Redis
  • Application Gateway

裏側が恐らく Cloud Services や古い VM で構築されているっぽいので、インフラ側が洗練されていません。

デプロイが遅いということは、スケーリングにも時間がかかるということなので困ることが多いです。

GitHub Learning Lab がめっちゃ良い話

GitHub Actions 絡みで少し話に出ましたが、GitHub Learning Lab がめちゃくちゃ学習に便利です。GitHub の機能について、GitHub 上でそのまま学べるので理解が早いです。

日本語は一部だけ用意されていますが、GitHub Actions の Learning については英語のみでした。

自分は DevOps 周りは一通り終わらせています。GitHub Actions の Workflow を自動生成するだけだと応用が利かないので、Learning Lab で学んでおくと捗ると思います。

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

困った点は良くも悪くも英語ノリなのでジョークなどのネタが全く通じないことです。説明が必要。

コンテンツを完了するといろいろな種類の Octocat が見られるので、割と面白かったです。

App Service で Let's Encrypt の新しいルート証明書を扱えないのを何とかする

ちょいちょい話題に上がってくる Let's Encrypt のルート証明書が新しくなる件ですが、少し前に 2021/1/11 からに延期となりました。まだ延期の可能性はありそうです。

新しいルート証明書は ISRG Root X1 になるので、古い Android などで問題になるようです。

まずは App Service で扱うための方法の前に、この辺りの事情について軽く整理しておきます。

今は IdenTrust の DST Root CA X3 によるクロス署名が行われた中間証明書が使われています。例えばこのブログでは Let's Encrypt の証明書が使われているので、証明のパスは以下のようになります。

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

既に Let's Encrypt Authority X3 への署名は DST Root CA X3ISRG Root X1 の両方によって行われているので、実際には 2 種類存在することになります。

この辺りの仕組みについては Let's Encrypt による解説が一番正確で分かりやすいです。

実際に ISRG Root X1 によって署名された証明書は、certbotや acme.sh などで --preferred-chain オプションを付けることで取得できます。なので ISRG Root X1 を指定して証明書を取得し、PFX に変換後 App Service にアップロードすれば使えるはずですが、何故か上手くいかなかった話です。

今回は acme.sh を使って ISRG Root X1 で署名された証明書を取得しました。PFX を作成後に openssl で調べると、中間証明書の issuerISRG Root X1 になっていることが確認できます。

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

ちなみに PFX には秘密鍵、証明書、中間証明書が含まれている状態です。

この PFX を App Service にアップロードして追加済みのカスタムドメインにバインドを追加してみると、実際にはこれまで通り古い DST Root CA X3 で署名された中間証明書が使われています。

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

Qualys SSL Labs で調べてみても結果は変わらなかったので、クライアント側の問題ではないようです。同じ PFX を Azure CDN にアップロードすると、問題なく ISRG Root X1 で署名されたものが返ってきます。

PFX には問題がないことは分かったので色々と試行錯誤をした結果、PFX に ISRG Root X1 自体の証明書を含めることで App Service で扱えるようになることが分かりました。

ISRG Root X1 の証明書は Let's Encrypt のサイトからダウンロードできるので、それを acme.sh によってダウンロードされた中間証明書と結合して PFX に変換しました。

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

全く関係ないですが WSL 2 を使うと openssl が Git Bash と比べて安定しているので便利でした。

この PFX をアップロードして適当なカスタムドメインにバインドしなおすと、以下のように ISRG Root X1 で署名された中間証明書が使われていることが確認できます。

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

とりあえずこの方法で動作することは確認できましたが、PFX にルート証明書を含めることに意味があるのか謎です。更に謎な挙動として一度 ISRG Root X1 として扱われるようになると、別の証明書でも中間証明書が切り替わるようでした。正直意味が分からない挙動ですし、何らかのバグの臭いを感じます。

ほぼ全ての ACME に対応したクライアントはルート証明書を含めてはくれないので、App Service に対してISRG Root X1 向け証明書発行の自動化は難しいと思います。

それよりも DST Root CA X3 とそれによって署名された Let's Encrypt Authority X3 の期限が切れた後の挙動が凄く心配です。問い合わせはしているので、反応があれば追記します。

Ignite 2020 で発表された App Service に関するアップデート

Ignite 2020 が始まって、いつも通り Azure Update のフィードが大量に流れてきていますが、App Service 以外にはあまり興味が無いのでいつも通り App Service 周りだけまとめます。

App Service のセッションは見当たらなかったですが、Book of News と Update で十分把握できます。

久し振りに App Service のインフラ周りに大きなアップデートがあったので結構テンションが上がっています。最近はコンテナ勢に押され気味でしたが、もう App Service で十分という機運がさらに高まっています。

Azure Functions に関しては明日の YouTube Live で何かしらアップデートがあると思われます。

Premium V3

App Service の本番向けインスタンスと言えば Dv2 ベースの Premium V2 が常識でしたが、Dv4 ベースとなった Premium V3 が追加されました。久し振りに 8 コアの App Service Plan が戻ってきました。

既に使えそうな書き方がされてますが、デプロイ出来なかったので 10/1 からの予感です。*1

Cascade Lake ベースの最新 VM で、Windows / Linux / Container の全てで使えます。コア数とメモリ容量からして D2_v4 / D4_v4 / D8_v4 がそのまま P1V3 / P2V3 / P3V3 に割り当てられているようです。

Premium V2 は Dv2 ベースだったので仮想コア == 物理コアでしたが、Dv4 は Hyper-Threading が有効になっているので ACU はちょっと低いです。しかしその分価格が安くなっているのがメリットです。

最大で 8 コア 32GB メモリというインスタンスなので、これまでは App Service では難しかったメモリを大量に消費する系のアプリケーションにも対応できるようになっています。これまで以上にアプリケーションを高密度に積むことも出来ますね。

気になる価格はまだ Pricing のページが公開されていないので不明な点が多いですが、East US で P1V3 が $0.32/h から使えるようです。これは P2V2 よりも安いですがメモリは多いのでお得感あります。

Reserved Instance, Dev/Test pricing

App Service の特性上、常時稼働するインスタンスが存在することがほとんどなので欲しかったのですが、ついに Reserved Instance が選べるようになりました。3 年間の予約で最大 55% 引きで利用できます。

長期的に使うことがほぼ確定しているアプリケーションの場合は検討する価値ありです。

そして最高だと思ったのが Premium V3 と V2 に対して Dev/Test 用途の価格が用意されていることですね。

これまで開発・ステージング環境で VNET Integration を検証する際には、Premium V2 か新しい Stamp に載った Standard が必要でしたが、今後は約 1/3 の価格で Premium V3 / V2 を使えるようになるみたいです。恐らく SLA 無しでサブスクリプションの制限*2とかがありそうですが、魅力的なオプションです。

Terraform などで開発・ステージング・本番を同一定義で作っている場合などにも、面倒な切り分けが必要なくなりそうなので、かなり便利な気がしています。

Web App for Containers (Windows) GA

2 年ぐらい Public Preview だった気がしますが、ついに Windows Containers ベースの Web App for Containers が 10/1 に GA です。ぶっちゃけ OS イメージの縮小と最適化に 2 年かかっただけの気がします。

Windows Containers も Premium V3 上で動くようになるようですが、現在の Premium Container は Dv3 ベースなので、Dv4 ベースの Premium V3 へすんなりは移行できなさそうな予感です。

Regional VNET Integration / Private Link / Managed Identity といった通常の App Service と同じ機能が使えるようになっているのは良いですね。WEBSITE_VNET_ROUTE_ALL が通るのかは少し気になるポイントです。

最近は Server Core / Nano Server のイメージサイズもかなり小さくなっているので、ようやく実用的な起動時間で扱えるようになっているかもしれません。

レガシーアプリケーションの塩漬け環境としては Windows Containers は最適な気がしますが、これを機にちゃんと Lift and Shift 出来ればよいですね(遠い目

Isolated V2 (ASE v3)

Isolated V2 と ASE v3 で表記がブレッブレですが、11/1 から Public Preview が開始されるようです。分かりやすい特徴としては VMSS ベースになったことと、固定の Stamp fee が無くなったことが大きいでしょう。

それ以外にはネットワーク構成が大きく変わって、Azure の管理用とアプリケーション用で分離されるようになるらしいです。これまで NSG で Azure 管理用に特定のポートを開ける必要がありましたが、その辺りを気にする必要が無くなるということでしょう。

価格周りは全く情報が出てきていないので、11/1 になったら実際に試してみるつもりです。

GitHub Actions integration

アップデートの内容からは結局どう変わったのかが良く分からないですが、Azure Portal にある Deployment Center がアップデートされて GitHub Actions に対応したという話のようです。

既に数日前から Preview 付きの Deployment Center が見られるようになっていました。

この UI からポチポチとリポジトリやブランチを選べば、GitHub Actions 向けの Workflow ファイルが生成されるという仕組みのようです。

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

経験上、自動生成で上手くいくケースはあまり多くないので、参考程度に使うのが良いと思います。特に Monorepo だとまずちゃんと動かないはずですし、GitHub Actions は勉強しておいて損はありません。

Azure Functions に関しては特に発表はありませんでしたが、もちろん Premium V3 や RI の恩恵は受けるはずなので、明日の Function Team の YouTube Live を楽しみに待っておきたいと思います。

*1:価格も正式にはまだ公開されておらず 10/1 にアップデートと書かれている

*2:Visual Studio subscriber 限定とか

Azure Functions におけるスケーリングの基本的な考え方

最近は Azure Functions でのスケーリングに関する質問をよくされるのと、自分自身が仕事で効率良くスケーリングが可能な Function 実装を行う必要が多かったのでまとめておきます。今回はスケールアップではなく、スケールアウトの方を指しています。

事前に以下のドキュメントを読んでおいた方が理解が早いです。特に Azure Functions は Web Apps とは異なるスケーリングの仕組みを持っているので、若干仕組みが特殊です。

多少混乱しやすいですが Azure Functions は 3 箇所でスケーリングに関する設定を行えます。昔は設定が少しわかりにくかったですが、最近はポータルでの設定が分かりやすくなりました。

結果としては Function App のインスタンス数 * Function の同時実行数の結果が、この Function App で実際に同時に実行される Function の回数になります。

  • App Service Plan のインスタンス数
    • 実行に使われる仮想マシンの数に相当する
  • Function App 単位のインスタンス数
    • App Service Plan のインスタンス数を上限にアプリ単位で設定が可能
  • インスタンス単位の Function 同時実行数
    • デフォルトで Runtime は設定された範囲で Function を同時に実行する

Consumption や Premium Plan を使っていると Function の同時実行数を気にしなくてもガンガンスケールしていきますが、インスタンスサイズや処理内容によっては同時実行数を変更した方が全体のスループットが改善します。この辺りは Application Insights や Azure Monitor を使って日々モニタリングします。

高速にスケーリングが行われると言ってもそれは App Service Plan 単位での話なので、Function の実装が非効率な場合は思ったようにスケールされずに、コストばかりがかかるようになります。CPU とメモリを効率よく使用する実装を行う必要があります。

それぞれ 3 箇所でのスケーリング設定についておさらい程度にまとめておきます。

App Service Plan のインスタンス数

単純に実行される仮想マシンを増やすので、全体としてのスループットが向上します。Consumption は特殊なので設定自体が存在しませんが、Premium Plan と App Service Plan の場合は設定可能です。

Premium Plan

Azure Portal の Scale out を選ぶと Plan Scale out という項目で Maximum Burst*1 が設定できます。

Minimum Instances はグレーアウトして設定出来ませんが、Function App の設定から自動計算されるのでデフォルトの 1 のままで良いです。

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

注意点としては Premium Plan の場合は Maximum Burst == Function App のインスタンス数にならないことです。Function App 単位で独立したスケールが行われるので、実稼働インスタンス数はバラバラです。

App Service Plan

お馴染みの App Service Plan の場合はこれまでと同じで、手動でインスタンス数を設定するか Azure Monitor ベースの Autoscale を使うかのどちらかです。Azure Monitor ベースなのでスケールは多少遅いです。

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

既存の Web Apps と共存されることが出来るのでコストを抑えられるのがメリットです。後はタイマーで長時間稼働するような、リソースは必要だけど動的なスケーリングが必要ない場合に使うのが良いです。

Function App 単位のインスタンス数

App Service Plan で稼働させる場合は App Service Plan のインスタンス数 == Function App のインスタンス数が成り立ちますが、Premium Plan と Consumption Plan では Function App 単位で自由にスケールされるので、それぞれ個別に設定出来るようになっています。

Consumption Plan

シンプルなのが Consumption Plan の設定です。基本は 0 インスタンスから最大 200 までスケールされますが、最近になって最大インスタンス数を設定出来るようになりました。

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

従量課金なので制限してもしなくても、結局は実行数が同じなら課金は変わらないです。しかし相手が RDB などのコネクション数に上限のあるリソースの場合に、この設定を上手く使うことで問題を回避できます。

Premium Plan

Premium Plan の場合は Always Ready Instances という項目が追加されていて、設定した数だけ Function App を常に立ち上げておくことが出来ます。これは課金に影響してくるので注意深く設定しましょう。

後の設定は Consumption Plan と同じなので説明は省略しておきます。

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

これ以外にも ARM 上でのみ設定可能な項目として preWarmedInstanceCount というものがありますが、これが課金にどう影響するのかを確認中なので一旦は省略しておきます。

インスタンス単位の Function 同時実行数

最後は 1 インスタンスでの Function 同時実行数の設定です。これはアプリ側の設定になるので host.json で指定するようになっています。使用頻度が高そうなものをピックアップしています。

強く注意しておきたい点として、やみくもに増やせば良いというものではないことです。Function の実行でリソースがどのくらい使用されているのかを把握しながらやらないと意味がないです。

Durable Task

Durable Functions は実行単位として Activity と Orchestrator の 2 つが存在するので、それぞれで同時実行数を設定出来るようになっています。

大量に Activity や Orchestrator の Task を作成して Task.WhenAll で完了を待つパターンが多いはずなので、リソース使用率を確認しながら調整が必要です。

  • maxConcurrentActivityFunctions
  • maxConcurrentOrchestratorFunctions

デフォルト値が CPU 数 * 10 なので、ネットワークなどの I/O 待ちが多い処理の場合は CPU がスカスカなことも多そうです。Application Insights と組み合わせて上手く調整していきましょう。

Event Hubs

大量データを処理するものと言えば Event Hubs が代表的です。Queue に近いので 1 回で処理するバッチサイズという形で同時実行数を設定出来るようになっています。

  • maxBatchSize
  • prefetchCount

デフォルトだと maxBatchSize は 10 なので、そのまま使っていると思ったようにスループットが上がらずに悩まされそうです。CPU コア数に比例した値の方が良さそう。

この辺りの設定は Azure Functions の Binding 固有のものではなく、Event Hubs SDK に渡す値なだけなので、適切な値は Event Hubs のドキュメントを読んだ方が分かりやすいです。

Http

ちょっと意外なのは Http 周りですが、Azure Functions は大量にリクエストが投げられた場合には 429 を返すことが出来るので、スロットリングを簡単に実装出来ます。

  • maxConcurrentRequests
  • maxOutstandingRequests

ただし Premium Plan や App Service Plan ではデフォルトが無制限なので基本は全て受け切ります。

スロットリングの実装の場合は dynamicThrottlesEnabled という CPU 使用率を見て 429 を返すオプションもあるので、結構便利に使えます。

Queue Storage

何だかんだで未だに使う機会が多いのが Queue ですが、元々バッチで処理する前提の API になっています。なので Azure Functions でも batchSize を指定できるようになっています。

  • batchSize
  • newBatchThreshold

batchSize のデフォルト値は 16 で、最大でも 32 までしか設定できませんが、これは Queue API の制限なので増やすことは出来ません。

Azure Functions の実装としてはバッチで受信した未処理メッセージ数が newBatchThreshold を下回れば、新しくメッセージをバッチで取得するので、最大同時実行数は 32 + 16 = 48 になります。この辺りの設定はちょっとわかりにくいです。

Service Bus

最後は Service Bus ですが、最近は個人的に使わなくなってきたのでさらっと行きます。基本はこれも Queue に近いので、設定名は違いますがやっていることは同じです。

  • maxConcurrentCalls
  • maxConcurrentSessions

重要なのは maxConcurrentCalls の設定で、デフォルトは 16 なので処理に合わせて変更すれば、より良いスケールが行えるでしょう。maxConcurrentSessions はデフォルト値が大きいのでそのままで良いです。

Durable Function 以外は CPU コア数によって同時実行数が変化しないので、2 コア以上の Tier を使ってもスループットが上がりにくいのは注意しておいた方が良いです。

そういったことを把握するためにも Application Insights と Azure Monitor が必須というわけです。

*1:要するにスケーリング時の最大インスタンス数のこと