Azure Machine Learning(Azure ML)でローカル CSV の uri_file は動くのに、同じコードを ADLS Gen2(Azure Data Lake Storage Gen2)のパスに切り替えた途端、ジョブ実行中に PermissionDenied を吐く――この“あるある”を根本原因から解消するための実践ガイドです。実行主体(Managed Identity)と ADLS Gen2 の二重ガード(RBAC と POSIX ACL)、さらにネットワーク・YAML の落とし穴まで、現場でつまずくポイントをすべて可視化し、再発しない設計に落とし込みます。
現象:Studio では見えるのに、ジョブでは PermissionDenied
Azure ML のデータアセットでローカル CSV を指定した type: uri_file は正常に動作。しかし、ADLS Gen2 上のファイルを azureml://datastores/<データストア名>/paths/<filesystem>/<path>/file.csv の形式で参照すると、ジョブ実行時に以下のようなエラーが発生します。
ScriptExecution.StreamAccess.Authentication → PermissionDenied: This request is not authorized ...
ところが Azure ML Studio 上ではファイルをブラウズできるため、「権限はあるはず」と判断しがちです。この矛盾こそ、トラブルの正体です。
根本原因:実行主体の違いと ADLS Gen2 の“二重ガード”
Azure ML のジョブはコンピュートに紐づく Managed Identity(MI) の権限で動きます。一方、Studio のブラウズはユーザー本人またはワークスペース MIの権限で行われます。ここで実行主体が食い違うため、「見えるのに動かない」という状態が生まれます。さらに ADLS Gen2 は、データプレーンのAzure RBAC(Storage Blob Data ロール)とPOSIX 風 ACLという二重の権限制御を持ち、どちらか一方でも不足しているとアクセスが拒否されます。
| 操作主体 | どこで使われる | 必要な権限 | よくある誤解 |
|---|---|---|---|
| ユーザー本人 | Studio でのブラウズ/プレビュー | ユーザーに付与された RBAC/ACL | 「自分に見える=ジョブにも見える」と思い込み |
| ワークスペース MI | Studio の一部バックエンド操作 | ワークスペース MI の RBAC/ACL | ジョブと同じ権限だと思い込み |
| コンピュート MI(重要) | ジョブ本番実行時 | コンピュート MI の RBAC + ACL + ネットワーク到達 | 管理プレーンの「Reader/Contributor」で足りると勘違い |
前提知識:Managed Identity の種類と“どれが使われるか”
Azure ML のコンピュート(Compute Cluster / Compute Instance)には、システム割り当て MI または ユーザー割り当て MI(UAMI) を付与できます。ジョブは原則として「そのジョブが走るコンピュートに関連付いた MI」で外部ストレージへアクセスします。ワークスペース MI やユーザー本人ではありません。
| コンピュート種別 | 既定の実行主体 | 備考 |
|---|---|---|
| Compute Cluster | クラスターに付与した MI | スケールアウト時も同一 MI を利用 |
| Compute Instance | インスタンスに付与した MI | 開発・デバッグ時はこれが実行主体 |
| Job/Component | 参照先コンピュートの MI | データアセットの参照主体ではなく “実行主体” が重要 |
完全解決:RBAC + ACL + ネットワーク + YAML を順に正す
1. コンピュートの Managed Identity を特定する
Azure ML Studio または Azure Portal で、対象コンピュート → Identity からオブジェクト ID を控えます。CLI なら以下も有効です。
# コンピュートの MI を確認(例:CLI v2)
az ml compute show -n <compute-name> -g <rg> -w <workspace> --query identity
UAMI を使う場合は、UAMI のクライアントID/オブジェクトIDも併せて確認します。
2. ストレージ側で「データプレーン」の RBAC を付与する
ストレージアカウントまたはファイルシステム(コンテナー)の IAM で、コンピュート MI に Storage Blob Data ロールを割り当てます。管理プレーン用の Reader/Contributor ではデータ読み書きはできません。
| ロール名 | 用途 | 代表的な可否 |
|---|---|---|
| Storage Blob Data Reader | 読み取り専用 | 読み取り:〇 / 書き込み:× / 削除:× |
| Storage Blob Data Contributor | 読み書き | 読み取り:〇 / 書き込み:〇 / 削除:〇 |
| (注意)Reader/Contributor | 管理プレーン | データ平面アクセス:×(誤用に注意) |
CLI 例:
# Data Reader を付与(スコープはファイルシステム推奨)
az role assignment create \
--assignee <コンピュートMIオブジェクトID> \
--role "Storage Blob Data Reader" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<sa>/blobServices/default/containers/<fs>
3. ファイルシステム(ADLS Gen2)の POSIX ACL を付ける
ADLS Gen2 は RBAC に加え、ACL の通行証が必要です。親ディレクトリには x(トラバース)を、対象フォルダ/ファイルには r-x(読み取り+実行)をコンピュート MI に付与します。書き込みが必要なら rwx を検討します。新規ファイルへ継承したい場合は default ACL も設定します。
# 単一パスに ACL 付与(読み取りのみの例)
az storage fs access set \
--account-name <sa> \
--file-system <fs> \
--path <path> \
--acl "user:<コンピュートMIオブジェクトID>:r-x"
# ディレクトリ配下へ再帰付与(既存の深い階層にも反映)
az storage fs access set-recursive
--account-name
--file-system
--path
--acl "user:<コンピュートMIオブジェクトID>:r-x"
# 継承用 default ACL(新規作成物に自動付与)
az storage fs access set
--account-name
--file-system
--path
--acl "default:user:<コンピュートMIオブジェクトID>:r-x"
ACL の落とし穴:有効権限は mask により抑制されることがあります。対象ディレクトリの mask:: が十分に開いているか(例:mask::r-x)も併せて確認しましょう。
4. ネットワーク到達性(Firewall / VNet / Private Endpoint)を整える
- ストレージにファイアウォールがある場合:コンピュートが属するサブネットを許可。
- Private Endpoint を使う場合:コンピュートからの名前解決が PE 宛てになるよう DNS を構成。
- 「信頼済み Microsoft サービスからのアクセスを許可」を使うかは、組織のセキュリティ方針に従う。
5. YAML/SDK の path と type を見直す
ADLS Gen2 をデータアセットで参照する際は、ローカルパスではなく azureml:// 形式を使います。ファイルなら uri_file、フォルダなら uri_folder を指定します。
# データアセット(単一ファイル)
name: my-csv
version: 1
type: uri_file
path: azureml://datastores/<データストア名>/paths/<filesystem>/<path>/file.csv
description: "ADLS Gen2 上の CSV"
# データアセット(フォルダ)
name: my-folder
version: 1
type: uri_folder
path: azureml://datastores/<データストア名>/paths///
ジョブから利用する例:
# command ジョブの例
command: python train.py --data ${{inputs.data}}
inputs:
data:
type: uri_folder
path: azureml://datastores/<データストア名>/paths/<filesystem>/<path>/
compute: azureml:<compute-name>
environment: azureml://registries/azureml/environments/sklearn-1.2/labels/latest
6. 5分でできる動作確認:MI で ADLS に入れるか単体テスト
ジョブの前に、コンピュート上から MI を使って ADLS Gen2 にアクセスできるかを確認します。最小コードは以下です。
from azure.identity import ManagedIdentityCredential
from azure.storage.filedatalake import DataLakeServiceClient
account = ""
filesystem = ""
path = "/file.csv" # or directory
cred = ManagedIdentityCredential() # コンピュート MI を使用
dfs = DataLakeServiceClient(f"https://{account}.dfs.core.windows.net", credential=cred)
fs_client = dfs.get_file_system_client(filesystem)
# 存在確認
try:
file_client = fs_client.get_file_client(path)
props = file_client.get_file_properties()
print("OK:", props.size)
except Exception as e:
print("NG:", e)
ここで失敗する場合は、RBAC / ACL / ネットワークのいずれかに欠落が残っています。順に切り分けましょう。
切り分けのためのチェックリスト
- 誰の MI で実行されている? コンピュートの Identity 画面でオブジェクト ID を再確認。
- RBAC はデータプレーンのロール? Storage Blob Data ○○ をコンテナー(ファイルシステム)や必要に応じアカウントに割り当て。
- ACL は親ディレクトリまで貫通? すべての親ディレクトリに
x、対象にr-x(またはrwx)。 - mask の抑制なし?
mask::を確認。グループ経由付与の場合は特に注意。 - ネットワークは到達する? Private Endpoint の DNS 解決/ルーティング、ストレージのファイアウォール設定を確認。
- YAML の
pathはazureml://形式? ローカルパスやabfss://直書きと混在していないか。
よくある落とし穴と対策
- 「管理プレーンの Reader/Contributor を付けたから大丈夫」:
データアクセスには無効。必ず Storage Blob Data Reader/Contributor を付ける。 - ACL をファイルだけに付けて親を忘れる:
親にxがないと到達不能。深い階層はset-recursiveで一括反映。 - ユーザーで見えた=ジョブでも見えると誤認:
Studio の閲覧主体とジョブの実行主体は別。コンピュート MI に合わせて整備。 - Private Endpoint 導入後の名前解決:
コンピュートから<sa>.dfs.core.windows.netが PE に解決されるか確認。DNS フォワーダーやホストファイルの暫定対処は推奨せず、正式な DNS 構成を。 - default ACL を設定し忘れ:
パイプライン中に新規生成するパスへ権限が継承されず途中で失敗。default ACL を事前に付与。 - マウントとダウンロードの挙動差:
大規模データでは mount が選ばれる場合があり、ネットワーク制限に敏感。どちらでも通るようにネットワーク/権限を揃える。
権限設計のベストプラクティス
- UAMI の活用で実行主体を固定:複数クラスター/インスタンスに同じ UAMI を付ければ、RBAC/ACL の付与先を一元化でき、運用が安定します。
- 最小権限の原則:読み取りだけなら Storage Blob Data Reader と
r-x、書き込み要件が出たときに昇格。 - コンテナー単位で境界を設計:機能やデータ分類ごとにファイルシステムを分割し、RBAC スコープも分ける。
- default ACL で“将来のファイル”もカバー:パイプラインが動的に作るパスへ権限が継承されるように設計。
- 監査と検証を自動化:デプロイパイプラインに RBAC/ACL の検査ステップ(CLI/SDK)を組み込み、ドリフトを検知。
運用に効くコマンド集(再掲+補足)
# 付与済み RBAC の確認
az role assignment list --assignee <コンピュートMIオブジェクトID> --scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<sa> -o table
# パスの ACL を表示
az storage fs access show
--account-name
--file-system
--path
# 配下の ACL を集計(必要に応じて)
az storage fs access list-recursive
--account-name
--file-system
--path
--max-results 100
ケーススタディ:ローカルから ADLS へ切り替える時の最短手順
- データアセットの
pathをazureml://datastores/<ds>/paths/<fs>/<path>に修正。 - コンピュート MI を特定(UAMI 利用なら UAMI を確認)。
- ストレージの IAM に Data Reader/Contributor を付与(スコープは最低限:コンテナー配下)。
- ACL を設定(親に
x、対象にr-x、必要なら default ACL も)。 - ネットワーク到達を確認(ファイアウォール/PE/DNS)。
- コンピュート上で MI を用いた単体テスト(上記 Python コード)。
- ジョブを再実行(ログに
PermissionDeniedが消え、I/O が流れることを確認)。
トラブル再現と解消の思考フロー
[エラー発生]
│ ScriptExecution.StreamAccess.Authentication → PermissionDenied
▼
[実行主体は?]
├─ ユーザー/WS MI → (誤判定)Studioでは見える
└─ コンピュート MI(これが正)
│
├─ RBAC:Storage Blob Data ロールは付いたか
├─ ACL:親 x / 対象 r-x / mask は妥当か
├─ ネットワーク:FW/VNet/PE/DNS は整合か
└─ YAML:azureml:// 形式・type (uri_file/folder) 正しいか
FAQ
Q. Studio でプレビューできるのにジョブが失敗します。
A. 実行主体が異なります。Studio はユーザー/ワークスペース MI、ジョブはコンピュート MI。後者に RBAC(Storage Blob Data ロール)と ACL(親 x・対象 r-x 以上)を付け直してください。
Q. データストアを作り直す必要はありますか?
A. ほとんどのケースで不要です。権限設定(RBAC と ACL)とネットワーク/パスの見直しで解消します。
Q. Studio でファイル名が「error」と表示されます。
A. プレビュー失敗時の UI 表示上の問題で、実ファイル名とは無関係です。権限が整えばジョブ自体の入出力は正常になります。
Q. 書き込みも行いたい場合は?
A. RBAC を Storage Blob Data Contributor にし、ACL を rwx(または必要最小)へ。新規生成物へ権限を継承させるため default ACL を追加してください。
Q. SAS やアカウントキーで回避できますか?
A. 一時的には可能ですが、セキュリティ/運用の観点では MI+RBAC+ACL の標準パターンを推奨します。鍵管理の負担や配布リスクを避けられます。
Q. パイプラインの別ステップで別のコンピュートを使う場合は?
A. ステップごとにそのステップのコンピュート MIが実行主体になります。各コンピュート MI に同等の RBAC/ACL を用意するか、UAMI を共通化して付与しましょう。
確認用テンプレート:最小構成のデータアセット & ジョブ
以下は、ADLS Gen2 の特定ファイルを読み取るだけの最小テンプレートです。まずはこれで通るかを確認しましょう。
# data.yml
name: sample-csv
type: uri_file
path: azureml://datastores/<ds>/paths/<fs>/<path>/file.csv
version: 1
description: "ADLS 上の CSV の最小例"
# job.yml
command: >
python -c "import pandas as pd; print(pd.read_csv('${{inputs.csv}}').head())"
inputs:
csv:
type: uri_file
path: azureml://datastores/<ds>/paths/<fs>/<path>/file.csv
compute: azureml:<compute-name>
environment:
image: mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:20230815.v1
# 作成&実行(CLI v2 の例)
az ml data create -f data.yml -g <rg> -w <ws>
az ml job create -f job.yml -g <rg> -w <ws>
まとめ:再発しない“型”を作る
- Studio で見えるのは“ユーザー/WS MI”。本番ジョブは“コンピュート MI”。
- ADLS Gen2 は RBAC(Storage Blob Data ロール) と POSIX ACL の両方が必須。
- ネットワーク(FW/VNet/PE/DNS)と
azureml://の正しいパス指定も忘れずに。 - UAMI を使って実行主体を固定し、RBAC/ACL を IaC で一元管理すると運用が劇的に楽になります。
参考:冒頭の「推奨手順」要約
- コンピュートの MI の オブジェクト ID を特定。
- ストレージの IAM でその MI に Storage Blob Data Reader/Contributor を割り当て。
- 対象のファイルシステム/パスに ACL を付与(親
x、対象r-x、必要に応じ default ACL)。 - ストレージの ファイアウォール/VNet/PE と DNS を整える。
- YAML の
path: azureml://datastores/<ds>/paths/<fs>/<path>とtype: uri_file / uri_folderを確認。
補足・豆知識
- 二重の権限制御が特徴:RBAC と ACL のどちらか一方だけでは足りません。
- Studio の「error」表示:プレビュー失敗時の UI 上の問題。ファイル名自体は壊れていません。
- データストアの再登録は不要:権限とネットワークの修正で解決します。
以上で、Azure ML ジョブが ADLS Gen2 のデータアセットにアクセスできない(PermissionDenied)問題を、誰が実行するのか・どの権限が足りないのか・どこに届いていないのかの3軸で根治できます。自動化と標準化で、今後のプロジェクトでも同じパターンを再利用してください。

コメント