Azure Functions PowerShellでAzure CLI(az)が動かない原因と回避策:requirements.psd1の誤解から運用設計まで

PowerShell 版 Azure Functions から Azure CLI(az)でサブスクリプション自動作成を行いたいのに、「az が見つからない」と失敗する――この原因は requirements.psd1 の誤解にあります。本記事では、Az モジュールとの違いを整理しつつ、Function App 上で az を動かす現実的な回避策と設計上の落とし穴を解説します。

目次

PowerShell 版 Azure Functions で Azure CLI(az)が動かない:まず押さえる結論

結論から言うと、requirements.psd1 で有効化できるのは PowerShell の「モジュール」であり、Azure CLI(az)はモジュールではなく別の実行ファイル(コマンドラインツール)です。そのため 'Az' = '10.*' を有効化しても、Function App の実行環境に az コマンドが追加されるわけではありません。

PowerShell 版 Azure Functions の標準ランタイムには、通常 Azure CLI が同梱されていないため、Function から az を呼ぶと 「az が見つからない」というエラーになりがちです。そこで「どうしても az を動かす」場合は、Azure Files(Function App のコンテンツ共有)へ Azure CLI 本体を配置して呼び出す、または Azure CLI を含む実行基盤へ処理を寄せるのが現実解になります。

  • requirements.psd1 は PowerShell モジュール管理(Az モジュールなど)で、Azure CLI ではない
  • az を Function App 上で動かすなら「自分で置く」(または CLI を含む実行基盤へ移す)
  • サブスクリプション自動作成は、Az.Subscription(New-AzSubscriptionAlias)や REST API でも実現可能で、CLI 依存を減らせる

requirements.psd1 の正体:Az モジュールは「Azure CLI」ではない

PowerShell 版 Azure Functions には「Managed Dependencies」という仕組みがあり、requirements.psd1 に書いたモジュールを実行時に取得して利用できます。ここで指定できるのはあくまで PowerShell のモジュールであり、OS にインストールされるコマンド(実行ファイル)ではありません。

たとえば 'Az' = '10.*' を有効化すると、Azure PowerShell(Az モジュール)を PowerShell から叩けるようになります。ですが Azure CLI(az)は別物なので、az の実行ファイルが存在しない Function App では az は動きません。

比較項目Az モジュール(Azure PowerShell)Azure CLI(az)
形態PowerShell モジュール(PSGallery から取得)コマンドラインツール(実行ファイル)
導入方法requirements.psd1(Managed Dependencies)で参照MSI / ZIP / Docker などでインストール・配置
Function での扱いPowerShell の中で直接 cmdlet を呼ぶ外部プロセスとして az を起動する
出力の扱いやすさPowerShell オブジェクトとして扱える文字列/JSON(パースが必要)
運用面依存は psd1 でピン留めしやすいCLI 本体の配置・更新・パス管理が必要

つまり「PowerShell で Azure を触る」だけなら、まずは Az モジュール中心で設計するのが自然です。特にサブスクリプション作成のような管理系処理は、Az.Subscription の New-AzSubscriptionAlias や、Microsoft.Billing / Microsoft.Subscription の REST API でも実行できます。

Function App の実行環境で起きる「az が見つからない」典型パターン

Azure Functions(App Service ベース)の実行環境は、ローカル PC のように自由にインストールできるサーバーではありません。特に消費プランやスケールアウト構成では、インスタンスが増減し、ファイルシステムも「永続領域」と「一時領域」が混在します。そのため、開発端末では動く az が、Function App では動かないのは珍しくありません。

よくある状況現象背景
requirements.psd1 に Az を追加したaz が使えると思ったが変化なしAz は PowerShell モジュールで、Azure CLI(az)ではない
Function から az を呼んだ「az が認識されない」実行環境に CLI 実体がなく、PATH にも無い
wwwroot に CLI を置いた展開できない/書き込めないRun From Package などで wwwroot が読み取り専用になることがある

回避策:Azure CLI(CLI2)を Azure Files に配置して PowerShell から呼び出す

ここからは、質問で挙げられている「既知のワークアラウンド」を、実際にハマりやすいポイント込みで手順化します。前提はWindows ベースの Function Appで、Azure CLI の Windows 配布物(CLI2 ディレクトリ)を使うパターンです。

配置先は「%HOME%\data」配下にするのが無難

Function App(App Service)のファイルシステムは、ドライブレターを固定せず %HOME%(PowerShell なら $env:HOME)を起点に考えるのが安全です。実際のパス表記は環境差があるため、コードやドキュメントに D:\home をベタ書きするより、$env:HOME を使う方が移植性が上がります。

場所典型パス(Windows)性質用途の目安
アプリ本体%HOME%\site\wwwrootRun From Package だと読み取り専用になりやすい関数コード(run.ps1 / host.json など)
永続データ%HOME%\data比較的永続、スケールアウトでも共有されやすいCLI 本体、キャッシュ、設定ファイル置き場
一時領域%TEMP% / D:\local などインスタンスごと、一時的展開の一時置き、重い処理のローカルキャッシュ

手順:Function App が使うファイル共有(Azure Files)を特定する

Windows の消費プランや Premium など、動的スケールする Function App では、アプリのコンテンツ共有に Azure Files が使われる構成があります。代表的な設定値として、WEBSITE_CONTENTSHARE(ファイル共有名)と WEBSITE_CONTENTAZUREFILECONNECTIONSTRING(接続文字列)が使われます。まずは Function App の「構成(Configuration)」からこの 2 つを確認し、どのファイル共有にアップロードすべきかを特定します。

設定名意味確認ポイント
WEBSITE_CONTENTSHAREコンテンツ共有(Azure Files)の共有名Storage Account の「File shares」に同名の共有がある
WEBSITE_CONTENTAZUREFILECONNECTIONSTRINGAzure Files へ接続するための接続文字列この接続先ストレージに CLI を置く

手順:Azure CLI(CLI2)を用意する

Azure CLI を Windows に入れる方法は複数ありますが、Function App に「持ち込む」目的なら、ZIP 配布物か、ローカルにインストール済みの CLI2 ディレクトリ一式を使うのが現実的です。

  • ZIP 配布物を使う:管理者権限がない環境向けに ZIP パッケージが用意されています(プレビュー扱いの記載あり)。展開したフォルダ配下の az を呼び出します。
  • ローカル PC の CLI2 をコピーする:Windows では Azure CLI のインストール先が 32-bit と 64-bit で異なり、代表例として C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2 や C:\Program Files\Microsoft SDKs\Azure\CLI2 が挙げられます。いずれも az.cmd が ...\CLI2\wbin\az.cmd にあります。

手順:CLI2 を zip でアップロードして展開する

CLI2 はファイル数が多く、ファイル単位でアップロードすると時間がかかりやすいです。zip で固めてアップロードし、Function 側(Kudu のコンソール)で展開するのが運用上ラクです。

  1. ローカル PC で CLI2 を zip 化する
  2. Storage Explorer 等で、対象の Azure Files(コンテンツ共有)へ zip をアップロードする(例:%HOME%\data 相当の場所)
  3. Kudu(Advanced Tools)の Debug Console で zip を展開する

ローカルで zip を作る例(PowerShell):

Compress-Archive -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\*" -DestinationPath ".\CLI2.zip" -Force

Function 側で展開する例(Kudu の PowerShell コンソール想定):

$dest = Join-Path $env:HOME "data"
Expand-Archive -Path (Join-Path $dest "CLI2.zip") -DestinationPath $dest -Force

展開後、%HOME%\data\CLI2\wbin\az.cmd が存在することを確認します。

手順:profile.ps1 で az を呼べるようにする

PowerShell 版 Azure Functions では、profile.ps1 に初期化処理を書いておくことで、関数実行時に共通のセットアップができます。ここで az の呼び出しを関数化しておくと、run.ps1 側がシンプルになります。

ポイントは 3 つです。

  • パスは $env:HOME 起点で組み立てる
  • 関数ラッパーで @args を受け渡す(エイリアスより事故が少ない)
  • AZURE_CONFIG_DIR と AZURE_EXTENSION_DIR を永続領域へ(トークンキャッシュや拡張機能の置き場を安定させる)

profile.ps1 の例:

# profile.ps1(host.json と同じ階層に置く想定)
$cli2Root = Join-Path $env:HOME "data\CLI2"
$azCmd     = Join-Path $cli2Root "wbin\az.cmd"

if (Test-Path $azCmd) {
# az の設定/トークン/拡張機能を永続領域へ
$env:AZURE_CONFIG_DIR    = Join-Path $env:HOME "data.azure"
$env:AZURE_EXTENSION_DIR = Join-Path $env:HOME "data.azure\cliextensions"


# どこからでも az を叩けるようにラッパー関数化
function global:az {
    & $azCmd @args
}


} else {
Write-Host "Azure CLI not found: $azCmd"
}

動作確認:HTTP トリガーで az –version を返す

まずは「起動できるか」だけを確認すると切り分けが早いです。HTTP トリガーを 1 本作り、az --version の標準出力を返してみてください。

run.ps1 の例:

param($Request, $TriggerMetadata)

try {
$out = & az --version 2>&1 | Out-String


Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = 200
    Body       = $out
})


}
catch {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = 500
Body       = $_ | Out-String
})
}

ここでバージョン情報が返ってくれば、少なくとも「CLI 本体配置」「profile.ps1 読み込み」「外部プロセス起動」の 3 点は通っています。

認証:Managed Identity で az login –identity を使う

Function App から Azure CLI を叩く場合、認証をどうするかが次の関門です。推奨はFunction App の Managed Identity(システム割り当て/ユーザー割り当て)を使うことです。Azure CLI は Managed Identity でのログインをサポートしており、システム割り当てなら az login --identity、ユーザー割り当てなら --client-id / --object-id / --resource-id で対象 ID を指定してログインできます。

run.ps1 側で「ログイン済みか」を軽く確認してから必要に応じてログインする例:

function Ensure-AzLogin {
    & az account show --only-show-errors 1>$null 2>$null
    if ($LASTEXITCODE -ne 0) {
        & az login --identity --only-show-errors | Out-Null
    }
}

Ensure-AzLogin
& az account show -o json | ConvertFrom-Json

ユーザー割り当ての Managed Identity を使う場合の例:

# 例:環境変数に clientId を持たせておき、それを使う
$clientId = $env:AZURE_CLIENT_ID
& az login --identity --client-id $clientId --only-show-errors | Out-Null

拡張機能が必要な az コマンドへの備え

Azure CLI は、コマンドグループによっては拡張機能(az extension)を前提とするものがあります。Function App は「初回実行で拡張を自動インストール」するフローが、ネットワーク制限や書き込み先の問題で失敗することがあるため、必要な拡張は事前にインストールしておくか、少なくとも拡張の配置先(AZURE_EXTENSION_DIR)を永続領域へ固定しておくとトラブルを減らせます。

拡張の有無を確認する例:

& az extension list -o table

拡張を入れる例:

& az extension add --name <extension-name> --only-show-errors

サブスクリプション自動作成は「az 以外」でもできる:Az.Subscription と REST API

「クライアント登録のたびに新規サブスクリプションを作る」という要件だと、Azure CLI を動かす以前にどの契約形態(EA/MCA/MPA)で、どの API/コマンドが使えるかが重要になります。Microsoft Learn では、契約形態ごとにプログラムからサブスクリプションを作成する方法(REST API など)が整理されています。

PowerShell 寄りで実装したい場合、Az.Subscription の New-AzSubscriptionAlias が選択肢になります。これは Azure サブスクリプション作成要求に「エイリアス」を付けて作成する方式で、BillingScope(課金スコープ)を指定してサブスクリプションを作ります。

requirements.psd1 に必要モジュールだけを追加する例:

@{
  'Az.Accounts'      = '2.*'
  'Az.Subscription'  = '0.*'
}

run.ps1 のイメージ(BillingScope は契約形態によって形式が変わります):

# Managed Identity でログイン(例)
Connect-AzAccount -Identity | Out-Null

$aliasName = ("sub-" + [guid]::NewGuid().ToString("N"))
$subName   = "Client-001"
$scope     = "/providers/Microsoft.Billing/...(省略)"

$result = New-AzSubscriptionAlias -AliasName $aliasName -SubscriptionName $subName -BillingScope $scope -Workload "Production"
$result

もう一つの選択肢が REST API です。Microsoft.Subscription の Create Subscription API など、管理プレーンの API を直接呼ぶことで CLI 依存をなくせます(ただし必要な権限や契約条件があるため、事前に要件確認が必須です)。

トラブルシューティング:うまく動かないときのチェックリスト

az を Function App で動かす方式は「うまくいけば便利」ですが、ハマりポイントも多いです。よくある症状と原因を表にまとめます。

症状主な原因対処の方向性
'az' is not recognizedCLI2 の配置先が違う / profile.ps1 が読み込まれていない%HOME%\data\CLI2\wbin\az.cmd の存在確認、profile.ps1 の配置階層を確認
Cannot find ...\az.cmdzip 展開先がズレている(CLI2/CLI2 になっている等)ディレクトリ構造を Kudu で確認し、パスを修正
ERROR: Please run 'az login'認証が未実施 / トークンキャッシュが消えているaz login --identity を実行、AZURE_CONFIG_DIR を永続領域へ
毎回とても遅いコールドスタート、ファイル共有からの実行、毎回ログイントークンキャッシュの永続化、Premium/常時起動、実行場所の見直し
並列実行で失敗するCLI の設定/キャッシュの競合(同一 AZURE_CONFIG_DIR を共有)実行単位で config dir を分ける、あるいは CLI を外へ逃がす

「az 実行が遅い」典型原因と、改善の打ち手

Function App から az を叩くと遅くなりやすい理由は、主に次の積み重ねです。

  • コールドスタート:ワーカー起動・依存読み込みに時間がかかる
  • 外部プロセス起動コスト:az は呼ぶたびにプロセスが立ち上がる
  • ファイル共有 I/O:CLI2 を Azure Files 上で実行すると読み込みが遅くなりやすい
  • 認証の初期化:毎回 login している、またはキャッシュが効いていない

改善の方向性は 3 つに整理できます。

方向性効きやすいポイント具体例
実行基盤を強くするコールドスタート/CPU 制限Premium/専用プラン、常時起動、スケール戦略の見直し
CLI 配置を工夫するファイル共有 I/O起動時に一時領域へコピーしてローカル実行(インスタンス単位キャッシュ)
初期化を減らす認証/設定の再作成AZURE_CONFIG_DIR を永続化、login 済み判定、不要な az 拡張の削減

なお、Run From Package を使っている場合は wwwroot が読み取り専用になりやすいため、CLI 本体やキャッシュを %HOME%\data 配下に寄せる設計が安全です。

セキュリティ面での注意:サブスクリプション作成は権限が強い

サブスクリプションの自動作成は便利ですが、実行主体(Managed Identity / Service Principal)に付与する権限は強くなりがちです。付与範囲を広げすぎると「誤作成」「想定外の課金スコープへの紐づけ」「不要な権限拡大」につながります。最小権限の原則で、課金スコープ(BillingScope)単位・管理グループ単位などで権限を絞り、ログ(Activity Log / Azure AD サインインログ)も必ず追えるようにしておくのが安全です。

設計面の注意:Azure Functions で az を動かす前に検討したい代替案

CLI を Function に押し込む方法は「その場しのぎ」としては成立しますが、運用が長期化するほど更新・検証・障害対応の負担が増えます。特に「サブスクリプション自動作成」は権限も強くなりがちなので、実行基盤の選定は慎重に行うのが安全です。

選択肢向いているケースメリット注意点
Az モジュール(PowerShell)PowerShell で完結させたいオブジェクト指向で扱いやすく、requirements.psd1 で管理しやすいモジュールが重いとコールドスタートに影響。必要最小限に絞る
REST API 直叩きCLI 依存を消したい最短距離で実装でき、実行環境が軽くなるAPI バージョン/権限/契約条件の理解が必要
Deployment Script(Azure CLI/PowerShell)IaC の流れで CLI 実行したいCLI 実行を前提にした仕組みで、テンプレートと統合しやすい実行はコンテナ(Linux)で行われるなど制約あり
コンテナ実行基盤(ACI 等)CLI 前提のバッチを安定稼働させたい必要ツールをイメージに固定でき、再現性が高い運用・監視・ネットワーク設計が Functions より重くなることも

特に Deployment Script は、Azure Resource Manager の仕組みとして Azure CLI / Azure PowerShell スクリプトを実行できる選択肢で、テンプレート展開と一緒に「どうしても CLI でやりたい処理」を閉じ込めるのに向きます。

関連ドキュメントを探すための検索キーワード

「公式ドキュメントを読みたい」という場合は、次のキーワードで検索すると一次情報に辿り着きやすいです(URL は環境で変わるので、タイトル検索がおすすめです)。

  • Azure Functions PowerShell developer reference / functions-reference-powershell(requirements.psd1 / profile.ps1)
  • Install Azure CLI on Windows(ZIP package / CLI2 のインストール先)
  • Authenticate Azure CLI managed identity(az login –identity)
  • Azure Functions storage considerations(WEBSITE_CONTENTSHARE / Azure Files)
  • Deployment scripts in ARM templates(Microsoft.Resources/deploymentScripts)
  • Programmatically create Azure subscriptions(New-AzSubscriptionAlias / BillingScope)

まとめ

PowerShell 版 Azure Functions で az が動かないのは、requirements.psd1 が Azure CLI を導入する仕組みではなく、Az モジュールなど PowerShell モジュールの依存管理であることが主因です。どうしても Function App 上で Azure CLI を実行したい場合は、CLI2 一式を Azure Files(%HOME%\data 配下)へ配置し、profile.ps1 でラッパーを作って呼び出すのが実務的な回避策になります。

ただし、この方式は更新や性能面の課題が出やすいので、サブスクリプション自動作成のような基盤系処理では、Az.Subscription(New-AzSubscriptionAlias)や REST API、Deployment Script/コンテナへの移管も含めて、運用コストと安全性のバランスで最適解を選ぶことをおすすめします。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次