Azure Databricks + ADLS Gen2 で403エラーに遭遇したらこれを読め

## はじめに

Azure DatabricksでADLS Gen2のファイルを加工・分析したいことは多いと思います
ググれば、DatabricksのクラスターにADLS Gen2をマウントする方法がたくさん出てきますが、
今回遭遇した403エラーはなかなか解決策を見つけられず苦労したのでメモとして残しておきます
**マウントした後、Notebookで加工・分析する具体的な実装については触れていません**

## 403エラーに遭遇するまで

### 公式チュートリアルを見ながら進める

[こちら](https://docs.microsoft.com/ja-jp/azure/databricks/data/data-sources/azure/adls-gen2/azure-datalake-gen2-sp-access)のチュートリアルを実施
ADLS Gen2は作成済のものを使いましたが、それ以外はチュートリアル通りに進めました
サービスプリンシパル等もろもろ準備ができたので、DatabricksにNotebookを作成して、いざマウント！！

```python:マウント処理（python）
configs = {"fs.azure.account.auth.type": "OAuth",
           "fs.azure.account.oauth.provider.type": "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider",
           "fs.azure.account.oauth2.client.id": "<application-id>",
           "fs.azure.account.oauth2.client.secret": dbutils.secrets.get(scope="<scope-name>",key="<service-credential-key-name>"),
           "fs.azure.account.oauth2.client.endpoint": "https://login.microsoftonline.com/<directory-id>/oauth2/token"}

# Optionally, you can add <directory-name> to the source URI of your mount point.
dbutils.fs.mount(
  source = "abfss://<container-name>@<storage-account-name>.dfs.core.windows.net/",
  mount_point = "/mnt/<mount-name>",
  extra_configs = configs)
```

### `dbutils.fs.mount()` で403エラー

`This request is not authorized to perform this operation` だそうです

```
ExecutionError: An error occurred while calling o267.mount.
: Operation failed: "This request is not authorized to perform this operation.", 403, HEAD, https://<account>.dfs.core.windows.net/<container>/?upn=false&action=getAccessControl&timeout=90
	at shaded.databricks.azurebfs.org.apache.hadoop.fs.azurebfs.services.AbfsRestOperation.execute(AbfsRestOperation.java:200)
	at shaded.databricks.azurebfs.org.apache.hadoop.fs.azurebfs.services.AbfsClient.getAclStatus(AbfsClient.java:750)
	at shaded.databricks.azurebfs.org.apache.hadoop.fs.azurebfs.services.AbfsClient.getAclStatus(AbfsClient.java:732)
	at shaded.databricks.azurebfs.org.apache.hadoop.fs.azurebfs.AzureBlobFileSystemStore.getIsNamespaceEnabled(AzureBlobFileSystemStore.java:299)
    ……省略
```

## 原因

**結論：ADLS Gen2のFireWallでDatabricksからのアクセスがブロックされていた**

* システム割り当てマネージドIDによるアクセス許可：Databricksをサポートしていない
* `信頼された Microsoft サービスによるこのストレージ アカウントに対するアクセスを許可します`をON：[信頼されたMicrosoftサービス](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-network-security?tabs=azure-portal#exceptions)にDatabricksは含まれない  
Databricksもアクセスできるのかと思っていたら違っていました、、、  
~~チェックボックスの横にあるInformationアイコンから信頼されたサービスのリストを確認できるんだから、ちゃんと確認しとけば、、、~~

つまり、**DatabricksからのアクセスはFWで許可されていない**状態でした

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1104230/84e8d004-e6c3-ba22-42eb-da3e8277e4e3.png)

## FW設定は有効にしたまま、Databricksからアクセスしたい！

以下で実現できます

* [仮想NW内にDatabricksをデプロイ（VNetインジェクション）](https://docs.microsoft.com/ja-jp/azure/databricks/administration-guide/cloud-configurations/azure/vnet-inject)
* 仮想NWにADLS Gen2への[サービスエンドポイント](https://docs.microsoft.com/ja-jp/azure/virtual-network/virtual-network-service-endpoints-overview)を作成
* ADLS Gen2のFW設定で当該仮想NWのpublic subnetを許可

なお、Databricksを作成する時にしかVNetに関連付けることはできないので、既に作成している場合はDatabricksワークスペースから作り直しです
[移行する方法](https://docs.microsoft.com/ja-jp/azure/databricks/scenarios/howto-regional-disaster-recovery#detailed-migration-steps)も紹介されていますが、私はNotebookをDRC形式でExportして、ワークスペースやクラスターを手動で再作成した後でDRCをインポートする方法で移行しました

## 最終的な状態

### イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1104230/21751d55-fffc-93c7-a8c2-cdaf607217ae.png)

### ADLS Gen2 FW設定
![2021-03-18_15h02_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1104230/f24d02a6-1921-dfce-6b20-1990fd029e04.png)

### VNet サービスエンドポイント設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1104230/5c3f7df3-3e4f-209b-c537-cbf7a406fe80.png)

### その他

* VNet内のDatabricksからKey Vaultへのアクセスについて  
DatabricksでADLS Gen2をマウントする際に、サービスプリンシパル + OAuthで認証していますが、サービスプリンシパルのシークレットをKey Vaultに保存しています。Key VaultもStorageと同様に、**①Key Vault側のFWでVNetを許可** & **②VNet側でKey Vaultへのサービスエンドポイントを設定**が必要かと思ったのですが、設定しなくても問題ありませんでした。KeyVaultでは[例外として許可されるサービス](https://docs.microsoft.com/ja-jp/azure/key-vault/general/overview-vnet-service-endpoints#trusted-services)にDatabricksが含まれているからですね
* VNet内にpublic/private Subnetを作るのですが、**DatabricksのリソースがどのSubnetにどう配置されるのか？**
[仮想NWの要件](https://docs.microsoft.com/ja-jp/azure/databricks/administration-guide/cloud-configurations/azure/vnet-inject#virtual-network-requirements)として以下のような記載がありましたが、クラスターはprivateに配置されるのか？といった所はイマイチ理解できていません  

    > * **サブネット**: VNet には、Azure Databricks ワークスペース専用の2つのサブネットが含まれている必要があります。コンテナーサブネット (プライベートサブネットと呼ばれることもあります) とホストサブネット (パブリックサブネットと呼ばれることもあります) です。

## チェックするポイント

最終的にADLS Gen2のFW設定が原因でしたが、他にもチェックすべきポイントをまとめておきます

* ADLS Gen2
    * 「許可するアクセス元」が「すべてのネットワーク」であれば問題なし
    * **「選択されたネットワーク」にする必要がある場合、Databricksを仮想NW内にデプロイする**
* サービスプリンシパル
    * ストレージBlobデータ共同作成者など、適切なIAMロールを付与しているか
    * アプリケーションID、テナントID、シークレット…などをあっちこっちにコピペする際に間違えていないか
    * IAMロールのスコープ[^1]

## 参考

* [stackoverflow](https://stackoverflow.com/questions/54018584/azure-databricks-accessing-blob-storage-behind-firewall)
    * **VNetにDatabricksをデプロイする**という解決策が記載されていました
    * もう一つの解決策として紹介されていたのが**AzureのDCのIPアドレスを全部ホワイトリストに入れちゃえよ**という案も提示されていましたが、やめておきました

[^1]: [こちらのページ](https://docs.microsoft.com/ja-jp/azure/storage/blobs/data-lake-storage-use-databricks-spark#prerequisites)によると、`親リソース グループまたはサブスクリプションにロールを割り当てることはできますが、それらのロール割り当てがストレージ アカウントに伝達されるまで、アクセス許可関連のエラーが発生します。`といった記述がありました。伝達されるまで時間がかかるということでしょうか。セキュリティの観点からもスコープは最低限にしたほうが望ましいので、サブスクリプションスコープではなくストレージアカウントのスコープでIAMロールを付与することもご検討ください。