Azure DatabricksからData Lake Storage Gen2をマウントする

## はじめに
[Azure Data Lake Storage Gen2](https://docs.microsoft.com/ja-jp/azure/storage/blobs/data-lake-storage-introduction) (ADLS) は大量データ分析をするためのストレージサービスで、[Azure Data Lake Storage Gen1](https://docs.microsoft.com/ja-jp/azure/data-lake-store/index)の後継にあたる。Gen1は独立したストレージサービスだったが、これらの特長(性能、階層型名前空間、POSIXライクなアクセスコントロール等)を引き継ぎつつ、オブジェクトストレージであるBlobストレージをベースにして上位レイヤに分散ファイルシステムを作って実現をしたものだ。[コストもデータ容量においてはほとんど差が無い](https://azure.microsoft.com/ja-jp/pricing/details/storage/)（オペレーションにかかるコストには少し差があるようだが）。Azureにおいて分析用途のデータを格納しておくにはこれが最有力の選択肢になるだろう。さらにClouderaやHortonworksからもアクセスができるようだ。
今回は [公式ドキュメント](https://docs.databricks.com/spark/latest/data-sources/azure/azure-datalake-gen2.html)を参考にそのADLSにAzure Databricksから接続して分析やETL用途でアクセスする方法を試してみる。

## サービスプリンシパルの作成
サービスプリンシパルはリソース/サービスレベルの無人操作を実行する目的でテナント内で作成するAzure ADのユーザーIDで、雑に言うとシステムユーザーみたいな感じ。

Azure ADの管理画面から App registrations を選び、[+ New application registration] からサービスプリンシパルを作成する。
<img src="https://qiita-image-store.s3.amazonaws.com/0/144789/6f8a1d0e-2d0c-fd41-6929-3218a143b644.png" width=70%>

Name, Application type, Sign-on URLを設定する。Sign-on URLは使わないので適当に入力した。
<img src="https://qiita-image-store.s3.amazonaws.com/0/144789/9ec91a5a-250d-2ec1-3e08-876101fbffc5.png" width=50%>

作成ボタンを押し、サービスプリンシパルが作成されたら、作成したサービスプリンシパルの画面に移動して Application ID をコピーしてメモしておく。それからKeyを作成するために [Settings] をクリック。
<img src="https://qiita-image-store.s3.amazonaws.com/0/144789/24f26670-2ca7-1e6c-ca4a-e39f42c0f445.png" width=70%>

Keys の設定画面に移動してキー名を入力して、キーの期限を選択して [Save] をクリック。VALUE欄にキーが1度だけ表示されるので、コピーしてメモしておく。
<img src="https://qiita-image-store.s3.amazonaws.com/0/144789/2709b3f8-c7ef-0c51-010c-82f26f727671.png" width=100%>

ADLSの管理画面にいき、IAMの設定からRole Assignmenの追加をおこなう。作成したサービスプリンシパルに対してStorage Account Contributorを付与する。
<img src="https://qiita-image-store.s3.amazonaws.com/0/144789/6c337e46-387b-3064-20ee-2c12c0bcc905.png" width=100%>


## シークレットの作成

### Databricks CLI
Databricks CLIをインストール

```bash
pip install databricks-cli
```

ユーザートークンの設定

```bash
databricks configure --token  
# Databricksのホスト（https://japaneast.azuredatabricks.net 等）と、ユーザートークンを聞かれるので入力 (ユーザートークンを作ってなければ作成)
```

### ストレージアカウント用のsecret

scopeを作成

```bash
databricks secrets create-scope --scope <スコープ名> --initial-manage-principal users
```

secret keyの作成

```bash
databricks secrets put --scope <作成したスコープ名> --key <キー名>  
# エディタが開きいて下記が表示されます。

"この部分にストレージアカウントのキーを貼り付けて保存(ダブルクォート不要) "
# ----------------------------------------------------------------------  
# Do not edit the above line. Everything below it will be ignored.  
# Please input your secret value above the line. Text will be stored in  
# UTF-8 (MB4) form and any trailing new line will be stripped.  
# Exit without saving will abort writing secret.
```

### サービスプリンシパルサービスプリンシパル用のsecret

scopeを作成

```bash
databricks secrets create-scope --scope <スコープ名> --initial-manage-principal users
```

secret keyの作成

```bash
databricks secrets put --scope <作成したスコープ名> --key <キー名> 
# エディタが開きいて下記が表示されます。

"この部分にサービスプリンシパルのキーを貼り付けて保存(ダブルクォート不要)"
# ----------------------------------------------------------------------  
# Do not edit the above line. Everything below it will be ignored.  
# Please input your secret value above the line. Text will be stored in  
# UTF-8 (MB4) form and any trailing new line will be stripped.  
# Exit without saving will abort writing secret.
```

## ファイルシステム作成

ADLSでファイルシステムを作成していない場合は、Databricks側から作成することが可能。Databricks Notebook(Python)を開き以下のコードを実行する。Databricksのランタイムは5.1以上である必要がある。`dbutils.secrets.get(scope = "<scope-name>", key = "<key-name>"))`の部分は先に作成したストレージ用のsecret情報を使う。

```python
spark.conf.set("fs.azure.account.key.<ストレージアカウント名>.dfs.core.windows.net",  
dbutils.secrets.get(scope = "<scope-name>", key = "<key-name>"))  
spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "true")  
dbutils.fs.ls("abfss://<ファイルシステム名>@<ストレージアカウント名>.dfs.core.windows.net/")  
spark.conf.set("fs.azure.createRemoteFileSystemDuringInitialization", "false")
```

## ファイルシステムのマウント
準備が出来たらファイルシステムをマウントする。ここでの`"fs.azure.account.oauth2.client.secret": dbutils.secrets.get(scope = "<scope-name>", key = "<key-name>"),`部分はサービスプリンシパル用のsecretを使う。

```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": "<サービスプリンシパルのアプリケーションID>",  
             "fs.azure.account.oauth2.client.secret": dbutils.secrets.get(scope = "<scope-name>", key = "<key-name>"),  
             "fs.azure.account.oauth2.client.endpoint": "https://login.microsoftonline.com/<AADのファイルシステム名テナントID>/oauth2/token"}  

  dbutils.fs.mount(  
    source = "abfss://<ファイルシステム名>@<ストレージアカウント名>.dfs.core.windows.net/",  
    mount_point = "/mnt/<マウントポイント>",  
    extra_configs = configs)
```

## データへのアクセス
マウントしたのであとは通常のdbfsへのアクセスと同じように行う。

```python
df = spark.read.text("dbfs:/mnt/<マウントポイント>/<ファイル名>")
display(df)
```

シークレットの用意のためにDatabricks CLIを使うところがちょっと面倒だが、マウントしてしまうと後は全く他との違いを意識しなくても良いのでその点は楽だ。ADLSにデータを統合できると良いが、そうでなくてもAzure DatabricksはAzure内のストレージやDB等だいたいどこにデータがあってもアクセスできるので便利だ。