しばやん雑記

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

アプリを Azure Government と Azure China に対応させつつ ARM Template でデプロイする

日本在住であれば関わる機会がほぼないと思われる Azure Government と Azure China ですが、OSS で Azure 向けアプリを出していると稀に対応せざるを得ないときがあります。

今回は App Service と Key Vault の Acmebot で対応する機会がやってきました。普通ならまず弄らない部分なので、Issue が上がってきた時から興味がありました。対応した PR は以下になります。

完全に興味ドリブンでの対応だったので、この知識が今後役に立つことは 100% 無いし期待もしていませんが、面白かったのでブログネタとして昇華することにします。

ちなみに自分では動作確認していません、というか出来ません。多分動く書き方の紹介です。

そもそも Azure Government / Azure China とは

それぞれグローバル版 Azure とは異なり完全に別環境に展開された Azure のことですが、Azure Government は Microsoft が直接運用しているのに対して Azure China は 21Vianet が運営する形になっています。

無料アカウントを簡単に作れそうな雰囲気がありますが、連邦政府関係者かどうかのチェックが入るため普通に日本で仕事していると絶対に作れません。

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

同様に Azure China も、アカウント作成のハードルがめちゃくちゃ高いのでまず無理です。

ぶっちゃけ存在を知らなくても問題なく Azure を使うことは出来ます。連邦政府関係や中国国内限定の会社とお仕事する場合には知っておいた方が良いでしょうが、そんな機会はまず来ない。

環境による差異を理解する

グローバル版 Azure とは完全に別になっているので、Azure Government / Azure China 共にほぼ全てのエンドポイントが別で用意されています。このあたりの対応さえしてしまえば勝ちという感じです。*1

それぞれのドキュメントに差異が記載されていますが、新しいサービスは抜け落ちているようです。

見事なほどに一貫性のないエンドポイントになっているので、何らかの方法で各エンドポイントの情報を取得するか保持しておく必要があります。Azure China の方が僅かにブレが少ないです。

Azure CLI や Azure PowerShell では一部の情報を取得出来ます。ARM に API が用意されているようです。

適当に Active Directory のエンドポイントだけ絞り込むようにすると、以下のように 4 つ返ってきます。ちなみに AzureGermanCloud はグローバル版がデプロイされたのでお役御免です。

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

この辺りの差異をデプロイ時やアプリケーション側で吸収するように作ることで、Azure Government と Azure China への対応が行えます。正直かなりめんどくさいです。

実際の対応については、この後から適当に必要だった部分ごとに書いていきます。

接続文字列を設定するサービス (Storage / SQL など)

Azure は接続文字列をめちゃくちゃ使うサービスなので、普段から Azure Storage や SQL Database などで使っているはずです。その場合は接続文字列にエンドポイントの情報が含まれているので対応は不要です。

具体的に Azure Storage で見ると EndpointSuffixcore.windows.net という値が設定されます。

なので接続文字列を使っていれば Azure Government / Azure China であることを意識することなく、アプリケーション側の変更はほぼ無しで行けます。

接続文字列が提供されていないサービスの場合は、個別にエンドポイントを意識してコードを書く必要があるので結構面倒です。最近は Managed Identity を使うことで接続文字列が不要になってきているので、さらに意識する必要が出てきました。

Azure AD / Managed Identity

最近は Managed Identity を使って RBAC で必要な権限だけ割り当てることが多いです。

Managed Identity を使ってアクセストークンを取るのは App Service や IMDS が自動でやってくれますが、どのリソースに対してのトークンが必要かどうかは指定する必要があります。

本来なら GetAccessTokenAsync に必要なリソース ID を渡すだけで良さそうですが、AzureServiceTokenProvider のコンストラクタには azureAdInstance という引数でグローバル版 AD のエンドポイントがデフォルトで設定されているので、これをオーバーライドしておきます。

// Azure Government の Resource Manager 用 Access Token を取得
var tokenProvider = new AzureServiceTokenProvider(azureAdInstance: "https://login.microsoftonline.us");

var accessToken = await tokenProvider.GetAccessTokenAsync("https://management.usgovcloudapi.net");

この辺りはドキュメントなどが全然なくて結構厳しいです。とはいえこれ以上設定出来る部分はないです。

Azure Resource Manager

Azure SDK を使って各リソースを弄る場合にはデフォルトのエンドポイントがグローバル版 Resource Manager になっているので、明示的に必要なエンドポイントを指定する必要があります。

例えば Azure Government の App Service に対して操作を行う場合は、以下のように初期化します。

// Access Token は Managed Identity で取得したものを使う
var credentials = new TokenCredentials(accessToken);

// BaseUri を Azure Government の Resource Manager 向けに設定する
var webSiteManagementClient = new WebSiteManagementClient(new Uri("https://management.usgovcloudapi.net"), credentials);

現在の Azure SDK には必ず baseUri というパラメータがあるので、それを指定すれば良いです。

ここまでエンドポイントを直接指定してきましたが、それぞれの Azure 環境に対応しようとすると、オプションなどで変更可能にしておく必要があります。これがまた面倒ですが、結局は ARM Template でのデプロイ時に設定しつつ、アプリにエンドポイント情報を持たせる方法にしました。

ARM Template

全く知らなかったのですが、ARM Template にはデプロイ先の情報を取得するための関数が用意されていました。これを使うことで環境名や一部のエンドポイントをデプロイ時に取得できます。

何故か全てのエンドポイントが網羅されていないので、これで全て解決することは出来ないのが厳しいです。今回は Azure Functions を使うアプリケーションでしたが、考え方は基本的に同じです。

Azure Functions

元々の Issue には Azure Functions のデプロイに失敗するとあり原因が良くわからなかったのですが、世の中に出回っている Azure Functions の ARM Template は Azure Storage の EndpointSuffix を考慮していないものなので、グローバル版 Azure にしかデプロイ出来ない代物でした。

Azure Storage にアクセス出来ない場合には Azure Functions 自体のデプロイが失敗するというのは学びですね。原因はシンプルなので、以下のように environment 関数を使って EndpointSuffix を埋めてあげれば動くようになります。

"appSettings": [
  {
    "name": "AzureWebJobsStorage",
    "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'), '2018-11-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
  },
  {
    "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
    "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'), '2018-11-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]"
  }
]

これで Azure Functions のデプロイが成功します。アプリケーションが実際に動くかどうかは別ですが。

Application Insights

Azure Storage は簡単でしたが、問題は Application Insights です。元々はグローバルにしか対応していないライブラリでしたが、接続文字列を導入することでそれ以外の環境にも対応するようになりました。

しかしここでも命名に一貫性がない弊害が出てきます。ドキュメントには EndpointSuffix としては Azure Government と Azure China 向けの 2 つが有効な値と記載されています。

さらに Application Insights のエンドポイントは environment で取れないという絶望的な状況でしたが、ドキュメントが実は間違っていたことに気が付いたので、ARM Template レベルで解決しました。

グローバル版の Azure では applicationinsights.azure.com というエンドポイントがひっそりと有効になっていたので、これを使って環境名をキーに切り替えるようにします。

"variables": {
  "appInsightsEndpoints": {
    "AzureCloud": "applicationinsights.azure.com",
    "AzureChinaCloud": "applicationinsights.azure.cn",
    "AzureUSGovernment": "applicationinsights.us"
  }
}

エンドポイントのマッピングは上のように用意しつつ、実際に Application Insights の接続文字列を作っている部分で EndpointSuffix を追加するようにします。

地味に ARM Template で Key-Value な変数を扱うのはやったことがありませんでした。

"appSettings": [
  {
    "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
    "value": "[concat('InstrumentationKey=', reference(resourceId('Microsoft.Insights/components', variables('appInsightsName')), '2015-05-01').InstrumentationKey, ';EndpointSuffix=', variables('appInsightsEndpoints')[environment().name])]"
  }
]

多分これで 1 つの ARM Template で Azure Government と Azure China へデプロイ出来るようになりました。

無駄な知識を手に入れてしまった感はありますが、ARM Template 周りのノウハウは溜まった気がします。とはいえ接続文字列周りは Terraform を使うと解消されるので、ARM Template で頑張る必要はありません。

*1:例外的に Azure CDN と Front Door に関しては同じエンドポイントが使われている気配

Azure Functions (Premium / ASP) から Private Endpoint を使う際に必要な設定

Twitter にて SQL Server と金麦で有名なムッシュが Premium Plan で WEBSITE_VNET_ROUTE_ALL が使えないと呟いていて、いろいろ気になったので環境を作って試してみました。

WEBSITE_VNET_ROUTE_ALL は Private Link や Route Table などを使う際に必要となる設定です。この辺りについては以前書いたので、そっちを参照してください。

実際に Premium Plan で検証環境を作成してみると、最初は動いたように見えましたが、再起動などのタイミングで動かなくなってしまいました。

検証を進めると Premium Plan に限らず、App Service Plan でも同様の問題が発生することが分かりました。

ちなみに今回テストに使用したのは以下のような Function です。Private Endpoint を有効にしてある Blob Storage に置いてある画像をプロキシするだけの簡単なものです。

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

static HttpClient httpClient = new HttpClient();

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var stream = await httpClient.GetStreamAsync("https://appprivateendpointtest1.blob.core.windows.net/sample/shibayan.jpg");

    return new FileStreamResult(stream, "image/jpeg");
}

検証しているうちに Private Endpoint を使うサンプルを見つけたので、根本的な原因も大体わかってきました。具体的には Function Key などを保存している Blob Storage へアクセス出来ていないのが原因でした。

必要な情報を読み込めていないのに Host 自体は立ち上がるのは微妙な挙動です。一応は解決策を 2 つ見つけたので、この後はそれぞれについて簡単に説明をしておきます。

別の Storage Account を作成して Private Endpoint を追加する

サンプルでは AzureWebJobsStorageWEBSITE_CONTENTAZUREFILECONNECTIONSTRING をそれぞれ別の Storage Account として用意して、AzureWebJobsStorage 側に Private Endpoint を作成しています。

AzureWebJobsStorage は Function Key などの実行に必要な情報を格納するために使われています。

Azure Portal から作成すると同じ Storage Account が設定されますが、実際には同じである必要は全くないのと、AzureWebJobsStorage は Azure Functions Runtime が参照するため、Private Endpoint を有効にしても問題なくアクセス出来るという仕組みです。

適当に Storage Account を作成して AzureWebJobsStorage の値を上書きしておきます。

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

そして以下のように AzureWebJobsStorage に指定した Storage Account に対して Private Endpoint を作成すると、Azure Functions Runtime からアクセス出来るようになるため、WEBSITE_VNET_ROUTE_ALL を有効にした状態で各 Function が動作するようになります。

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

Function Runtime が起動したタイミングで必要なファイルは自動的に生成されます。この時、各キーは新しく作成されるため Function Key や Master Key は値が変わるので注意が必要です。

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

Private Endpoint を使うために Private Endpoint の追加が必要になるというのは、若干混乱しそうです。

Subnet で Service Endpoint を有効化する

個別に Private Endpoint を作成するのは割と手間ですし、Function の状態までも Private Endpoint にしなくてもよい良いケースもあると思います。その場合は Service Endpoint を有効にするのがお手軽です。

App Service が Regional VNET Integration で追加されている Subnet に対して、Microsoft.Storage の Service Endpoint を有効化するだけです。Storage Account を分ける必要はありません。

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

Storage Account に対して Firewall の設定を追加すると App Service にマウント出来なくなって動かなくなるので、Firewall Rule の追加はせずに Service Endpoint だけを有効化します。

Subnet に対して Service Endpoint を有効化すると、ルーティングが変化してアクセス出来るようになります。このあたりの挙動がドキュメントに書かれているのか把握はしていません。

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

テストすると問題なく Private Endpoint 経由で画像にアクセスできることが確認できます。

Service Endpoint を有効化しないままだと動かないのは謎ですが、App Service の Regional VNET Integration 自体が謎テクノロジーで作られているので仕方ない感はあります。

この場合は Azure Functions と同時に作成された Storage Account はこれまで通りパブリックのままです。そこも Private Endpoint にしたい場合は上の例のように Storage Account を分けてください。

Runtime Scale Monitoring の設定には注意

Premium Plan と Regional VNET Integration を同時に使うと、Azure Portal にある Runtime Scale Monitoring という設定が重要になってきます。

Azure Functions は Scale Controller が外部にあって、Storage Queue や Service Bus のメッセージを監視してスケーリングする仕組みになっていますが、それらのリソースが Private Endpoint などで塞がれると Scale Controller が必要なメトリックを取れなくなります。

従って Premium Plan 向けに Function Runtime 側がスケーリングに必要なメトリックを返す仕組みが追加されました。それが Runtime Scale Monitoring です。

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

ドキュメントにあるように、新しい Extension バージョンが必要になるので、古いバージョンを使っている場合はちゃんとアップデートしておきましょう。

Premium Plan と Regional VNET Integration を組み合わせて使う際は設定に気を付けましょう。

無料バウチャーがあったので AZ-204 / AZ-400 を取得した

去年開催された Ignite The Tour に参加すると、認定試験が無料で受けられるバウチャーが配られていることに気が付いたのと、AZ-204 を取っておいた方が良い状況だったので受けてみました。

バウチャーの使い方とかは以下のドキュメントを参照してください。勝手に追加されていた気もします。

試験センターで受けるのが一般的かもしれませんが、このご時世で人が密集する場所に数時間居たくないので自宅で受けました。正直、試験よりこっちの方が大変でした。

AZ-204 は Azure でも開発系なので、何もしなくても余裕で合格するだろうという目論見でした。他の AZ-104 / AZ-303 / AZ-304 は日本語化されていなかったり、まだベータだったりしたので選んだという説もあります。

全く勉強とかはしませんでしたが、予約する前にスキルアウトラインだけは目を通しました。バウチャーを使う予定だったのですが、いろいろトラブルがあったので普通にお金を払いました。

試験時間は 3 時間ぐらいだったと記憶していますが、1 時間ぐらいで終わってすんなり合格していました。

正直なところ問題はいまいちな部分が多かったです。現実世界ではまず使わないし選ばない系の問題がちょいちょい出てきて、回答しながら微妙な気持ちになりました。

バウチャーが余ってしまったので何で使うか調べていたところ、AZ-400 を取ると DevOps Engineer Expert という上級の認定が貰えることを知ったので、こっちも受けてみることにしました。日本語もありましたし。

例によってスキルアウトラインだけ目を通して、特に勉強もせずにそのまま受けました。

こっちも 1 時間ぐらいで終わりましたが、合格していました。自分の知識を多少は試せた感があります。

問題はいまいちな部分が多かったです。何故そんなことを聞いてくるのかと何回も思いました。正しさよりも質問者が求めている回答を予想して選んでいくという感じでした。

受けておいてなんですが、正直なところ実務経験の方が圧倒的に良いので、今回のように必要という状況にならない限りは追加で受けることは無さそうな気がしています。

正直なところ、試験の内容よりも自宅で受けるための準備の方が大変だったのと、自宅のメインマシンだと受けることが出来なかったので、その辺りの知見を残して終わりにします。

部屋と PC 周りを片付ける

試験のチェックイン時に部屋の写真を撮影する必要があります。もちろん PC 周りに余計なものがあってもいけないので、電子機器や筆記具などは別の部屋などに遠ざけておくと安心です。

腕時計などもチェックされるので、普段使っている Apple Watch も外しておきました。

事前の環境チェックは念入りに

絶対にアプリケーションの不具合だと思っているのですが、CPU コアがたくさんあって、比較的つよつよの SSD / GPU を積んでいるメインマシンでは試験のリリースが正しく行えず、その日の受験は強制的にキャンセルにされてしまいました。

挙動的にマルチスレッド周りでデッドロックを起こしているか、競合で無限ループのような状態に入ってしまっている、もしくはフックプロシージャ周りの実装がダメダメという感じでした。そもそもマウスカーソルがちゃんと動作しないというお粗末さです。

事前のチェックはしていましたが、タイミングにシビアな実装になっている気がしました。結局のところ Surface Laptop 3 で受けることにしてこの問題を回避しました。

アクセスコードは残しておく

上の問題に関連して、チャットで画面がフリーズした場合はアプリケーションを強制終了させて再度入りなおすように言われましたが、アクセスコードを残していなかったので入ることが出来ませんでした。

その時はサポートに電話して聞くことが出来ましたが、ファイルなどに保存しておいた方が良いです。

PC は電源に接続した状態で

試験に使用するアプリケーションは試験中は常時カメラとマイクがオンになった状態なのと、アプリケーション自体が結構マシンパワーを消費するようなので Laptop 3 の CPU ファンが常時回っていました。

最初はバッテリーで十分持つだろうと思っていましたが、予想以上にマシンは熱くなっていたので電源につないで受けた方が良いでしょう。途中で電池切れになったら最悪です。

Hack Azure! #2 Ask the Geeks - Cosmos DB 編フォローアップ

先週の木曜に "Hack Azure! #2 Ask the Geeks - Cosmos DB 編" と題して、Microsoft Corp の勇さん・ちょまどさん・Azure MVP の三宅さん・大平さんと自分の 5 人で Cosmos DB について話す会を行いました。

今後も #HackAzure ということで Synapse Analytics や Azure AD B2C、App Service / Azure Function など定期的に開催できれば嬉しいなと思っています。

クローズドな場ではなくオープンな場で開催し Q&A やライブコーディングなどを行って、楽しく情報を共有していくことに意義があると思っているのでお気軽に参加ください。

今回は Teams Live Event の Q&A 機能を使って、都度質問は簡単なものは横浜さんが回答しつつも、それ以外は Q&A コーナーで全員で話すという形を取りましたが、全てを拾い切れたわけではないのと時間切れなどで端折った部分もあるので、その辺りのフォローアップを行っておきます。

既に動画は公開されていますが、Teams Live Event での Q&A のまとめにはもう少しかかる予定です。

Twitter まとめと YouTube アーカイブ


パーティションキーの変更

Cosmos DB はコンテナーを作成した時に設定したパーティションキーからは変更できない仕様になっているので、後からパーティションキーの設計を変えるのは非常に大変です。

とは言え完全に方法がないわけではなく、公式で以下のようなツールが公開されています。

Azure Data Factory を使ってデータをコピーする方法もあります。

結局のところは新しいコンテナーを作成してデータを全てコピーするだけですが、手間であることには変わりないので事前に PoC などで検証しておいた方が良いです。

Backup & Restore 機能が PITR と同時に予定されているので、多少は簡単になるかもしれません。

とは言え、後からの変更は苦労するだけなので出来るだけ避けたいところです。

消費された RU の確認

Cosmos DB で消費された RU はレスポンスに含まれているので、該当のプロパティを参照すれば簡単に確認できます。設定を有効にすれば統計情報も返ってくるので、極端に重いクエリの調査にも有用です。

実際に稼働しているアプリケーションで RU をモニタリングする場合には、Application Insights にメトリックとして値を送信してしまうのが割とおすすめです。

もしくは Log Analytics へ Cosmos DB のログを書き出すように設定して、KQL で後からクエリを書いて RU を調べるという方法もあります。

現時点ではこのログが一番確実な RU の確認方法のようです。Azure Portal で確認できる値は怪しいです。

Cosmos DB SDK v3 の Preview 機能

.NET 向け Cosmos DB SDK v3 は #if PREVIEW で囲まれた機能は -preview サフィックスの付いたバージョンで利用可能になっています。

Change Feed の Pull Model が代表的ではありますが、今後も新機能はまず Preview として出る可能性が高いので知っておいて損はありません。

Subpartitioning や JSON Patch 対応などは Preview のうちに試しておきたくなる機能です。

Table Storage と Cosmos DB

Table Storage と Cosmos DB について Twitter でちょいちょい見ましたが、もはや Table Storage は Cosmos DB の一部ぐらいの扱いになっていて、しばらく前から Table Storage SDK の公開は行われなくなりました。

現在はドキュメントでも Cosmos DB ベースの SDK を使うように紹介されています。

確かに価格が安いのでシンプルな Key-Value ストレージとして使いたくなりますが、もう今後はアップデートされないのではないかと思っています。*1

v3 SDK でのパフォーマンス Tips

ライブ中に Changelog をいくつか紹介しましたが、公式ドキュメントにもパフォーマンスを改善するためのヒントが結構まとめられています。

v3 SDK からは Stream バージョンの API*2 や Bulk Operation も実装されているので、v2 よりパフォーマンス改善が行いやすいです。

Change Feed の設計パターン

例によって公式ドキュメントが神がかっているので紹介しておきます。大体は Cosmos DB のユースケースとして紹介されている図でも、最近は大体 Change Feed を当たり前のように使っています。

パーティションキー内での順序保証がされるのと、現時点ではデータが残っている限りはリプレイが可能という大きな特徴を持っているので、有効活用していきましょう。

Change Feed の保持期間と制限

Twitter で Change Feed の変更が保持される期間などに制限があるのかという質問が出ていましたが、公式ドキュメントにそういった制限はないことが明記されています。

You can read the change feed for historic items (the most recent change corresponding to the item, it doesn't include the intermediate changes), for example, items that were added five years ago. You can read the change feed as far back as the origin of your container but if an item is deleted, it will be removed from the change feed.

Working with the change feed support in Azure Cosmos DB | Microsoft Docs

Changes can be synchronized from any point-in-time, that is there is no fixed data retention period for which changes are available.

Working with the change feed support in Azure Cosmos DB | Microsoft Docs

コンテナー作成時から遡って Change Feed でデータを扱えるので、整合性を保ったまま新規にインデックスや読み取り用 DB の構築を行うことも簡単です。

具体的には StartTime を過去の値(例えば DateTime.MinValue)にしてしまえば全ての変更を扱えます。

Change Feed の Pull Model (Preview)

Change Feed Processor や Azure Functions の CosmosDBTrigger では変更があった時に、設定したデリゲートや Function が実行されるという Push 型になっていますが、新しく Pull 型の API が v3 SDK の Preview として提供されています。

Change Feed の先のサービスが障害などで書き込めなくなった時に、Circuit Breaker や Exponential Backoff などで回復を待つ処理を柔軟に書けるので、個人的にはかなり有用ではないかと思っています。

Cosmos DB を ASP.NET Core のセッションに使う

最近はセッション自体を使うことが無くなってきたのですが、v3 SDK ベースの IDistributedCache 実装が提供されているので、Redis と比べて信頼性の高いストレージを利用できます。

実際に使う場合には Consistency の設定には注意が必要なので、README をよく読んでおきましょう。

具体的には Session Affinity が有効になっている場合は Session を、それ以外は Bounded staleness や Strong を選ぶという話です。

おまけ : NuGet パッケージのインストールが早い

Core i9-10940X / Optane SSD 900P / 11ax (Wi-Fi 6) という構成のつよつよマシンでお送りしました。

Optane SSD は単価と消費電力が高いですが、現実世界の I/O ワークロードに強いので便利です。

*1:廃止されることは絶対にないが、機能改善などのアップデートはされなさそう

*2:レスポンスを .NET の Stream として返してシリアライズコストを下げる

2020 年 10 月に既存の Azure Functions v2 は v3 (.NET Core 3.1) に更新されます

Azure Functions v2 もサポートが継続されるみたいな話を見た気がしますが、既存の Function v2 は使用している .NET Core バージョンの EOL に伴って v3 にアップグレードされるようです。

今年の 10 月に行われる予定のようですが、例によってリージョンによって多少の差が出るはずです。

自動的に FUNCTIONS_EXTENSION_VERSION~3 に変更されるのかと思いましたが、実体は異なっているようだったので概要をまとめました。

  • Azure Functions Runtime が v3 (.NET Core 3.1) にアップグレードされる
    • v2 は .NET Core 2.2 がターゲット (既に EOL)
  • ただし FUNCTIONS_EXTENSION_VERSION~2 のまま
    • ~2.0 を指定すると固定可能なようなので、恐らく 2.1 のような扱い
  • FUNCTIONS_V2_COMPATIBILITY_MODE = true が自動で設定される
    • そこまで大きな差はない (レスポンス書き込み時の同期 IO が許可されるぐらい)

v2 の互換性モードが有効になることで変更になる動作はきっちりとまとめられていないので、Azure Function Runtime のコードを読むしかないです。以下の部分が該当します。

開発チーム曰く、.NET Core 2.2 から .NET Core 3.1 の間で変更された API を使っていない限りはシームレスに移行が行われるようです。

つまり v3 へのアップグレードには手間がかからないということでもあるので、現在 v2 で運用している Azure Functions はさっさと v3 にアップグレードした方が良いでしょう。

以前書いたように、大体は SDK と TFM の更新ぐらいで終わります。マイグレーションのドキュメントも用意されていますが、JavaScript の方が破壊的変更が多いので少し心配です。

アップグレード後はテストが必要になりますが、それは放置していても同じなのでしつこいようですが、早めに v3 へのアップグレードを終わらせておいた方が良いです。

今回のアップグレードをオプトアウトしたい場合には FUNCTIONS_EXTENSION_VERSION~2.0 に設定します。恐らく実質的にはサポート終了扱いになるので、あまり推奨はしません。

If you do not upgrade your app to Azure Functions runtime v3, you may “pin” your app to Azure Functions v2.

To pin your app to Azure Functions v2, update the application setting FUNCTIONS_EXTENSION_VERSION to ~2.0.

Platform upgrade from Azure Functions runtime v2 to v3 · Azure/azure-functions-host Wiki · GitHub

de:code 夏祭り #2 で Azure Functions は v3 しか使っちゃダメとは言いましたが、思ったより早く v2 がリタイアになったなという印象は持ちました。

ただし直近で v2 Runtime が削除されることはないので、2.0 に固定していても動作し続けるというのが良くない点です。後で慌てないためにも 10 月までに v3 へのアップグレードを終わらせておきましょう。

みつばたんへの Twitter での Azure / ASP.NET Core アドバイスのまとめ

みつばたんが最近 ASP.NET Core MVC 周りで死ぬほどはまっていたみたいですが、ポートフォリオサイトを作っていたようです。自分は死ぬほどはまっていた時には全くアドバイスしなかったのに、大体動くようになってからアドバイスをするという徳の低いことをした気がします。

その時のアドバイスは雑に Twitter で空リプで行いましたが、残しておいた方が良いかなと思ったので雑に Twitter を引用しつつまとめました。

認証は AAD + Easy Auth で

数えきれないぐらい言ってきていますが、サクッとサイトをログイン必須にして保護したい場合には AAD と Easy Auth を使うのが手っ取り早いです。

カスタムドメイン + リバースプロキシ環境下で動作させている時には、Easy Auth がちゃんとホスト名を認識してくれず動作しない気配があるので、カスタムドメインを割り当てるか App Service のデフォルトドメインで運用するかになるでしょう。

プロバイダーに AAD を選ぶと、現在のテナントに入っているユーザーのみログイン可能なページを作れるので楽です。セットアップも一番楽なので、大体はこれを使っておけば良いです。

ASP.NET Core Routing のオプション

未だにデフォルトで小文字にならないのが不思議ですが、ASP.NET Core MVC のルーティングで URL を生成すると Pascal Case で生成されます。要するにクラス名と同じというわけです。

ルーティングのオプションで LowercaseUrls = true を設定することで小文字な URL を生成できます。

Startup クラスで書くと以下のようになります。知っていれば大したことない部分です。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting(options => options.LowercaseUrls = true);

    services.AddControllers();
}

ASP.NET MVC の時のように常にテンプレ的に書くようにしていますが、やはりデフォルトは小文字にしてほしいです。とにかく URL がダサくなるので止めてほしい。

Blob Storage 周りの話

データストアとして Blob Storage だけを使って、メタデータで運用するというのは今時で良い感じです。絞り込みなどは難しいですが、Blob Index Tags がリリースされれば簡単に実装できるようになります。

Japan East / West で使えるようになるのはまだまだ先な気がしますが、期待して待ちます。

正直最近というほど新しくはないですが Event Grid や Change Feed を使えば、Blob への変更を検知していろいろと後続の処理を追加できるので、Blob に置くだけというのはそういう意味でも良いです。

Event Grid と Change Feed は似ていますが性質が異なるので、良い感じに使い分けていく必要があります。具体的には Push と Pull という違いです。

Change Feed はクライアントが上手く動作しなかったので試せていないですが、Object Replication で使われているので Blob Storage の新機能を裏で支えるものになっていくのでしょう。

今後みつばたんがやること

Cache の TTL をもっと長くする

Cloudflare を使ってある程度キャッシュさせているみたいですが、ヒット率が低すぎるので良くないです。

サイトの性質からすればヒット率は 98% 以上は狙えると思うので、短すぎる TTL をもっと長くして更新のタイミングで API を使ってキャッシュをパージした方が良いです。

このあたりは対して難しくはないでしょう。比較的サクッと対応できるはず。

サムネイルの生成は要改善

サムネイルの生成をアップロード時に Web サーバーでやっているのを、Event Grid + Azure Functions にすると簡単に処理を分離しつつ Web サーバーの負荷を下げることが出来ます。

チュートリアルにはそのままのものがあるので、そのまま使ってしまえばよいです。

写っているものが中途半端にトリミングされることを防いで、良い感じにサムネイルを作りたい場合には Cognitive Services の Computer Vision API を使うのも手です。

https://docs.microsoft.com/en-us/rest/api/cognitiveservices/computervision/generatethumbnail

ROI を特定してサムネイルを生成してくれるので結構便利です。

お金があれば Cloudflare の Image Resizing を使ってしまうのが手っ取り早いです。

CDN が画像のリサイズや最適化を行う機能を持つことが多くなって便利になりましたが、それなりの金額を払う必要がありますね。

CI/CD パイプラインを組む + Run From Package 化

Visual Studio からのデプロイは Web Deploy が使われるので不安定です。GitHub Actions や Azure Pipelines を使って Run From Package でデプロイすることで深夜にキレる必要が無くなります。

この辺りは何回も紹介していますが、以下のエントリを参照すれば解決するはずです。

初期の方からアドバイスすればもっと早く完成していた気もするのですが、まあ良いでしょう。

Azure が提供するデフォルトドメイン名を狙った改ざんについて注意喚起

Twitter でちらほら見かけて自分も軽く反応したんですが、ターゲットが Cloud Services と Traffic Manager だったので多少なりとも書いておいた方が良いかなと思ったので、注意喚起を兼ねて書きます。

今回の件を改ざんと書くのは微妙に違う気がしたのですが、元のツイートは改ざんと表現していたので倣って改ざんとしておきます。このあたりの定義は難しそうです。

要約すると、攻撃者が過去に利用されていた名前の *.cloudapp.net*.trafficmanager.net を新しく取り直して、利用者が削除し忘れていたドメイン名でのアクセスを通るようにしてしまったという話です。

従って回避する方法は、削除し忘れていた DNS レコードを削除するだけでよいです。簡単な話です。

プラットフォーム側で同じ名前で作れないように、という声もあるかもしれませんが、ARM Template や Terraform を考えると作成する度に別名が必要というのは割と困ります。なので基本は不要な DNS レコードは削除しましょう。

削除後に注意すべきサービス

ターゲットとなりうるサービスは、以下のような機能を提供しているものになると考えています。Cloud Services と Traffic Manager はこの条件を完全に満たしています。

  • サービスが共通のドメイン名を提供している
    • 今回であれば *.cloudapp.net
  • サブドメイン名を自由に入力、かつ使用済みのものを利用できる
  • CNAME を使ってサービスへポイントさせる
  • ドメインの所有確認を行わない、または契約者に固有の情報で行っていない
    • TXT レコードを作成し、ドメイン名をそのまま入力させる、など

他にも Azure のサービスでは CDN や Front Door なども、CNAME で正しくポイントされていればカスタムドメインを追加できるので、同様の攻撃対象となる可能性があります。

今回は Azure についてのみ書いていますが、上の条件を満たせば他のプロバイダーでもターゲットとなる可能性があるので注意してください。

Azure の場合、恐らくは以下のサービスは対象となる可能性があります。

  • Cloud Services
  • Traffic Manager
  • App Service (2020/5 以前に作成したもの)
  • CDN
  • Front Door
  • Blob Storage

ただし SSL / TLS 証明書の取得は行えないため HTTP のみ利用可能になりますが、別サイトにリダイレクトしてしまえば関係ありません。元々のサイトが HSTS を使っていて、かつブラウザに情報が残っていた場合はエラーになりますが、まずあり得ないでしょう。

API Management は *.azure-api.net というドメイン名を提供していて対象となりそうですが、カスタムドメインの追加時に証明書を同時に登録する必要があるので、改ざん向けに使うのは難しいでしょう。

勘違いしてほしくないのですが、サービス側に致命的な問題があるのではなく、不必要な DNS レコードが残っていることが致命的な問題です。忘れないようにしてください。

今回の改ざんの Azure 利用者視点での解説

今回ターゲットとなった Cloud Services は *.cloudapp.net というドメイン名を提供しつつ、サブドメイン部分は空いていれば誰でも取れるようになっています。L4 ロードバランサーは付いていますが、サービスとしてカスタムドメインに関する機能は提供されていません。

そして IP アドレスは厳密には固定ではないので、CNAME を使ってカスタムドメインを割り当てるのが一般的でした。この辺りはドキュメントを参照してもらえばわかると思います。

今回のように Cloud Services だけが削除されて DNS レコードが残った状態が発生した場合には、攻撃者は同じ名前で Cloud Services にデプロイするだけで簡単に新しいサイトにアクセスさせることが出来ます。

そして同じように Traffic Manager も *.trafficmanager.net というドメイン名を提供しつつ、同じ名前で取ることが出来るので攻撃者に新しく Traffic Manager を取得されてしまったようです。

とは言え DNS ベースで動作する Traffic Manager では、どのドメインにアクセスしようとしているかなんて確認しようがないので、使わなくなった DNS レコードは必ず削除するのが正しい解決策です。

この後は攻撃対象とならないために出来ることを 2 点書いておきます。基本は使わなくなった DNS レコードを削除するように徹底することなので、頭に入れておいてください。

新規に App Service を使う

App Service は今回のターゲットとなった Cloud Services 上に構築されていますが、カスタムドメイン周りの仕組みが完全に異なっていて、明示的に使用するカスタムドメインを登録する必要があります。

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

そもそも Cloud Services は明示的に IIS へ設定しない限りは IP 指定でも通るようなザルさなのと、デプロイ周りもレガシー感があふれているので App Service の利用を第一に検討するべきです。

そして 5 月あたりにカスタムドメインの検証周りがアップデートされて、サブスクリプションに固有の ID を設定しないと検証エラーで追加が行えなくなりました。

ただし変更前は TXT レコードに App Service のドメイン名を設定して所有確認していたので、昔から使っていた場合は Cloud Services と同様の問題が発生します。

既にカスタムドメインを設定済みの App Service に対しては、新しく asuid を使うように変更しても良いですし、一度追加したものに対して再チェックは行われないはずなので削除しておいても良いでしょう。とにかく必要ないものを残しておくことは NG です。

IaC でDNS レコードを同時に管理する

根本的にはアプリケーションやインフラと DNS レコードのライフサイクルが別々に管理されているのが原因なので、Terraform などを利用して DNS レコード自体も一体として管理すれば解決します。サービスに必要なリソースを IaC でドメイン名ごと管理してしまえば、削除忘れは起こりません。

SSL / TLS 証明書の管理と同様に、DNS やドメイン名周りは手作業に頼りがちな部分です。

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/dns_zone

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/dns_cname_record

DNS ゾーンは 1 つを使いまわしたいケースも多いと思いますが、Data Source との組み合わせや DNS だけを別のリポジトリで管理するなど、いろいろな方法はあります。

バージョン管理しないと簡単に破綻する部分でもあるので、活用していきましょう。

追記 : Alias Record Set の利用

公式ドキュメントに今回の攻撃に対する内容が少し前に追加されています。

App Service はこれまでに書いた通りサブスクリプション固有の ID を設定することで回避する方向ですが、CDN と Front Door に関しては Azure DNS の Alias Record Set を使うことで、該当リソースが削除されたタイミングでレコードを引けなくなるので回避可能です。

内部では既に問題として認識されていて、対応を検討しているようなので情報が今後出てくると思います。

de:code 夏まつり #2 フォローアップ (Azure パート)

昨日の夜に YouTube Live で配信された de:code 夏まつり #2 というイベントに Azure 担当として参加して、なんと!あの超有名な世界のぶちぞう RD と一緒に Azure 最新情報トーク(自称)をしました。

ちなみにこのタイトルは某森口さんが付けてくれました。謎の漫才師っぽさあります。

[19:05 - 19:30] こすもすえび&しばやんの「Azureアンカンファレンス~Azureについては俺たちに聞け(ばええやろ)」~ - こすもすえび/しばやん

5月に開催されたMicrosoft Build 2020で発表されたアップデートを始め、気になるAzureの最新情報を話します。

使用したスライドはぶちぞう RD が用意してくれたものです。圧をかけて公開してもらいました。

Teams でスピーカーが全員参加する方式だったので、実際の YouTube Live では以下のように表示されていました。レコーディングが色々と処理された後に公開されるらしいです。

ここから先は少し真面目にフォローアップを行っておきます。25 分だと皆のエンジンがかかってきたところで終わるのでなかなか厳しい感じでした。

Cosmos DB のアップデート凄い

世界のぶちぞう RD のフレンドである Mark Brown から様々なアップデートの話がありました。

  • Autoscale (旧称 Autopilot, GA)
  • Serverless Plan (間もなく)
  • Full Change Feed (間もなく)
  • Point In Time Restore (話さなかったけど間もなく)

Serverless Plan と Full Change Feed はかなり期待しています。Materialized-view や Event-driven なアプリケーションなどが、これまで以上に簡単に実装できるようになるはずです。

PITR も Change Feed ベースで実装されるのかもしれません。Blob Storage の PITR は Change Feed と Versioning の合わせ技でした。

一先ずは Serverless Plan のリリースを非常に楽しみに待っています。もちろん Change Feed も。

待望の Static Web Apps

Static Site Generator や JAMstack のデプロイに最適なサービスがリリースされましたが、この辺りは三宅さんの独壇場なので多くは語らずに記事の紹介だけにします。

不具合や要望があれば、以下のリポジトリに Issue を作成すると良いです。

既にいくつかは GA までに対応するラベルが付けられているので、適当に流し見しても良いでしょう。

Analytical Store と Synapse Link はめっちゃ良い

Preview に申し込んだのに有効にならないままリリースされた Analytical Store と、Azure Synapse Analytics に取り込むための Synapse Link がめっちゃ良さそうという話をしました。

Cosmos DB に溜まっているデータを自動的に Analytical Store に同期して、Synapse Link 経由で Spark などを使った分析が行えるのはかなり熱いですね。

関係ないですが SQL DW を Synapse Analytics に変更したのは良かったと思います。響きがかっこいい。

Azure Storage 周りも地味に更新

数年前は Azure Storage にアップデートが全然無いという暗黒期みたいなのがありましたが、最近は様々な新機能が追加されていて面白いです。

  • GZRS (GA)
  • Account Failover (GA)
  • Versioning (Preview)
  • Change Feed (Preview)
  • Object Replication (実質 Limited Preview だと思う)

舌をかみ切りそうな RA-GZRS はともかく、それ以外は DR / HA に対応したシステムを構築するのに役立ちそうなものばかりです。

Object Replication に大注目していたので一通り検証していますが、Change Feed の更新が遅いからかレプリラグが思っていたより大きかったのが残念です。

フィードバックはしているので GA までに多少なりとも短縮と、目標レプリ時間の SLA が提供されると良いなと思います。面白い機能だと本当に思います。

Regional VNET Integration と Private Link が素敵

Azure Serverless (PaaS) 大好き野郎としてはこの辺りの素晴らしさの話をしたかったのですが、時間がなかったのでこのフォローアップで触れます。

  • Linux の Regional VNET Integration が GA
  • ついでに Linux の Hybrid Connection も GA

Build 2020 が終わった後にリリースされました。App Service チームは元々 Build や Ignite に合わせてリリースしない傾向があるので、定期的にブチザッキを確認するのが良いです。

Regional VNET Integration を使うと何が嬉しいのかは以前に書いたので、興味がある方はこっちを参照してください。App Service Environment の存在を最近は忘れつつあります。

ExpressRoute 経由や Azure PaaS へ Service Endpoint ではなく、セキュアかつプライベートな接続が必要な場合には Private Link というか Private Endpoints を使います。

Private Endpoints を作成すると、VNET 内に Private Endpoint / Network Interface / Private DNS Zone が作成されて、プライベート IP で各サービスに接続出来るようになります。

かなり強力だと思う点がリソースグループやリージョン、サブスクリプションといった境界を越えて Private Endpoints を作成できることです。単純に全て Private Link という発想ではなく、Service Endpoint と上手く使い分けていきましょう。

ちょっと前に App Service への Private Endpoints も全てのパブリックリージョンで使えるようになりました。追加設定は必要ですが、App Service から Private Endpoints 経由の接続も行えるようになっています。

API Management の Consumption が日本にない罠

最中に何回か Redis Cache みたいなデプロイに数十分かかるようなサービスはダメだと話をしましたが、API Management の Consumption 以外もそれに該当するサービスです。

何故か Japan East / West の両方共に Consumption がデプロイされていないので、低レイテンシな環境で使えないのがかなり厳しいです。一番近いのは韓国中部です。

前のリリースから半年たったし増やして欲しいですが、COVID-19 のせいで遅れていそうな気配です。

SignalR Service の Plan 切り替え時の注意点

クイズ大会用アプリでテストの時は動いていたのが、本番だと上手く動かなくなってしまったらしく、裏側に移動して数人で問題解決に勤しんでいました。

429 が出ていたので一瞬で Plan の問題だと分かったので、Free から Standard にスケールアップすればすぐ解決!と思いましたが、Azure Portal には以下のような警告が出ていました。

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

Free と Standard は別のシステム構成になっているらしく、Plan の変更時には IP アドレスが変化するというものです。Azure Search のように Plan の変更は禁止にした方が安全な気がしますね。

Standard への変更に時間がかかってしまうので、今回は新しく Standard のインスタンスを作成して対応したという話です。対応自体はほんの数分で完了しました。

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

ちなみにクイズ中の SignalR Service のメトリクスグラフを載せておきます。クイズ開始時に急に接続数が増えているのが分かりますね。

その他

Bonsai とか Q# とかは世界のぶちぞう RD に全て任せているのと、よく分からないので言及はしません。関係ないですけど Limited Public Preview って一体どういうことなのかという気持ちが少しあります。

後はブチザッキを読んでおけば良いです。フィード登録するか、毎週木曜日に見に行けば更新されています。

今後も継続的にブログ更新されるはずなので、安心感しかありません。自動化みたいな話が出ていましたが、私たちは世界のぶちぞう RD の手作業によるキュレーションを期待しているのです。

雑に喋りすぎてもう呼ばれない可能性がありますが、また機会があればどこかでお会いしましょう。

Azure Functions に Options のバリデーションを追加する

ASP.NET Core で導入された Options パターンを使ったアプリケーション設定ですが、IConfiguration と DI を導入すれば使えるので最近は Azure Functions の Trigger に関係ない部分で使っています。

Azure Functions では IConfiguration を簡単に取れないので、自前で用意する必要はありますが ASP.NET Core とほぼ同じ感覚で使えます。何回かエントリを書いているので参照してください。

Trigger に関係する設定は Function Runtime というか Scale Controller が読み取り可能な場所に書かないといけないので、IConfiguration を使って JSON などから読み込んでも無視されます。今のアーキテクチャでは解決は不可能かもしれません。

そして今回の本題ですが Options にはバリデーションを追加することが出来ます。使い慣れた Data Annotations をプロパティに付けるだけなので簡単ですし、複雑なバリデーションも実装出来るようです。

ASP.NET Core では ValidateDataAnnotations を呼び出すだけで、属性ベースでの Options のバリデーションが有効になります。タイミングは IOptions<T>.Value を参照し、インスタンスが作成された直後です。

Azure Functions の場合は Microsoft.AspNetCore.App が暗黙的に参照されているわけではないので、ドキュメントにある通り追加の NuGet パッケージをインストールする必要があります。

実際に Azure Functions で Options パターンを使いつつ、Options 値のバリデーションを有効にするには、以下のような Startup クラスを用意すれば良いです。

バリデーションを行う場合は Configure<TOptions> が使えないのが少し手間です。

public class Startup : FunctionsStartup
{
    public Startup()
    {
        var config = new ConfigurationBuilder()
            .AddEnvironmentVariables();

        Configuration = config.Build();
    }

    public IConfiguration Configuration { get; }

    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.Replace(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));

        builder.Services.AddOptions<SampleOptions>()
               .Bind(Configuration.GetSection("Sample"))
               .ValidateDataAnnotations();
    }
}

public class SampleOptions
{
    [Required]
    public string Value { get; set; }
}

public class Function1
{
    public Function1(IOptions<SampleOptions> options)
    {
        _options = options.Value;
    }

    private readonly SampleOptions _options;

    [FunctionName("Function1")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        ILogger log)
    {
        return new OkResult();
    }
}

IOptionsFactory<> をわざわざ標準の実装に入れ替えているのは、Azure Functions が提供している実装が雑でバリデーションが動作しないものになっているからです。

入れ替えたとしても違いは Options の値をログに書き出すかどうかなので、正直無くても良いです。入れ替えずに実行すると、以下のように SampleOptions.Valuenull でも問題なく通ってしまいます。

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

標準の実装に入れ替えることで IOptions<T>.Value を参照した瞬間に、バリデーションが正しく行われて OptionsValidationException が投げられていることが確認できます。

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

この件については Issue を一応上げていますが、これまでの傾向から行くと修正される可能性は低そうです。なので標準の実装に入れ替えてしまうのが一番スマートでしょう。

ちょいちょい接続文字列を設定するのを忘れて実行時エラーというのを経験しているので、最低でも必須の値には付けておくのが良いと感じました。特に OSS で配布している場合は必須だと実感しました。

Azure Storage の Object Replication (Preview) を一通り試した

Build 2020 で発表はされたものの、全く使える気配がなかった Azure Storage の Object Replication ですが、最近になってようやく有効化されたので調べつつ基本的な機能を試しました。

Object Replication は Change Feed と Versioning の上に成り立っているので、それぞれの Resource Provider も登録が必要です。ドキュメントの通りにやれば問題ないですが、未だに Pending が続いているようです。

これまでも Azure Storage には GRS や RA-GRS が用意されていて、非同期で別リージョンにレプリケーションが可能でしたが、あくまでも DR 向けでしかなく、最近になって Account Failover によってユーザーが切り替えることが出来るようになりました。

Object Replication は上手く設計すれば HA 向けに使えそうだったので、発表の時から注目していました。

Object Replication の特徴

最初に Object Replication を一通り試して分かったことをまとめておきます。大体これで理解出来ます。

  • 1 つの Storage Account に 2 つの Replication Policy を設定可能
  • 1 つの Replication Policy に対して 10 の Rule を設定可能
  • Replication Policy 内では同一の Container を指定できない
    • Prefix を指定してレプリケーション先を分けることは出来ない
  • Policy 作成時に既存の Blob をコピーするか指定できる
    • 全てコピーする、指定した日付より新しいものをコピーするなど
  • レプリケーションの完了は概ね 5 分以内(ただし実測値)
    • Blob Change Feed の更新時間に左右されている気配
    • バッチで処理が実行されているっぽい、イベントドリブンではない
  • Service Endpoint / Private Endpoint で制限していても動作する
    • Firewall で全てのアクセスを制限しても動作した
  • レプリケーション先に指定した Storage Account は読み込み専用になる
    • 書き込もうとすると 409 が返ってくる

Preview 時点での情報なので、数に関する制限は GA 時に変わってくる可能性はあります。レプリケーション完了時間の SLA も用意されるかもしれません。

Replication Policy を作成する

Object Replication が有効化されると Azure Portal に項目が出てくるので、そこからレプリケーションの設定を行います。ちなみにレプリケーション先の Storage Account は Versioning のみ有効にしておけば良いです。

Azure CLI でも設定可能ですが Policy の指定が結構難しいので、Azure Portal での設定が一番簡単です。

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

レプリケーション先の Storage Account を選択すると、後は Source Container と Destination Container を設定するだけで最低限の利用が可能になります。

デフォルト設定では変更のあった Blob のみレプリケーション対象になりますが、Filter では Blob Path の Prefix が指定できるので特定のディレクトリだけを対象に出来ます。Prefix は複数指定できます。

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

Copy over は Rule 作成時の動作を指定すると考えればわかりやすいです。Everything を選択すると Rule 作成時に全ての Blob がレプリ先の Container にコピーされるので、既に Blob が存在する場合に便利です。

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

コピー対象となる Blob を作成時間ベースでも指定できるので、新しい Blob のみコピーという処理も簡単です。その場合はレプリ元との整合性が保たれないので、使い道には気を付けたいところです。

これまではレプリ元の Storage Account で設定していましたが、Replication Policy を作成するとレプリ先の Storage Account にも同様の設定が追加されるようになっています。

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

たまに片方だけ Policy が残ってしまうことがあるようで、その場合は一旦全て削除してからじゃないとエラーになるので注意が必要です。特に Resource Group の移動を行った場合には簡単に壊れるようでした。

Container 指定の制約

Source Container と Destination Container の指定は同一 Policy 内ではユニークである必要があります。以下のように同一の Source Container から別々の Destination Container への Policy は作成できません。

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

逆もしかりで、別々の Source Container から同一の Destination Container へも無理です。

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

前者は Filter と組み合わせてユニークであれば許して欲しいところですが、今のところは Change Feed や Storage Event を使ってコピーする処理を自作するしか対応法は無さそうです。

レプリケーション結果の確認

対応リージョンが少ないので同一のリージョンで試していますが、複数回試した結果で大体 5 分以内にレプリケーションが完了していました。別リージョンでもコピー自体は速いようなので同じぐらいです。

結局のところは Change Feed の更新は数分以内と言われているので、それにより全体的なレプリケーションのラグが決まってきているようです。GRS の RPO は 15 分以内らしいので、それよりは早いです。

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

正直もっと素早いものを期待していたので残念でした。Change Feed はもっと頑張れると思うので、今後の更新に期待したい気持ちはあります。

メタデータは Change Feed でキャプチャ出来るので、変更した場合にはレプリケーション先にも正しく反映されます。Blob Index Tags は Change Feed に対応していないので、現在は変更しても反映されません。

Change Feed に左右される部分が多いので、ドキュメントに目を通しておいた方が良さそうです。

Azure Portal から Blob を選択すると、どの Replication Policy によって処理されたかを確認できます。レプリ元とレプリ先でそれぞれ確認できるので、動作が怪しいと思った時に便利そうです。

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

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

レプリケーション完了時間が出ないのは不満ですが、今回のようにプラットフォーム側で動くものは処理されたか判別が難しいので、ログが残っているのは良い感じです。

当初は Account Failover みたいなことが出来るかなと思っていましたが、Replication Policy の切り替えを素早く行えない限り難しいと感じました。レプリラグがもっと小さくなって欲しい。