はじめに
PowerShell スクリプトを単体で動かすと正常に動作するけど、タスクスケジューラやジョブ管理ツールなどから同時に複数実行すると上手くいかないっていうことはよくある話かと思います。
今回は、マネージド ID を使って Azure リソースを操作する PowerShell スクリプトについて、どうして前述のような現象が発生するのか見ていきたいと思います。
検証環境
以下のように、仮想マシン (Windows) 上で、同一マネージド ID を使って Azure リソースを操作する PowerShell スクリプトを同じユーザーで同時に複数セッションで実行してみました。
この投稿では、ユーザー割り当て済みマネージド ID を使っていますが、システム割り当て済みマネージド ID でも同様のことが言えます。
実行する PowerShell スクリプトは、このように書いていました。
# マネージド ID のクライアント ID
$clientId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
# Azure サブスクリプション ID
$subscriptionId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
# Azure に接続
Connect-AzAccount -Identity -AccountId $clientId
# Azure サブスクリプションを指定
Set-AzContext -Subscription $subscriptionId
# これ以降に自動化したい操作を実装
Get-AzResource
気をつけること
問題点 : Azure コンテキストはキャッシュされる
Azure サブスクリプションと認証情報を持つ Azure コンテキストは、既定で PowerShell セッション間で使用するために、ユーザープロファイル配下にキャッシュします。
前述したように、PowerShell スクリプトを同じユーザーで同時に複数実行すると、Azure に接続した後の処理で正常に動作しない (単体で実行した時と同様の結果が得られない) おそれがあります。
おそらく、各 PowerShell セッションから認証トークンが格納され、自 PowerShell セッションが取得した認証トークンとは異なるものを用いて Azure リソースを操作しようと試みた結果、その内容に誤りがあると拒否されたと思われます。
※あくまで個人の見解です。
解決策
Connect-AzAccount
、Set-AzContext
コマンドレッドの -Scope
パラメーターで Process
を指定することで、Azure コンテキストは、現在のセッションでのみ有効となり、ユーザープロファイル配下に自動的に保存されません。
冒頭の PowerShell スクリプトを以下のように修正します。これで Azure コンテキストが競合されてしまう問題は解決できます。
# マネージド ID のクライアント ID
$clientId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
# Azure サブスクリプション ID
$subscriptionId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
# Azure に接続
Connect-AzAccount -Scope Process -Identity -AccountId $clientId
# Azure サブスクリプションを指定
Set-AzContext -Scope Process -Subscription $subscriptionId
# これ以降に自動化したい操作を実装
Get-AzResource
なお、この対策は Azure コンテキストが自動的に保存されないだけであって、保存されているものがクリアされるわけではありません。保存された Azure コンテキストをクリアするには、Clear-AzContext
コマンドレッドを使います。
問題点 : IMDS には制約がある
マネージド ID を使って Azure に接続する (Connect-AzAccount
コマンドレッドを実行する) 場合、各仮想マシンの IMDS へ要求しますが、これには以下の制限があります。
- マネージド ID に適用されるレート制限はありますか?
マネージド ID の制限には、Azure サービスの制限、Azure Instance Metadata Service (IMDS) の制限、および Azure Active Directory サービスの制限に対する依存関係があります。
IMDS: 一般に、IMDS への要求は、1 秒あたり 5 つの要求に制限されます。 このしきい値を超える要求は、429 の応答で拒否されます。 マネージド ID カテゴリに対する要求は、1 秒あたり 20 個の要求、同時要求数は 5 個に制限されます。 詳細については、「Azure Instance Metadata Service (Windows)」の記事を参照してください。
要約すると、
IMDS への要求は、仮想マシンごとに
- 1 秒あたり 20 個の要求まで
- 同時に 5 個までの要求まで
という制限があります。 なお、この制限を引き上げることはできません。
この制限によって、同時に大量の PowerShell セッションから認証およびアクセストークン取得の要求を行うと、このしきい値を超える要求分は、429 の応答で拒否されるおそれがあります。
解決策
解決方法としては、以下の 2 点が考えられると思います。
- 同時に行う要求数をこの制限のしきい値を超えないように調整する
- 呼び出し元 (PowerShell スクリプト側) でリトライ処理を実装する
なお、Connect-AzAccount
コマンドレッドでは、IMDS のスロットリングを検知して、リトライする仕組みは実装されています。しかし、このリトライの回数を超えてエラーとなる可能性も拭いきれません。
呼び出し元でリトライ処理を実装する場合は、以下の IMDS エンドポイントに対する「再試行のガイダンス」が参考になります。
- 再試行ガイダンス
ということで、必要に応じて上記の 2 点で対処しましょう。
前者のほうが業務なことも含めて調整が難しいと思いますので、後者のほうがベストかなと思います。
まとめ
仮想マシンから、自動化でマネージド ID を使って Azure リソースを操作する際 (PowerShell スクリプト) は、以下の点について気をつけましょう。
-
Connect-AzAccount
コマンドレッドは既定で Azure コンテキストをキャッシュする- Azure コンテキストのスコープを現在のプロセスでのみ有効にする
- Connect-AzAccount -Scope Process ...
- Set-AzContext -Scope Process ...
- Azure コンテキストのスコープを現在のプロセスでのみ有効にする
- IMDS への要求には仮想マシンごとに制限がある
- 同時に行う要求数をこの制限のしきい値を超えないように調整して回避する
- 呼び出し元でリトライを実装して回避する