【Databricks】Unity Catalog を使用してS3へ接続してみた

# 背景・目的
[Unity Catalog を使用したクラウド オブジェクト ストレージへの接続](https://docs.databricks.com/ja/connect/unity-catalog/index.html)を整理し、S3への接続を試してみます。

# まとめ
下記の特徴があります。
- Databricksでは、オブジェクトストレージに接続する方法をいくつか提供しているが、UnityCatalogを介してクラウドオブジェクトへの接続を管理することを推奨しています。
- 下記のような環境を構築し、DatabricksからS3へ接続してみました。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/81cfb9ef-e486-4d00-fabb-4d1a2f6a6c08.png)

# 概要
> Databricks では、Unity Catalog を使用して、クラウド オブジェクト ストレージに格納されているすべてのデータへのアクセスを管理することをお勧めします。
> Unity Catalog には、クラウド オブジェクト ストレージへのセキュリティで保護された接続を構成するための一連のツールが用意されています。 これらの接続は、次のアクションを完了するためのアクセスを提供します。
> - 生データをレイクハウスに取り込みます。
> - セキュリティで保護されたクラウド ストレージでマネージド テーブルを作成して読み取ります。
> - 登録する または、表形式のデータを含む外部テーブルを作成します。
> - 非構造化データの読み取りと書き込み。

- Databricksでは、UnityCatalogを介してクラウドオブジェクトへの接続を管理することを推奨

## Unity Catalog はオブジェクト ストレージを Databricks にどのように接続しますか?
> Unity Catalog には、クラウド オブジェクト ストレージ内のデータへのアクセスを管理するための粒度のレイヤーがいくつか用意されています。 管理者は初期設定を完了する必要がありますが、新しい接続とオブジェクトを定義する権限を他のユーザーに付与できます。 「 Unity Catalog での特権の管理」を参照してください。

> ストレージ認証情報は、IAMロールを使用してクラウドテナントに保存されているデータにアクセスするための認証および承認メカニズムを表します。各ストレージ資格情報は、資格情報にアクセスできるユーザーとグループを制御する Unity Catalog アクセス制御ポリシーの対象となります。 ユーザーが Unity Catalog のストレージ資格情報にアクセスできない場合、要求は失敗し、Unity Catalog はユーザーに代わってクラウド テナントへの認証を試みません。 ストレージ資格情報のアクセス許可は、外部ロケーションを定義する必要があるユーザーにのみ付与する必要があります。 「 ストレージ資格情報の作成」を参照してください。

- ストレージのCredentialは、IAMロールを使用して認証と承認を行う
- 各ストレージCredentialは、資格情報にアクセスできるユーザとグループを制御するUnity Catalogアクセス制御ポリシーの対象

> 外部ロケーションは、クラウドストレージパスと、クラウドストレージパスへのアクセスを承認するストレージ認証情報を組み合わせたオブジェクトです。各ストレージの場所は、資格情報にアクセスできるユーザーとグループを制御する Unity Catalog アクセス制御ポリシーの対象となります。 ユーザーが Unity Catalog 内のストレージの場所にアクセスできない場合、要求は失敗し、Unity Catalog はユーザーに代わってクラウド テナントへの認証を試みません。 外部ロケーションを作成して使用する権限は、外部テーブル、外部ボリューム、または管理ストレージの場所を作成する必要があるユーザーにのみ付与する必要があります。 「外部ロケーションの作成」を参照してください。

> 管理対象ストレージの場所は、メタストア、カタログ、またはスキーマに関連付けられたクラウド・オブジェクト・ストレージ内の場所です。マネージド テーブルとマネージド ボリュームは、マネージド ストレージの場所に作成されます。 Databricks では、カタログ レベルで管理ストレージの場所を構成することをお勧めします。 オプションで、メタストア レベルで管理ストレージの場所を指定して、カタログ レベルのストレージが定義されていない場合にデフォルト ストレージを提供できます。 よりきめ細かな分離が必要な場合は、スキーマ レベルで管理されたストレージの場所を指定できます。 「Unity Catalog でマネージド ストレージの場所を指定する 」および「 Unity Catalog のベスト プラクティス」を参照してください。

- Databricksでは、カタログレベルで管理ストレージの場所を構成することを推奨
- オプションでメタストアレベルで管理ストレージの場所を指定してデフォルトを定義する


# 実践
## ストレージ資格情報の作成
[ストレージ資格情報の作成](https://docs.databricks.com/ja/connect/unity-catalog/storage-credentials.html)を元に作業を進めます。
> ストレージ資格情報は、クラウド アカウントで定義されているアクセス許可を Unity Catalog のマネージド特権にリンクするために使用されます。 ストレージ資格情報は、クラウド アカウント内のオブジェクト ストレージに格納されているデータを管理するためのアクセス権を Unity Catalog に付与します。 Databricks では、グローバル特権を持つクラウド ストレージのアクセス許可を使用し、必要に応じて Unity Catalog を使用してアクセスを制限することをお勧めします。
> ストレージ資格情報を作成するには、Databricks アカウント管理者、メタストア管理者、または CREATE STORAGE CREDENTIAL 特権を持つユーザーである必要があります。 S3 バケットパスへのアクセス (読み取り、または読み取りと書き込み) を承認する IAMロールが必要です。 その IAMロールは、ストレージ認証情報を作成するときに参照します。
> ストレージ資格情報を作成したユーザーは、所有権を別のユーザーまたはグループに委任して、そのアクセス許可を管理できます。

- ストレージ資格情報は、クラウドアカウントで定義したアクセス許可をUnity Catalogのマネージド特権にリンクするために使用する

### ステップ1：IAMロールを作成する
1. AWSにログインし、IAMから「ロールの作成」をクリックします。
1. 「カスタム信頼ポリシー」を選択します。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/233223bb-f8ab-3249-9942-d7ecc208323a.png)

1. 下記をコピペして、カスタム信頼ポリシーに貼り付け「次へ」をクリックします。 ※下記の「0000」は後ほど置き換えます。
    ```
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {
          "AWS": [
            "arn:aws:iam::414351767826:role/unity-catalog-prod-UCMasterRole-14S5ZJVKOTYTL"
          ]
        },
        "Action": "sts:AssumeRole",
        "Condition": {
          "StringEquals": {
            "sts:ExternalId": "0000"
          }
        }
      }]
    }
    ```
1. IAMポリシーは特に設定せずにIAMロールを作成します。
1. 作成したIAMロールで、「インラインポリシーを作成」をクリックします。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/93bd70b5-9e25-0ef7-256e-e305fb6e5fa7.png)

1. 「JSON」エディタで下記をコピペします。
    - KMSを使用してない場合は、kmsブロックを削除します。
    - <BUCKET>は利用しているS3バケットを指定してください。
    - <AWS-ACCOUNT-ID>と<AWS-IAM-ROLE-NAME>は、自身のアカウントIDとIAMロールを指定します。
    ```
    {
      "Version": "2012-10-17",
      "Statement": [
          {
              "Action": [
                  "s3:GetObject",
                  "s3:PutObject",
                  "s3:DeleteObject",
                  "s3:ListBucket",
                  "s3:GetBucketLocation",
                  "s3:GetLifecycleConfiguration",
                  "s3:PutLifecycleConfiguration"
              ],
              "Resource": [
                  "arn:aws:s3:::<BUCKET>/*",
                  "arn:aws:s3:::<BUCKET>"
              ],
              "Effect": "Allow"
          },
          {
              "Action": [
                  "kms:Decrypt",
                  "kms:Encrypt",
                  "kms:GenerateDataKey*"
              ],
              "Resource": [
                  "arn:aws:kms:<KMS-KEY>"
              ],
              "Effect": "Allow"
          },
          {
              "Action": [
                  "sts:AssumeRole"
              ],
              "Resource": [
                  "arn:aws:iam::<AWS-ACCOUNT-ID>:role/<AWS-IAM-ROLE-NAME>"
              ],
              "Effect": "Allow"
          }
        ]
    }
    ```
1. ポリシー名を作成し、「ポリシーの作成」をクリックします。

### ステップ 2：IAMロールの詳細をDatabricksに渡す
1. ワークスペースにログインします。ナビゲーションペインの「Catalog」をクリックします。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/450f1671-e50e-95c1-036b-f98d290dc452.png" width=200 />

1. 「+Add」から「Add a storage credential」をクリックします。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e827fd19-54fa-1b4d-33a0-02e8c454c2d1.png" width=200 />

1. 下記を入力し、「Create」をクリックします。
    - Storage credential name
    - IAM Role
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/2e7e71ef-215f-4f59-421f-9b3c5cb004cb.png)
1. 外部IDが表示されるのでコピーします。

### ステップ 3: IAMロールポリシーを更新する
1. 作成したIAMロールの信頼ポリシーを編集します。
    - YOUR-AWS-ACCOUNT-IDとTHIS-ROLE-NAMEを置き換えます。
    - STORAGE-CREDENTIAL-EXTERNAL-IDを上記で作成した外部IDで置き換えます。
    ```
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": [
              "arn:aws:iam::414351767826:role/unity-catalog-prod-UCMasterRole-14S5ZJVKOTYTL",
              "arn:aws:iam::<YOUR-AWS-ACCOUNT-ID>:role/<THIS-ROLE-NAME>"
            ]
          },
          "Action": "sts:AssumeRole",
          "Condition": {
            "StringEquals": {
              "sts:ExternalId": "<STORAGE-CREDENTIAL-EXTERNAL-ID>"
            }
          }
        }
      ]
    }
    ```


### ステップ4 外部ロケーションを作成する
[「カタログエクスプローラ」(Catalog Explorer) を使用した外部ロケーションの作成](https://docs.databricks.com/ja/connect/unity-catalog/external-locations.html#create-an-external-location-using-catalog-explorer)を元に試します。

1. 「External Locations」をクリックします。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/97d5d74c-3fa7-3ed7-eb01-e2982eaccaa7.png" width=200 />
1. 「Create location」をクリックします。
1. 「Manual」を選択し、「Next」をクリックします。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/517b987f-09fb-1543-0541-f431972bfad8.png" width=500 />

1. 下記を入力し、「Create」をクリックします。
    - External location name：任意の名前
    - Storage credential:上記で作成したCredentialを指定する
    - URL:外部ロケーションのURL
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/b5424957-fd17-89ff-1dbf-f91aa8e18c3f.png" width=500 />

1. 作成されるとExternal Locationsに表示される。
1. 作成された外部ロケーションをクリックします。
1. 「Test Connection」をクリックし接続テストを行います。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/2ff4fa2d-e74d-4dc0-d2fd-69e2699d3dfc.png)

## 外部ストレージ（S3）からデータを読み込む
1. 事前にデータを用意し、S3にアップロードしておきます。
    ```
    {"id":123456789}
    ```
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/1eed429e-9cb1-16a0-5468-e537784fc98e.png" width=300 />

1. WorkspaceからNotebookを開きます。
1. 下記のコードを実行します。確認ができました。
    ```
    # Databricks notebook source
    
    # S3 バケットとファイルパスの設定
    s3_bucket = "<My-bucket-name>"
    file_path = "test.json"  # 
    
    # S3 URI の構築
    s3_uri = f"s3://{s3_bucket}/{file_path}"
    
    # Parquet ファイルを読み込む
    df = spark.read.format("json").load(s3_uri)
    
    # DataFrame の内容を表示
    df.printSchema()
    df.show()
    ```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5d97a335-d61f-d3fc-164e-f1ae528f74b7.png)

# 考察
今回、DatabricksのUnity Catalogの外部ロケーションからS3ファイルを読み込みました。今後はこれを元にETLを試してみます。

# 参考

https://docs.databricks.com/ja/connect/unity-catalog/storage-credentials.html

https://docs.databricks.com/ja/connect/unity-catalog/index.html

https://docs.databricks.com/ja/connect/unity-catalog/external-locations.html#create-an-external-location-using-catalog-explorer