しばやん雑記

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

Build 2023 で発表された Azure App Service のアップデート

Build 2023 では珍しく App Service のアップデートもいくつか発表されました。これまでは Build のようなイベントと関係ないタイミングでアップデートが発表されることが多かったので意外でした。

とはいえ Build 前に発表されたものが半分ぐらいと、新規のものが残り半分という感じです。

現地でも App Service の Q&A セッションがあり、そこでも以下のようにアップデート内容が公開されていたのですが、どれもちょっとずつ内容が違っていて多少混乱します。特に診断周りの AI-powered 対応は Q&A セッション内でしか公開されていない気がします。

Expert ブースで表示されていた同じスライドには診断周りの記述がなかったので、若干不安定です。

今回発表されたアップデートには App Service でアーキテクチャを考える上でかなり重要なものもあるため、例によって重要だと考えている点についてアップデート内容をまとめておきます。

個人的には診断ツールが AI によって強化されるのが楽しみですが、今のところ有効化されている気配を感じないのでまた別途確認しようと思っています。

何回も書いていますが App Service の診断ツールは現時点でも強力なのでサポートに聞く前に開きましょう。

Premium V3 の新しい SKU P0v3 / P*mv3 が GA

App Service の中でも最新の SKU となる Premium V3 の中でも、非常にコストパフォーマンスに優れる P0v3 とメモリ最適化された P*mv3 が GA しました。

先月の時点で GA はしていましたが、発表された時から比べると利用可能なリージョンはかなり増えました。現在では Japan West で P0v3 と P*mv3 が利用可能になっています。

以下のように Azure CLI を叩くと P0v3 と P*mv3 に対応したリージョンを取得可能です。

az appservice list-locations --sku P0v3 --query "sort([].name)"
az appservice list-locations --sku P1mv3 --query "sort([].name)"

まだ Japan East には P0v3 と P*mv3 に対応した Scale unit がデプロイされていませんが、ベースとなっている Dasv5 は利用可能になっているので将来的には使えるようになるはずです。

Automatic Scaling の Azure Portal サポートがプレビュー

名前から想像するのは難しいですが Azure Functions のように HTTP トラフィック数によってオートスケーリングする機能です。この機能は割と昔から使えるようになっていたのですが、ようやく Azure Portal から設定できるようになりました。これまでは Azure CLI で設定する必要があったのでかなり手間でした。

機能については Ignite 2022 の時にも書いているので、詳細はこちらも参照してください。

しっかりと検証したことは無かったので、今回の Build では Demo セッションとして "Automatic scaling for Azure App Service web apps" があったので参加してきました。

実際に Automatic Scaling を設定した App Service に対して Azure Load Testing を利用して負荷をかけるというものでしたが、自動的にオートスケールが実行されていく様子を確認できました。利用するには Premium V2 か V3 の App Service Plan が必要になります。

Always On を無効化する必要があるようなので初回リクエストのパフォーマンスが不安ですが、裏に Pre Warm 済みインスタンスが存在するので問題ないようです。

スケールアウトしたインスタンス数は Live Metrics を使えとありますが、Functions Premium と同じであれば Azure Monitor からも確認できるはずです。

.NET 8 / Node.js 20 / Python 3.12 / PHP 8.3 の Early Access が間もなく

最近は定番となりつつ新しい言語バージョンの Early Access での提供ですが、例によって .NET 8 はプレビュー段階から提供されることが確定しています。それ以外には Node.js 20 がこれまでの傾向から行くと LTS ちょっと前のバージョンから利用できるようになるはずです。

Linux 版では Python 3.12 や PHP 8.3 の Early Access も提供されるようなので、組み込みの Runtime Stack を利用している場合にはアップデートを検討してください。

複数の App Service Plan を 1 つの Subnet へ統合サポートが間もなく

これまで 1 つの Subnet には 1 つの App Service Plan しか統合できませんでしたが、間もなく 1 つの Subnet に複数の App Service Plan を統合できる機能がプレビューとなります。

少し前にも Regional VNET Integration 周りは 1 つの App Service Plan が 2 つの Subnet に統合できるように改善されましたが、今回プレビューが予告された 1 つの Subnet に複数の App Service Plan を統合できるようになると、Subnet 設計や Service Endpoint の設定が大幅に簡単になります。

まだ情報が全く出てきていないので制約などは不明ですが、これまでの傾向を見ていると利用するには VMSS Worker で動いている App Service Plan が必要になる気がしています。例によって最初は Premium V3 を指定してデプロイすることが重要なことは変わりません。

受信 HTTP トラフィックの IPv6 サポートが予定

現在は azurewebsites.net は IPv4 しか返しませんが、今後 IPv6 にも対応することが発表されました。既存の App Service でも IPv6 が有効化されるのか、それとも新規にデプロイする必要があるのかなど気になる点は多いのですが、現時点では続報を待ちたいと思います。

個人的には Front Door を前段に置くことが多く、Front Door は既に IPv6 に対応しているので有効化する機会は少ない気がしています。

Minimum TLS Cipher Suites 設定の Azure Portal サポート

Ignite 2022 のアップデート時に試したように、Frontend が YARP + Kestrel にアップグレードされたことで最小の TLS Cipher Suites を指定できるようになりましたが、これまでは ARM レベルでの設定が必要でした。今回のアップデートで Azure Portal から設定できるようになりました。

現時点でもプレビュー扱いにはなっていますが、機能自体はプロダクションで利用できるレベルに達しているようです。今年後半に Frontend が Windows Server 2022 にアップグレードされ、それにより TLS 1.3 へのサポートが追加されたタイミングで GA する予定のようです。

今回で YARP + Kestrel で新しく構築された Frontend は Windows で動いていることが判明しましたね。

TLS 1.3 と E2E の TLS 暗号化サポートが間もなく

先程も書きましたが、ようやく TLS 1.3 サポートが App Service にもやってきます。現在は Application Gateway のみ TLS 1.3 をサポートしているので、App Service での対応は喜ばしいです。そして Front Door でも TLS 1.3 をサポートしてほしい気持ちが高まっています。

YARP + Kestrel は以前から TLS 1.3 に対応していましたが、Windows Server の対応が 2022 からで Frontend のアップグレードが必要になるため時間がかかっていたようです。

そして同時に Frontend と Web Worker 間についても TLS で暗号化されることが予告されました。実は Frontend と Web Worker 間はこれまで非 TLS で通信が行われていましたが、完全に閉じたネットワーク内なのでパフォーマンスを取ったという感じでしょう。

さらっと流していますが、Frontend と Web Worker の関係については YARP + Kestrel への移行記事に図が載っているので、こちらの記事も参考にしてください。

Frontend と Web Worker 間が TLS で暗号化されることで、更にセキュリティを高めることが出来ますし、あくまでも私の想像にはなりますが E2E で HTTP/2 が通るはずなので gRPC 周りの対応が謎の Proxy ポート経由ではなく、素直に利用できるようになる可能性があると考えています。

カスタムエラーページがプレビュー

User Voice で常にトップに上がっていた App Service が返すエラーページのカスタマイズですが、Frontend が YARP + Kestrel にアップグレードされたことでついに対応されました。対応するステータスコードは 403 / 502 / 503 の 3 つになり、HTML をアップロードする形で設定します。

簡単に試してはいますが、403 については比較的簡単に動作を確認できていて、IP Restriction の設定を行うだけの非常にシンプルな確認方法です。難しいのが 502 と 503 で現時点では動作を確認できていません。

503 については App Service を 1 インスタンス構成で再起動すると返されるはずなのですが、シンプルな 503 ページが返ってくるだけでした。また時間のある時に確認しようかと思います。

Build 2023 で発表された Azure Cosmos DB のアップデート

先日開催された Microsoft Build 2023 では Azure Cosmos DB の新機能が数多く公開されました。ぶっちゃけかなり大規模な機能追加となっているので、気になる機能は個別に検証しつつまずは全体として NoSQL API に関連するアップデートをまとめることにします。

とはいえ基本は Cosmos DB Blog で公開された以下の記事を参照してもらえれば問題ないです。

この中で一番使われているはずの NoSQL API に関係するアップデートは以下になります。GA / Preview 合わせてかなり多く、しかもそれぞれがかなり重要度が高い機能になっています。特に Burst Capacity は今すぐ有効化する価値があります。

特に挙動が気になる機能については個別に検証してブログにまとめる予定ですが、とにかく機能が多いのでまずはざっくりとアップデートをまとめます。

Burst Capacity が GA

Cosmos DB の Provisioned Throughput を利用している場合に、直近 5 分間のウィンドウで使用しなかった RU を貯めておいて、必要な際に消費する Burst Capacity が GA しました。

Autoscale よりも高速かつコストに影響しない形で利用できるので、無条件で有効化してよい機能だと考えています。アプリケーションへの影響は 429 が発生する頻度が減るといった程度なので、基本的にデメリットはないと考えてよいです。

既にプレビューの際に検証した結果を以下のエントリでまとめていますので、こちらも参照してください。GA したからと言って挙動が変わっているわけではありません。

Autoscale との組み合わせも効果的なので、Cosmos DB を Provisioned Throughput で利用している際には忘れずに有効化しておきたい機能です。

Hierarchical partition keys が GA

これまで Cosmos DB の Container では 1 つのパーティションキーのみ指定可能でしたが、それを最大 3 つまで指定できる機能がこの階層パーティションキーです。マルチテナントのシナリオで活躍します。

これまで単一のパーティションキーでは論理パーティションの上限 20GB に達する可能性がある、あるいはホットパーティションとなる可能性があるケースでは、合成パーティションキーを作成して分散させる戦略を取ってきましたが、階層パーティションキーを使うとその必要なくさらに効率的にクエリを実行できます。

合成パーティションキーでは一部の値だけ指定した場合のクエリはクロスパーティションで実行されるため、物理パーティションの数によっては効率が悪くなりますが、階層パーティションキーではプリフィックスが指定された場合には適切な物理パーティションにルーティングされるため、効率よくクエリが実行可能です。

このあたりの詳細は公式ドキュメントのテーブルがわかりやすいので目を通しておくと良いです。

実現できることは合成パーティションキーとほぼ同じですが、データを重複して持たせる必要がないのでサイズの削減に繋がり、更にルーティングも最適化されるので今後は合成パーティションキーを使った設計は必要なくなりそうです。

1TB Serverless Contianer が GA

Cosmos DB の Serverless では 50GB と 5000 RU が上限となっていましたが、1TB と 20000 RU まで利用できるようになりました。これでデータサイズがかなり大きいケースでも Serverless を利用することが出来るようになりました。

Serverless では物理パーティション当たりの RU が最大 5000 RU から 1000 RU までデータサイズが大きくなるにしたがって下がっていくため、Provisoned Throughput よりもパーティションキーの設計が重要になってきます。階層パーティションキーなどを使って効率よく分散する必要があります。

継続的バックアップの 7 日間モードが GA

これまで継続的バックアップは 30 日間保持するモードだけ GA していましたが、無償で利用可能な 7 日間保持するモードが GA しました。Analytical Store が必要なケース以外では継続的バックアップに切り替えるのを強くお勧めしています。

これまでの定期バックアップではリストアするためにサポートに連絡する必要がありましたが、継続的バックアップでは任意の時間のデータにユーザー主導で戻すことが出来るので使い勝手が格段に向上しています。

今回 GA した 7 日間保持するモードは無償なので移行して損はありません。

Log Analytics での Transformation (DCR) サポートが GA

Log Analytics に対して Cosmos DB の診断ログを送信する際に、テーブル単位で KQL を使ってフィルタリングやカラムの絞り込みを行う機能が GA しました。元々 Log Analytics に用意された Data Collection Rule と Transformation が Cosmos DB のログテーブルでも利用可能になったというアップデートです。

Cosmos DB の診断ログはパフォーマンス調査に非常に役立つのですが、その反面データ量が非常に多くなってしまいました。特に Cosmos DB のような低レイテンシなデータストアは高頻度でアクセスされるためログの量も膨大です。今回 GA した Data Collection Rule を使った Transformation を使うと KQL を利用して必要なデータのみフィルタリングして保存可能です。

例を挙げると Cosmos DB で Change Feed を利用している場合には leases Container に対して大量のアクセスが発生しますが、この Container に対するログは保存する必要がありませんので、Data Collection Rule を使うことで除外できます。

全てのバージョン、削除対応の Change Feed がプレビュー

名前が分かりにくいですが、以前は Full Fidelity Change Feed と呼ばれていた機能となります。4 年近く Private Preview のままでしたが、今回の Build でようやく Public Preview に到達したようです。

現在の Change Feed はデータを複数回変更しても、最後に変更された結果のみ取得出来るという Eventual な動作となっていますが、今回 Public Preview となった動作モードを使うと全ての変更と削除についても Change Feed として取得できるようになります。

この Change Feed の動作モードについては Full Fidelity と呼ばれていた頃に検証したことがあるので、以下の動画アーカイブを見てもらえればと思います。

現在の SDK では Java のみ Change Feed Processor でも新しいモードが使えるようになっています。.NET では Change Feed の Pull Model を利用する必要があるため、本格的に利用するには少しハードルが高いです。

正直なところ Azure Functions の CosmosDBTrigger が対応するまでは使いにくい状況が続きます。

NoSQL API での Materialized views がプレビュー

これまでも Azure Functions と Cosmos DB の Change Feed を利用して Materialized views を作るアーキテクチャを多く作ってきたと思いますが、簡単なクエリを書くだけで任意の Materialized views を作る機能がプレビューになりました。同様の機能は Azure Cosmos DB for Apache Cassandra でも Public Preview として提供されていました。

コードを書く必要がなく Container を作成するタイミングで SQL を使って Materialized views のスキーマを定義出来ますが、SQL で表現できることのみ実現可能という形になるため、Azure Functions を使った場合よりも柔軟性は低くなっています。

シンプルな Materialized views が必要なケースでは非常に便利ですが、追加でコンピューティングリソースのコストがかかりそうなので価格が気になるところです。実装は Change Feed の Pull Model がベースとなっているので、同期ラグがどのくらいになるのか注意ですね。

Computed properties がプレビュー

Materialized views に少し近いのですが、既存項目のプロパティを利用して実行時に新しいプロパティを定義できる機能がプレビューになりました。これまで C# 側の読み取り専用プロパティを使って、既存項目から新しい値を作ることをよく実装しましたが、Cosmos DB 側で定義可能になりました。

Computed properties も SQL を使って値の変換を定義するため、クライアント側で実装する場合に比べて実現できる変換は限界があるのですが、変換した値をインデックスの対象にできるというのが大きな特徴です。

デフォルトで Computed properties はインデックス対象になっていませんが、明示的にインデックス対象と指定することで変換後の値を使ってクエリを高速化できます。

同一アカウントへの PITR がプレビュー

これまで継続的バックアップを使って Point In Time Restore を実行する際には、新しいアカウントに対してのみ実行が可能という制約が付いていましたが、同一アカウントに復元する機能がプレビューになりました。

新しいアカウントに復元すると必然的に接続文字列の変更など多くの作業が発生してしまいますが、同一アカウントに復元出来ると Container 名の修正だけで済むため利便性が高いです。

これまで以上に継続的バックアップを利用する意味が出るため、早めに切り替えるのがおすすめです。

.NET / Java SDK で OpenTelemetry / Application Insights 統合がプレビュー

Cosmos DB 側のアップデートではないですが、ようやく .NET と Java の SDK で OpenTelemetry と Application Insights を利用したテレメトリの送信がプレビューになりました。これまでも Gateway を利用していれば Application Insights SDK によってデータが収集されていましたが、Direct の場合は全く収集されないという課題がありました。

今回のアップデートでは SDK レベルでテレメトリの送信が組み込まれているため、接続モードに依存せず常に利用が可能となりました。実際に Application Insights への送信を試しましたが、Direct を使っていても依存関係呼び出しが記録されていました。

実行した SQL は収集できていないようですが、今後のアップデートに期待したいです。

Azure App Service の Premium V3 に新しいインスタンスサイズが追加されたので試した

先日 Azure App Service の Premium V3 に新しいインスタンスサイズが追加されたことが発表されました。Premium V3 は Premium V2 よりも大きいインスタンスサイズ設定となっていましたが、サイズが拡充されたことでコストパフォーマンスと使い勝手が向上しました。

特に P0v3 のリリースによって Premium V2 を使う必要性が消滅したように感じています。新しく作成する際には必ず Premium V3 を選択しておけばまず失敗しません。

さて、今回 Premium V3 の拡充と Isolated V2 についてブログで発表されましたが、Isolated V2 はそこそこ前にリリースされていたので今回は Premium V3 の追加サイズについてのみ書きます。

今回追加された Premium V3 のサイズは以下の通りです。Premium V3 は 2 vCore から開始されていましたが、P0v3 の追加で 1 vCore から始めることが出来るようになりました。それ以外はメモリに最適化されたサイズになりますが、Premium V3 には P4v3 / P5v3 は存在していないので 16 vCores 以上のサイズが追加されたとも考えることが出来ます。

  • P0v3
    • 1 vCore / 4GB RAM
  • P1mv3
    • 2 vCores / 16GB RAM
  • P2mv3
    • 4 vCores / 32GB RAM
  • P3mv3
    • 8 vCores / 64GB RAM
  • P4mv3
    • 16 vCores / 128GB RAM
  • P5mv3
    • 32 vCores / 256GB RAM

当然ながら上位のサイズは高いのですが、P0v3 がリージョンによっては P1v2 よりも安くなるケースがあるみたいなので、P1v2 を使っている場合にも価格を確認して移行を検討してもよいと考えています。

Premium V3 を選ぶと必ず VMSS ベースのスタンプに割り当てられるという事情もあるので、本番向けの App Service で利用する際には Premium V3 が必須なことには変わりありませんし、今回のアップデートでその傾向は強化されたといえます。

P0v3 / P*mv3 な App Service Plan を作成する方法

Premium V3 が公開された時と同様に、現時点では特定のリージョンのみで利用可能になっているため、作成する前に Azure CLI を使って利用可能なリージョンを調べるのが良いです。

az appservice list-locations --sku P0v3

例によって北米とヨーロッパに優先してデプロイされていますが、近いうちに東日本リージョンでも使えるようになると思います。西日本は未だに Premium V3 自体が使えないため、先日発表されたリソース拡充が完了するまで待つ必要がありそうです。

対応しているリージョンが分かってしまえば、後はいつも通り Azure Portal からリージョンを選択すれば新しいインスタンスサイズが選べるようになります。

新しいインスタンスサイズの ACU に注釈が付いていますが、その理由は後述することにして先に進みます。

ちなみに現時点では既存の Premium V3 な App Service Plan からのスケールアップは出来ないようです。この理由も後述しますが、暫くは Premium V3 を使う時のように作成時に P0v3 や P*mv3 を指定して作成してから、必要なサイズにスケーリングする必要があります。

最初に P0v3 や P*mv3 を指定して作成してしまえば、全てのサイズにスケーリングが可能になります。

P0v3 / P*mv3 は AMD EPYC ベースの VM が利用されている

今回追加された新しいインスタンスサイズを選ぶと、これまでの Premium V3 とは異なり AMD EPYC 7763 ベースの VM が使われていることに気が付きました。App Service に入っている coreinfo を使って CPU の情報を取得すると一目瞭然です。

AMD EPYC 7763 が使われている VM サイズは Dasv5 となるので、これまでの Premium V3 で使われていた Dv4 から 1 世代新しくなっています。ちなみに Dv4 では Intel Xeon の各種世代が使われています。

Premium V3 で使われている CPU の種類と VM サイズを簡単にまとめると以下の通りです。公式ドキュメントで公開されている ACU や性能テストの結果を確認すると、Dasv5 の方が Dv4 よりも性能が良いので Minimum 195 ACU に注釈が付いているようです。

  • P1v3 / P2v3 / P3v3
    • Intel Xeon ベースの VM (Dv4)
  • P0v3 / P*mv3
    • AMD EPYC ベースの VM (Dasv5)

面白いことに P0v3 や P*mv3 が使える App Service Plan を P1v3 など既存のサイズに変更すると、Intel Xeon ベースの CPU に変わるので Scale unit に Intel と AMD が共存しているようです。従って既存のサイズの場合は ACU も変化しません。

このように全く新しい VM サイズが使われているため、Premium V3 と VMSS Worker が導入された時のように既存の App Service Plan は P0v3 や P*mv3 に変更することが出来ません。

Premium V3 や VMSS Worker の時には、稀に直近で作った App Service Plan が P0v3 や P*mv3 入りの Scale unit だった場合に、そのままシームレスに変更することが出来ましたが、今回はかなり望みは薄いでしょう。

将来的に Scale unit のアップデートが行われて、既存の App Service Plan でも P0v3 や P*mv3 が使えるようになる可能性はありますが、使いたい場合はリソースグループから新しく作るのが手っ取り早いです。

P0v3 は 1 vCore / 4GB RAM と言いつつ実態は異なる?

今回のサイズ追加では P0v3 が一番メリットが大きいと思うので優先的に試してみましたが、デプロイして調べると何故か 2 vCores / 8GB RAM として認識されていました。

これが現時点での不具合なのか、それとも内部的にスロットリングがかかっていて 1 vCore / 4GB RAM 以上は使えないようになっているのかは分かりませんが、P0v3 は P1v3 の半額までにはなっていないので、仕様の可能性もありそうです。

Azure Portal の対応状況は途中なので注意

今回の新しい追加オプションは Premium V3 から SKU 名が変更されている関係上、Azure Portal では Premium V3 で使えるはずの機能がグレーアウトしているケースが多くみられます。

例えば Regional VNET Integration 周りはグレーアウトしてしまい、Azure Portal 上からは設定出来ません。

これまでの傾向から、暫くすると Azure Portal のアップデートが行われて正常に扱えるようになりますが、それまでの間は Azure CLI や Bicep / Terraform といった ARM を直接利用するツールを使う必要があります。

Packer を利用して GitHub Actions から Azure Compute Gallery へ VM Image を作成する

前までは最近は Azure VM を使うことがほぼ無くなっていたのですが、最近は GPU インスタンスが必要なケースで VMSS の利用を検討することが多くなってきたので、VM Image のカスタマイズを自動化する際に役立つ HashiCorp Packer を触っておきました。

Azure に限ると Packer がベースとなっている Azure Image Builder が用意されていますが、まずはベースとなっている Packer を直接触っておいた方が理解が深まるはずです。

公式ドキュメントにも Packer を使って VM Image を作成する手順が紹介されているので、基本はこのドキュメントを見ながらやればよいのですが、全体的に古い感じだったので定義の参考程度に留めておきました。

Packer が行っていることはテンポラリの VM を作成し、SSH などで接続しスクリプトを実行、完了後に VM を停止して VM Image をキャプチャの 3 ステップとシンプルです。当然ながら自動化に最適なので Packer と GitHub Actions を組み合わせて自動化するのが良いです。

ここから先は Marketplace で公開されている VM Image をベースにして、Azure Compute Gallery へカスタマイズした VM Image を作成した手順となります。

Marketplace で公開されている Image の情報を調べる

まずはベースとなる VM Image を決めて、必要な情報を集めるところがから始めました。Azure Portal からだと探しにくいのが VM Image の情報ですが、一応 Usage Information を開くと少しだけ確認出来ます。

Plan ID は SKU と同じになるのですが、いくつか確認したところ信頼できないので別途取得します。

この辺りの情報を基にして、以下のドキュメントで紹介されているように Azure CLI を使って調べると効率が良さそうでした。油断すると ARM64 向けや Gen 1 VM を勧められるので気を付けましょう。

今回は Ubuntu Server 22.04 LTS のイメージを使うので、以下のコマンドを叩きこんで SKU を取得します。

az vm image list-skus --location japaneast --publisher Canonical --offer 0001-com-ubuntu-server-jammy --query "[].name"

実行すると SKU の一覧が返ってくるので、その中から x64 かつ Gen 2 VM 向けの SKU を選べばよいです。

これでベースとなる VM Image の情報は全て集まったので、何処かにメモしておきます。後で Packer の定義ファイルを作成する際に必要となります。

次は作成した VM Image を保管する先となる Azure Compute Gallery と VM Image Definition を作成しておきます。Azure Compute Gallery は適当な名前を付けて作成すれば問題ありません。

これまでの Managed Image とは異なり、Azure Compute Gallery に VM Image を作成するには先に VM Image Definition を作成しておく必要があるのと、Packer では自動生成してくれないので注意が必要です。

VM Image Definition は Azure Portal から作成できるので、以下のように必要な情報を入力して作成すれば良いです。OS の種類と VM の世代、一般化・特殊化といった項目は選択に注意が必要です。

バージョン入力もありますが、そこは Packer が自動で作成するので空っぽのままで問題ありません。

これで VM Image Definition の作成は完了なので、後は Packer の定義ファイルを作成して実行するだけです。

Packer の定義ファイルを作成する

肝心な Packer の定義ファイルですが JSON と HCL2 で書くことが出来ますが、Terraform を使ったことがある人なら HCL2 で書いた方が楽だと思います。VS Code に HashiCorp HCL 拡張を入れるとシンタックスハイライトも動作しますし、Terraform と同様に Packer 自体にフォーマット機能も用意されています。

Azure VM に関係する設定値は全て以下のドキュメントにまとまっていますが、かなりプロパティが多いので Azure Compute Gallery に VM Image を作成する最小限の定義だけ用意することにしました。

今回作成した Packer の定義ファイルは以下のようになります。本来であればサブスクリプション ID などは環境変数などから与えますが、今回は簡略化のために直接埋め込みます。

Ubuntu Server 22.04 LTS の VM Image を使って、簡単なコマンドだけ実行して一般化を行ってから Azure Compute Gallery に VM Image を作成するという流れです。provisioner はサンプルのまま使っていますが、ファイルのアップロードなど高度なカスタマイズが可能です。

source "azure-arm" "autogenerated_1" {
  use_azure_cli_auth = true

  subscription_id = "<SUBSCRIPTION_ID>"
  tenant_id       = "<TENANT_ID>"
  image_publisher = "canonical"
  image_offer     = "0001-com-ubuntu-server-jammy"
  image_sku       = "22_04-lts-gen2"
  os_type         = "Linux"
  location        = "Japan East"
  vm_size         = "Standard_D2s_v3"

  shared_image_gallery_destination {
    subscription         = "<SUBSCRIPTION_ID>"
    resource_group       = "rg-packer-demo"
    gallery_name         = "galpackerdemo"
    image_name           = "vmi-packer-demo"
    image_version        = "1.0.0"
    replication_regions  = ["Japan East"]
    storage_account_type = "Standard_LRS"
  }
}

build {
  sources = ["source.azure-arm.autogenerated_1"]

  provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline = [
      "apt-get update",
      "apt-get upgrade -y",
      "apt-get -y install nginx",
      "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
    ]
    inline_shebang = "/bin/sh -x"
  }
}

Azure Compute Gallery に VM Image を作成するためには shared_image_gallery_destination ブロックが必要になりますが、ここだけは旧名称になっているので注意が必要です。前述したように VM Image Definition は作成してくれませんので、未作成の場合はエラーとなります。

実行するために重要な Azure の認証情報ですが、公式ドキュメントでは Service Principal を使う方法が紹介されているのですが、実際には Azure CLI を使うのが一番楽なので use_azure_cli_auth を追加しておきます。それ以外にもいくつかの方法がありますので、公式ドキュメントを参照してください。

ちなみに Packer は Azure VM を作成するのにテンポラリのリソースグループから作成するため、サブスクリプションレベルでの権限が必要となります。地味にはまるポイントなので注意してください。権限周りの設定が完了すれば packer build ***.pkr.hcl を実行すればローカルで動きます。

Terraform とは異なり Packer はまだ OpenID Connect に対応していないようなので、GitHub Actions から使う場合には Azure CLI 経由で使うことになります。

GitHub Actions で packer build を実行する

ローカルで動かしていた packer build コマンドをそのまま GitHub Actions のワークフローで実行するようにすれば、問題なく Packer を使った VM Image の CI/CD が行えるようになります。

実行に必要な認証情報は強い権限が必要になるので OpenID Connect を使って守っておきたいです。

ワークフロー自体は難しいことが全くないため、サクッと紹介だけしておきます。Packer のセットアップ自体は HashiCorp から専用の Action が公開されているので、これを使えば簡単に終わります。

後は Azure CLI で OpenID Connect を使ったログインを行えば、packer build が動くようになります。

name: Packer Build

on:
  push:
    branches: [ "master" ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v3

      - name: Setup HashiCorp Packer
        uses: hashicorp/setup-packer@v2.0.0
        
      - name: Azure Login
        uses: Azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Run packer build
        run: packer build ubuntu.pkr.hcl

作成したワークフローを実行すると、以下のようなログと共に VM の作成から Azure Compute Gallery への VM Image の作成まで自動的に行われます。VM Image 作成後の各リソースの削除に割と時間がかかるので、何か高速化の余地がありそうな気はします。

実行完了後に Azure Compute Gallery を確認すると、指定したバージョンで VM Image が作成されていることが確認出来ます。本来ならバージョンはタグやコミットハッシュなどを上手く組み合わせたいところです。

後はこの VM Image を利用して VMSS などを作成するだけです。VMSS はスケーリングが非常に簡単なのが特徴なので、Azure Compute Gallery に必要な処理が完了済みの VM Image を作っておくと有利です。

補足 : Marketplace の購入プランが必要な場合

Marketplace には購入プランが必要な VM Image が公開されていますが、その VM Image をベースにする際にはプラン情報を Packer や VM Image Definition にも追加する必要があります。

Packer のドキュメントにあるように plan_info ブロックを追加して定義します。

購入プランについては以下のドキュメントを参照してください。必要なプラン情報を取得する方法から説明が記載されているので、取得した情報を Packer 定義と VM Image Definition に設定します。

VM Image Definition に設定を忘れた場合には、VM Image は作成されても起動できないイメージが完成するため、地味にはまることになります。あまり使うことはないかも知れませんが、知っておいて損はないです。

Azure App Service の Disaster Recovery Mode が 2025/3/31 で廃止されるらしい

ここ数日の間に App Service の Disaster Recovery Mode が 2025/3/31 で廃止されるというメールが大量に届いていると思います。こういうメールが届くと少し身構えてしまうものですが、そもそも App Service の Disaster Recovery Mode ってなんやねんという気持ちです。

個人的には App Service の Disaster Recovery Mode について知りませんでしたが、App Service をデプロイしているリージョンに障害が発生した場合には、通常は Standard 以上の Tier が必要なところ全ての Tier で有効になるという機能らしいです。

結局のところはバックアップ・リストアの機能でしかないですし、最近は App Service のリージョン障害もかなり減っているので使ったことばかりか存在すら知らない機能でした。

DR が必要なシナリオではリージョン障害が発生してから、別リージョンに Azure Portal から再デプロイするという手順はそもそも筋が悪いので、最新のドキュメントに従って Front Door を使って最初から 2 つ以上のリージョンにデプロイしておくのが良いです。

あるいは CI と IaC で別リージョンに素早くデプロイ可能な状態にしておくことが推奨されます。

DR という観点とは異なりますが、最近では Availability Zone を有効化するとリージョン内の 3 つのデータセンターにデプロイ出来るので、可用性を簡単に高めることが出来ます。

Availability Zone を有効化する

既に App Service の Availability Zone については何回も書いているので深く説明しませんが、最低でも 3 インスタンスが必要になるのでコスト的な負担は大きくなる点だけが注意です。それ以外は通常の App Service と機能やデプロイ方法は全く同じなので非常に簡単です。

現実的には東日本の AZ が全てダメになるようなケースはかなり可能性が低いので、複数リージョンへのデプロイが必須な場合以外は Availability Zone の方が管理のしやすさからもお勧めです。

IaC (Bicep / Terraform) を導入する

複数リージョンで常にインスタンスをデプロイしておくとアクティブ・アクティブの場合以外は無駄なコストがかかるので、IaC を使って素早く同一のリソースセットを必要に応じて任意のリージョンにデプロイ可能にしておく方法もお勧めです。

Bicep や Terraform を使うと実行時のパラメータが使えるので、その際にリージョンを渡せるようにしておけば、データだけレプリケーションは必要になりますが数分で全く同じ構成で立ち上げ直すことが出来ます。

しっかりと CI も整備しておけば、新しいリージョンへのアプリケーションのデプロイもあっという間に完了するので、App Service のバックアップ自体必要なくなります。コンピューティングリソースは状態を持たず何時でも捨てられる状態にしておくのが肝ですね。

Azure Cosmos DB の Burst Capacity (Preview) を試した

去年の Build で発表されていた Cosmos DB の Burst Capacity ですが、当時は Private Preview に近い形だったのでサインアップが必要で検証をしていなかったのですが、最近何となくドキュメントを確認するとサインアップの記述が消えていたので試しました。

Burst Capacity について簡単に説明すると、各物理パーティションが 5 分間での余った RU/s を貯めておいて、必要なタイミングで貯めておいた RU を上乗せする機能です。貯めておいた RU は物理パーティション単位で最大 3000 RU/s で消費が可能です。

Cosmos DB を使う上で避けられない 429 を Burst Capacity を利用すると、かなりのケースで回避できるようになると期待しています。発表された当時から Burst Capacity を利用するとかなり Cosmos DB の利用コストを最適化出来ると考えていたので、ようやくですが気軽に試せるようになって嬉しいです。

ドキュメントも多少変わっているはずなので、もう一度目を通しておくと良いです。まだ Preview なことには変わりないので、GA の際には動作が変わる可能性がありますし、GA しない可能性もあるかもしれません。

Autoscale は 1 時間単位の課金となってしまうので、Burst Capacity が秒単位の部分をいい感じに埋めてくれると期待しています。今年の Build で GA になって欲しいですね。

どのくらい前からサインアップが不要になっていたのかは把握していませんが、今は Azure Portal の Features に Burst Capacity と Partition Merge が表示されているので、条件が整っていれば自由に有効化出来ます。

当然ながら Provisioned Throughput の場合のみ Burst Capacity は有効になります。Serverless にはアイドル状態の RU という概念が存在しないので当然です。

Autoscale と組み合わせると 10 倍の Autoscale 上限に対して、更に Burst Capacity によって最大 3000 RU/s が上乗せできるため、GA した際には Autoscale + Burst Capacity で RU の調整は必要なくなりそうです。詳細は FAQ に一通りまとまっているので目を通しておくのが良いです。

Burst Capacity の上限は 3000 RU/s となっていますが、実際には物理パーティションが関わってくる点は注意です。1 つの物理パーティション当たり 3000 RU/s 以上のスループットが割り当てられている場合には Burst Capacity は利用されないため、将来的には物理パーティションの分割が最適化で必要になるかも知れません。

説明はこのくらいにして、実際に Burst Capacity を有効化して動作を確認していきます。Azure Portal から Features にある Burst Capacity (Preview) を有効化するだけで使い始めることが出来るので、Cosmos DB SDK のバージョンアップといったようなコード側での変更などは必要ありません。

検証に使用する Container は Provisioned Throughput で 400 RU を割り当てておきました。アクセスが全くない場合には、計算上は 5 分間で貯められるアイドル RU は 120000 RU にもなるはずです。それを最大 3000 RU/s で消費するので最短で 40 秒後には使い切ることになります。

Burst Capacity の有効化と Container の作成が終われば、適当なコードを書いて Cosmos DB に対して書き込み負荷を掛けるだけです。Cosmos DB SDK の Bulk を利用すると効率よく RU を使い切ることが出来ます。

負荷を掛けた後は Azure Monitor を使って RU の消費を確認しますが、Burst Capacity で消費された RU を確認するには "Total Request Units (Preview)" を使う必要があります。このメトリックには CapacityType が追加されているので、Split すればすぐに把握できます。

まずは軽めに Bulk で書き込みを行った際のメトリックですが、ProvisionedCapacity よりも BurstCapacity の方が数倍多く消費されていることが確認出来ます。これは Burst Capacity を有効化していなかった場合は 429 が大量に出ていたことを表しています。

画像は貼りませんが同じ時間帯の 429 の発生を確認したところ 0 となっていました。計算上でも Monitor 上でも Burst Capacity 的にはまだまだ余力があることが分かりますね。

今度はもっと大量のデータを Buk で書き込みを行った際のメトリックです。途中で 429 エラーとなってしまったのでメトリックに 0 の時が出てしまいましたが、時間が経つにつれて BurstCapacity が減っていき、逆に ProvisionedCapacity が増えていることが分かります。まだ Preview なので具体的な挙動についての考察はあまり意味がないのですが、Provisioned Throughtput を使い切っていなくても Burst Capacity を利用しているように見えます。この辺りは GA のタイミングでの説明を期待したいですね。

最終的には BurstCapacity が 0 になり、ProvisionedCapacity のみとなりました。これは Burst Capacity によって貯めておいた RU が完全に無くなってしまったことを表しています。今回は途中でアイドル状態を作ってしまったので計算が合いませんが、本来なら BurstCapacity の合計値は 120000 となるはずです。

ここまでの結果から分かるように、Burst Capacity は 3000 RU/s 以下の物理パーティションを持つ Container が存在する場合には、有効にしない方が圧倒的に損するというレベルの機能です。デフォルトで有効化しておいてほしい機能なので、GA の際にはオプトアウトになることを期待したいです。

Azure App Serivce の Regional VNET Integration の制約が緩和されて更に便利に

App Service で個人的に一番重要な機能は Regional VNET Integration だと考えていて、この機能の追加によってマルチテナントの App Service でもセキュアなアプリケーション構成が組めるようになりました。

Regional VNET Integration については既に何回も書いているのですが、リリースから 3 年経過した今では使わないケースの方が少なくなってきました。ほぼ必須の重要な機能となってます。

久し振りに該当のドキュメントを確認していたのですが、以下のように気になる記述を見つけました。これまで Regional VNET Integration は 1 つの App Service Plan に対して 1 つの Subnet への統合しか出来なかったのですが、それが 2 つの Subnet まで統合可能になったようです。

You can't have more than two virtual network integrations per App Service plan. Multiple apps in the same App Service plan can use the same virtual network integration. Currently you can only configure the first integration through Azure portal. The second integration must be created using Azure Resource Manager templates or Azure CLI commands.

Integrate your app with an Azure virtual network - Azure App Service | Microsoft Learn

かなりインパクトの大きいアップデートなので、サイレントではなくしっかり発表して欲しかったです。

これまで 1 つの App Service Plan 当たり 1 つの Subnet という制約によって、設計上 Subnet を分けたい場合には必ず App Service Plan も分ける必要があり、コスト的なインパクトが大きくなりがちでしたが、ドキュメントに書いている通りであれば 1 つの App Service Plan 当たり 2 つの Subnet に統合出来るので、必要な App Service Plan の数を減らしコストダウンが可能です。

確認の難易度が非常に高いのでやる予定もないのですが、恐らくは VMSS Worker のみの対応になるのではないかと考えています。現在 VMSS Worker へのマイグレーションが行われているようなので、将来的には気にする必要は無くなる部分です。

VMSS Worker が必要な場合には、これまで通り Premium V3 を選んで作成してスケールダウンしましょう。

ドキュメントでは Azure Portal では設定できないと書かれていて、構成方法が少し気になったので実際に Premium V3 の App Service Plan を用意して試してみました。

検証のために用意したリソースは以下の通りです。1 つの App Service Plan に対して 3 つの App Service を共存させています。3 つ用意しているのは 3 つ統合する際のエラーを確認するためです。

仮想ネットワークの Subnet 構成はシンプルに App Service 向けに 3 つ用意しただけです。サフィックスを App Service と合わせているので、どの Subnet に統合されているか一目で分かるようにしています。

まずはいつも通り Azure Portal から 1 つ目の App Service に対して Regional VNET Integration を設定します。流石にこれは全く問題なく設定が完了しました。

そして次は 2 つ目の App Service に対して 1 つ目とは別の Subnet に対して Regional VNET Integration を設定します。これまでは設定時にエラーとなっていた構成となりますが、あっさりと成功しました。

ドキュメントでは Azure Portal からは設定できないとありましたが、問題なく完了したので Azure Portal 側のアップデートが行われた可能性もありますね。Bicep や Terraform でも問題なく設定できるはずです。

App Service Plan 側の設定から Regional VNET Integration を状態を確認すると、2/1 というように表示が追いついていない感じがありますが、問題なく設定は完了しています。

この状態で 3 つ目の Subnet への統合を試すと以下のようなエラーとなりました。エラーメッセージからも 2 つまで統合可能というのがよく分かりますね。

これからは Regional VNET Integration の設定が完了した 2 つの App Service を使って状態を確認していきます。まずは Kudu を使って割り当てられている Private IP を確認しました。

割り当てられた Private IP は WEBSITE_PRIVATE_IP という環境変数に入っています。

それぞれの App Service で統合先の Subnet に割り当てられた範囲で Private IP が振られていることが確認出来ました。この Private IP は必ず変化するので、依存するような構成を組むのは NG です。

最後に App Service の Regional VNET Integration を設定する目的の一つである、Service Endpoint を使ったアクセス制限で 2 つの Subnet それぞれで制限を掛けられるかを確認します。

以下のように 2 つの Storage Account を用意して、それぞれで Service Endpoint を使って対応する 1 つの Subnet からのアクセスのみ許可するように構成します。

Service Endpoint の設定が完了したら、Kudu のコンソールから curl で 2 つの Storage Account に対してリクエストを投げてアクセスできるか確認します。分かりやすいように Private IP の値を表示しています。

1 つ目の Subnet に統合した App Service からは 1 つ目の Storage Account にはアクセス出来ましたが、2 つ目にはアクセスできませんでした。想定通りの動作となっていますね。

逆に 2 つ目の Subnet に統合した App Service からは、2 つ目の Storage Account にはアクセス出来ていますが 1 つ目にはアクセス出来ていません。こちらも想定通りの動作です。

今回は Premium V3 の App Service Plan で試しましたが、現時点では Basic でも 2 つまで VNET Integration を追加できました。これまでの傾向から Tier によって制限があるかなと思ったのですが、VMSS Worker であれば Basic でも 2 つまで使えるようです。

VNET Integration は App Service の使い方を大きく変えた機能ですが、これまで 1 つの App Service Plan 当たり 1 つの Subnet という制約がコスト的にそこそこ厳しかったので、そこが改善されたのは嬉しいですね。

Terraform Cloud に追加された Dynamic Provider Credentials を Azure Provider で試した

今朝に発表された Terraform Cloud のアップデートで OpenID Connect を利用した認証情報の取得に対応したようです。Dynamic Provider Credentials という名前で紹介されています。

GitHub Actions ではほぼ OIDC のみを使うようになりましたが、一番強い権限が必要な Terraform Cloud で使えなかったのが悩みの一つでした。GitHub Actions と Azure AD を組み合わせて利用する方法は以前に書いているので、以下の記事を参照してください。

Terraform Cloud を Azure Provider で利用する場合には期限付きの Client Secret を作成する必要があったので、Client Secret 自体の管理が必要になるというデメリットがかなり大きかったです。まだベータ扱いですが、ついにそんな悩みから解放されます。

早速 Terraform Cloud で Azure リソースを管理しているプロジェクトで有効化してみました。Azure 向けドキュメントは以下に用意されているので、これを読めば簡単に設定できます。

Dynamic Credentials が利用可能な AzureRM Provider と AzureAD Provider のバージョンが決まっているので、古いバージョンの場合は事前にアップデートしておく必要があります。

Azure AD で Federated Credential を追加する

既に Terraform Cloud を利用しているケースでは Service Principal が作成済みのはずなので、追加で Federated Credential を追加して Dynamic Credentials 向けの情報を登録します。

この辺りはドキュメントに書いている通りの値を入力すればよいのですが、Subject identifier が若干複雑なフォーマットになっています。特に Project と Workspace の関係で混乱しやすいと感じたので、注意深く入力した方が良いでしょう。

plan と apply で別々の Subject identifier が用意されているので、それぞれに Service Principal を作成して個別に権限を割り当てることも出来ますが、今回は同じ権限を与えることにしました。

Issuer と Subject identifier は Terraform Cloud が発行するトークンの isssub に一致している必要があります。以下のドキュメントにはトークンの詳細が載っているので、Federated Credential に設定する値のイメージが付きやすいと思います。

結果として Service Principal に追加した Federated Credentials は以下のようになりました。GitHub Actions 用に 1 つと、Terraform Cloud 用に 2 つを追加しています。

Client Secrets が空っぽになっていることが重要です。これで Client Secret の期限を管理する必要なく、更に安全に Azure リソースの管理が行えるようになります。

Terraform Cloud に環境変数を追加

Service Principal に Federated Credential の設定を追加すれば、後は Terraform Cloud 側の環境変数の設定だけで完了します。ドキュメントに記載のある通り Dynamic Credentials を利用するには TFC_AZURE_PROVIDER_AUTHTFC_AZURE_RUN_CLIENT_ID の設定が必須です。

その 2 つとは別に ARM_SUBSCRIPTION_IDARM_TENANT_ID は Client Secret の時と同様に必要になるので、削除せずに残しておけば問題ありません。

設定後に適当に新しく Plan の実行か GitHub 上で Terraform 定義の変更を行えば、Dynamic Credentials を使って Azure リソースへのアクセスが行われます。もし実行時にエラーが出た場合には Federated Credential 周りの設定を再確認すると良いです。

これで Azure リソースの管理からアプリケーションのデプロイまでを全て Client Secret を使うことなく、OIDC を使った期間の短いトークンで行えるようになりました。セキュリティ的にも有利ですし、何よりも期限の管理が必要ないというのは最高ですね。

既存 App Service の実行基盤も VMSS に移行され始めた模様

昔にデプロイした Azure App Service の実行基盤はトラディショナルな Cloud Services で構築されているのですが、少し前に Cloud Services ベースで動いていた App Service が VMSS ベースに切り替わっていることに気が付きました。いつかは移行されると思っていましたが、既に始まっているようです。

App Service で Cloud Services と VMSS を見分ける方法は、以下のように Azure Monitor などでインスタンス名が RD から始まるかどうかをチェックするだけなので簡単です。去年末から今年初めまでは No fly が続いていましたが、今月中旬以降からアップデートが再開され、そのタイミングで切り替わっていました。

既に何回か App Service が Cloud Services ベースから VMSS ベースに切り替わっていることは書いてきましたが、基本的には新規デプロイ時に Premium V3 を選択しないと確実に割り当てられることはなく、実行中の App Service が VMSS に切り替わったのは今回が初めてです。

VMSS に切り替わることで Cloud Services とはドライブレターが変更され、アプリケーションの作りによっては多少影響が出る可能性もあります。詳しくは以下のエントリを参照してください。

数日前まで動いていたアプリケーションが急にエラーになった場合には、VMSS への切り替えが影響している可能性があるので、早めに Azure Monitor などで確認するとベターです。

以前にも Premium V2 が利用できない App Service に対して Retrofit が行われて、Premium V2 が必要だった Regional VNET Integration などに一部対応したという例があります。

今回はフル機能の VMSS ベースの Scale unit に切り替えられているようなので、新しくデプロイしたものと同じ機能が使えるようになっています。特に IP アドレスの変更が行われた形跡もないため、完全にシームレスな移行が行われているようです。

VMSS ベースに切り替わると Premium V3 へのスケールアップが可能になるため、これまではスケールアップには再デプロイが必要でしたがその必要が無くなります。それ以外にも Standard / Basic でも Regional VNET Integration が利用可能になるメリットもあります。

特に App Service のネットワーク系新機能は VMSS ベース向けにしか提供されないというケースが多かったので、既存の App Service に対しても Cloud Services から VMSS への移行が行われたのは喜ばしいですね。

これまで最初は Premium V3 を選択してデプロイするという方法をお勧めしていましたが、全て VMSS ベースに切り替わってしまえば必要なくなるため、Bicep や Terraform を使ったデプロイも楽になるはずです。

Premium V3 が使えない Japan West は VMSS に当たるかランダムでしたが、この問題も解消されそうです。

Azure Cosmos DB を使って安全にシーケンス番号を生成する

Cosmos DB には組み込みで RDB のようにシーケンス番号を生成する機能は用意されていないので、基本的には GUID / UUID を使うことになるのですが、稀にシーケンス番号が欲しくなることがあります。

RDB というか SQL Server の場合は以下のようにシンプルに生成できるので楽ですが、Cosmos DB ではトランザクションが用意されていないので実装に工夫が必要です。

偶にあるサンプルではストアドではトランザクションが効くのを利用しているケースもありますが、今の Cosmos DB では Partial Update がサポートされているため、サーバー側で値をインクリメントすることで安全にシーケンス番号を生成できます。

Partial Update を利用するとコンフリクトもサーバー側で自動的に解決してくれるのと、解決できないコンフリクトは SDK レベルでリトライも行われるため同時実行にも強いです。

サンプルコードは全て C# で書いていきますが、Partial Update が利用可能な SDK が用意された言語であれば、同じように利用できるはずです。SDK でサポートされている機能のまとめは以下を参照してください。

まずはシーケンス番号を保存するためのコンテナーとデータモデルを作成しますが、現在のシーケンス番号だけ保持すればよいので以下のようにシンプルなモデルで十分です。

Cosmos DB では id は必須ですが、フォーマットは指定されていないのでシーケンス名などを入れておけば、複数のシーケンスを同時に生成出来るようになります。

public class Sequence
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("value")]
    public long Value { get; set; }
}

シーケンス番号を生成する前にドキュメントは生成しておきます。これは RDB でも事前にシーケンスの準備をする必要があるのと同じで、安全に生成するためにはなければ作るという処理を避ける必要があります。

現実的には ARM Template や Terraform を使ってリソースをデプロイするタイミングや、管理サイトでのオペレーション時など確実にアトミックに生成出来るタイミングで作るのが良いです。

ドキュメントを作成しておけば、後は Partial Update を使ってドキュメント内の value プロパティをインクリメントしていくだけです。Cosmos DB SDK を使うと以下のようなコードで実現できます。

var connectionString = "<connection_string>";

var cosmosClient = new CosmosClient(connectionString);

var container = cosmosClient.GetContainer("my-database", "my-sequence");

var operations = new[]
{
    PatchOperation.Increment("/value", 1)
};

var response = await container.PatchItemAsync<Sequence>("sample", new PartitionKey("sample"), operations);

Console.WriteLine($"Seq = {response.Resource.Value}");

重要なのは PatchOperation.Increment を呼び出している部分だけです。1 以外やマイナスも指定することも出来るので、様々な用途に対応出来るようになっています。

Partial Update が無い時代には Read して返ってきた値に 1 を足し、更に Replace を Optimistic Concurrency を使って呼び出す必要があったので、エラーハンドリングやコード自体も複雑でしたが Partial Update のおかげで非常に分かりやすいコードになっています。

このコードを実行すると、以下のようにサーバー側で value プロパティへのインクリメントが行われて、その結果が返ってくることが確認出来ます。初回実行でしたので想定通り 1 が返ってきています。

Azure Portal からドキュメントを確認すると value プロパティが現在のシーケンス番号になっています。

当然ながらサンプルコードを実行する度にインクリメントされた値が返ってきますので、この値がシーケンス番号になっていることが確認出来ます。

Partial Update を使うことで SDK とサーバー側でコンフリクトの解決まで行ってくれますが、その確認のために Task を使って同時に 10 件のリクエストを投げて正しくシーケンス番号が生成されるのか確認します。

サンプルコードとしては以下のように Task を使ってシンプルに同時実行しているだけです。

var tasks = new List<Task<ItemResponse<Sequence>>>();

for (var i = 0; i < 10; i++)
{
    tasks.Add(container.PatchItemAsync<Sequence>("sample", new PartitionKey("sample"), operations));
}

var response = await Task.WhenAll(tasks);

foreach (var itemResponse in response)
{
    Console.WriteLine($"Seq : {itemResponse.Resource.Value}, Consumed RU/s = {itemResponse.RequestCharge}");
}

Console.WriteLine($"Next Seq : {response.Max(x => x.Resource.Value) + 1}");

このコードでは一緒に RU も表示するようにしているので、1 つのシーケンス番号を生成するのにかかった RU から秒間の最大生成数をある程度計算することが可能になります。

実行してみると、以下のような結果が返ってきました。大体 10 RU で 1 つのシーケンス番号が生成出来ていて、重複や抜けの発生なくリクエスト数分のシーケンス番号が生成されていることが確認出来ます。

デバッガーを確認するとコンフリクトは同時実行数が増えるほど発生しているのが確認出来ますが、SDK レベルで自動的にリトライが行われるため動作自体には影響しないようになっています。

今回のように id 以外でのアクセスを行わず、インデックスが全く必要ない場合にはポリシーを以下のように全てオフにすることで、処理に必要な RU を極限まで下げることが出来ます。

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

先ほどの結果は既にこのインデックスポリシーを反映した結果となっています。

Cosmos DB の Partial Update は RU の削減にはつながりませんが、アトミックな処理を簡単に実現出来るようになりますので、用途はかなり広いです。是非使っていきましょう。