Databricksによるデータメッシュ実現のためのTerraformサンプル実装の解説

# はじめに
以下GitHubで、複数のDatabricksワークスペースをデータメッシュの概念に沿って管理するためのTerraformのサンプル実装を公開しました。本記事でこのサンプル実装について解説します。
なお、サンプル実装および本記事は、あくまで個人のアイデアをまとめたものであり、会社の見解を代表するものではない点に注意ください。

https://github.com/nakazax/aws-databricks-terraform-specific-examples/tree/main/examples/data_mesh_example

## 前提
- Terraformサンプル実装および本記事はDatabricks on AWSを対象としています
- 以下のバージョンのTerraformおよびプロバイダーを利用して動作確認を行っています

```bash:Terraformバージョン
% terraform -v
Terraform v1.6.5
on darwin_arm64
+ provider registry.terraform.io/databricks/databricks v1.38.0
+ provider registry.terraform.io/hashicorp/aws v5.40.0
+ provider registry.terraform.io/hashicorp/random v3.6.0
+ provider registry.terraform.io/hashicorp/time v0.11.1
```

# データメッシュとは
解説に入る前に、簡単にデータメッシュの定義と実際の例について記載します。

## データメッシュの定義
データメッシュの定義は以下の通りです。

> データメッシュとは、スケールするデータ分析プラットフォームに対する一連の原則と論理的アーキテクチャを表現するパラダイムです。大規模にデータからさらなる価値を資産として導出することを目的としています。データメッシュという用語は2019年にZhamak Dehghaniによて導入され、彼女の2020年の記事のData Mesh Principles and Logical Architectureで拡張されました。

> データメッシュの論理的アーキテクチャのコアには4つの原則があります:
> 1. ドメインオーナーシップ: 複数のドメインチーム、データ生成者がキャプチャからキュレーション、分析、再利用に至るライフサイクルを通じて自身のデータに完全な席んを持つ、分散アーキテクチャを導入します。
> 2. 製品としてのデータ: データ分析ライフサイクルに製品管理原則を適用し、データ生成者の領域内外にいるであろうデータ利用者に提供されるデータの品質を保証します。
> 3. セルフサービスのインフラストラクチャプラットフォーム: 相互運用可能なデータ製品を構築、実行、維持するための共通ツールと方法論を用いて、データ分析ライフサイクルにデータ不可知のアプローチを適用します。
> 4. 統合されたガバナンス: 標準化を通じて組織のルールや業界の規制に準拠するデータエコシステムを確立します。

https://qiita.com/taka_yayoi/items/4a46df653eb44e36ac57

## データメッシュの実例
データメッシュの実際の例としては以下のカケハシ様のアーキテクチャーをご覧頂くとイメージが湧きやすいと思います。（以下画像はカケハシ様SpeakerDeckより引用）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/dbf6bf86-8e0e-0c6b-364a-09443abbeaa1.png)

https://speakerdeck.com/kakehashi/the-datamesh-organization-built-with-databricks?slide=6

# 各ディレクトリの役割・用途
ここからTerraformサンプル実装の解説です。まずは各ディレクトリの役割・用途について記載します。名前や構造はあくまでサンプルですので、実際に利用する際には組織のデータメッシュ設計の実情に合わせて変更頂くのが良いと思います。

ディレクトリ | 役割・用途
--- | ---
`domains` | データドメイン用の基底ディレクトリ。配下に各ドメインのディレクトリを格納する。
`domains/domain1` | `domain1`というデータドメイン用のディレクトリ。<br>実際に利用する場合にはドメインを表す分かりやすい名前に変更するのが良い。<br>他のドメインがある場合は`domains`配下に別のディレクトリを作成する。
`domains/modules` | 複数のデータドメインで利用するモジュール。
`platform` | プラットフォーム用の基底ディレクトリ。データメッシュ全体の共通的な機能やインフラに関するファイルを格納する。
`platform/01_workspace_setup` | データドメイン用のすべてのDatabricksワークスペースおよびそのAWSインフラの作成・管理用のファイルを格納。
`platform/02_workspace_config` | `01_workspace_setup`で作成したDatabricksワークスペースに共通的に適用する設定やポリシーを管理するファイルを格納。

:::note info
本サンプルではデータドメインとプラットフォームのファイルをまとめて1つのリポジトリで管理していますが、実際の利用シーンではデータドメインごとにリポジトリを分ける方が管理しやすい可能性がありますので、よく検討してみてください。
:::

# Terraformファイルの詳解
上記で各ディレクトリの役割・用途が理解できたと思いますので、次にどの順番で`terraform apply`（これを便宜上、Terraform実行と呼称）するのか、そして各ファイルについて解説します。

## 実行の順番
まず実行の順番ですが、最初にDatabricksアカウントの管理者が以下の順番で`terraform apply`を実行します。

1. `platform/01_workspace_setup`: 複数のDatabricksワークスペースおよびそのAWSインフラを作成する
1. `platform/02_workspace_config`: 複数のDatabricksワークスペースに共通的な設定・ポリシーを適用する（サンプル実装では各ワークスペース管理者に`CREATE_CATALOG`などの権限を付与）

上記が成功すると各データドメイン用のDatabricksワークスペースがReadyになります。以降は各データドメインのDatabricksワークスペース管理者が任意の管理作業を行う流れを想定しています。

ドメインごとの管理作業の例として、サンプル実装の`domains/domain1`では、ドメイン固有のデータ管理用のUnity Catalogのカタログと周辺リソースを作成・管理するtfファイルを含めています。

以降で各Terraformファイルの詳細について記載します。

# `platform/01_workspace_setup`
複数のDatabricksワークスペースおよびそのAWSインフラを作成・管理します。主要なファイルについて以下で解説します。

## プロバイダー (`providers.tf`)
AWS、Databricksをプロバイダーとして利用します。

```terraform:providers.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
    databricks = {
      source = "databricks/databricks"
    }
    random = {
      source = "hashicorp/random"
    }
  }
}

provider "aws" {
  region = var.region
}

provider "databricks" {
  alias         = "mws"
  host          = "https://accounts.cloud.databricks.com"
  account_id    = var.databricks_account_id
  client_id     = var.databricks_client_id
  client_secret = var.databricks_client_secret
}
```

### AWS
#### 必要な権限・認証方法
- 実行の主体（プリンシパル）がAWS IAMの[AdministratorAccess](https://docs.aws.amazon.com/ja_jp/aws-managed-policy/latest/reference/AdministratorAccess.html)の権限を持っていることを想定しています
- AWSの認証は複数の方法があるので任意の方法を利用ください。アクセスキーやシークレットキーをハードコードするのではなく、AWS CLIでの`aws configure`実行で作成される認証情報ファイル、あるいは環境変数などを利用するのが良いと思います（認証方法の種類とその詳細については以下リンクを参照）

https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration

### Databricks
#### 必要な権限・認証方法
- ワークスペースレベルではなくアカウントレベルのサービスプリンシパルの`client_id`と`client_secret`を用いた認証を想定しています。さらに当該サービスプリンシパルはアカウント管理者の権限を持っていることを想定しています
    - アカウントレベルのサービスプリンシパルの作成方法については以下リンクを参照ください
    - [サービスプリンシパルを管理する | Databricks on AWS](https://docs.databricks.com/ja/administration-guide/users-groups/service-principals.html#manage-service-principals-in-your-account)
- `account_id`は、アカウントコンソールの右上にある自身のメールアドレスをクリックした後に確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/4a806a24-9379-23ce-b4a4-49a0093100f1.png)

## メイン (`main.tf`)
意味のあるまとまりに区切って説明します。

### アカウント管理者グループの作成
アカウントレベルの管理者グループを作成し、そこに引数（variable）で指定したプリンシパル（ユーザーやグループ、サービスプリンシパル）を追加します。さらにアカウント管理者グループに`account_admin`ロールを付与します。

```terraform:main.tf - アカウント管理者グループの作成
resource "databricks_group" "account_admin" {
  provider     = databricks.mws
  display_name = var.databricks_account_admin_group_name
}

resource "databricks_group_member" "account_admin" {
  provider  = databricks.mws
  for_each  = var.databricks_account_admin_principal_ids
  group_id  = databricks_group.account_admin.id
  member_id = each.value
}

resource "databricks_group_role" "account_admin" {
  provider = databricks.mws
  group_id = databricks_group.account_admin.id
  role     = "account_admin"
}
```

### Unity Catalogメタストアの作成
既存のUnity CatalogメタストアをDatabrickワークスペースに関連づけたい場合は、引数でメタストアのIDを指定します。もしメタストアのIDが指定されていない場合は、`primary`という名前で新しくメタストアを作成し、アカウント管理者グループを所有者として設定します。`count`で引数の指定有無に応じたリソース作成有無の制御をしています。

```terraform:main.tf - Unity Catalogメタストアの作成
resource "databricks_metastore" "this" {
  count         = var.databricks_metastore_id == "" ? 1 : 0
  provider      = databricks.mws
  name          = "primary"
  owner         = databricks_group.account_admin.display_name
  region        = var.region
  force_destroy = true
}
```

### DatabricksワークスペースおよびAWSインフラの作成
DatabricksワークスペースとそのAWSインフラを作成するモジュール`aws_databricks_mws`を呼び出します。引数で複数のDatabricksワークスペースを指定できるようになっています。

```terraform:main.tf - Databricksワークスペースの作成
locals {
  metastore_id = var.databricks_metastore_id == "" ? databricks_metastore.this[0].id : var.databricks_metastore_id
}

module "aws_databricks_mws" {
  source = "./modules/aws_databricks_mws"
  providers = {
    aws        = aws
    databricks = databricks.mws
  }

  for_each = var.databricks_workspaces

  # Common variables for all workspaces
  region                  = var.region
  databricks_account_id   = var.databricks_account_id
  databricks_metastore_id = local.metastore_id

  # Workspace specific variables
  prefix                     = each.value.prefix
  vpc_cidr                   = each.value.vpc_cidr
  public_subnets_cidr        = each.value.public_subnets_cidr
  private_subnet_pair        = each.value.private_subnet_pair
  tags                       = each.value.tags
  workspace_admin_group_name = each.value.workspace_admin_group_name
  workspace_admin_user_ids   = each.value.workspace_admin_user_ids
}
```

#### モジュール `aws_databricks_mws/main.tf`
こちらのモジュールではDatabricksワークスペース関連の以下のリソースを作成・管理します。

- ワークスペース用のAWSインフラ（VPC関連リソース、S3バケット、IAMロール、IAMポリシー）
- ワークスペースおよびその周辺リソース（ストレージ設定、ネットワーク設定）
- ワークスペースとメタストアのアサインメント
- ワークスペースの管理者グループとADMIN権限、プリンシパルの関連付け

こちらは長いので全文はGitHub上の`main.tf`をご覧頂ければと思います。

https://github.com/nakazax/aws-databricks-terraform-specific-examples/blob/main/examples/data_mesh_example/platform/01_workspace_setup/modules/aws_databricks_mws/main.tf

なお、このモジュール自体は以下のサンプルを参考に、シンプル化して作成したものになります。

https://registry.terraform.io/modules/databricks/examples/databricks/latest/examples/aws-databricks-modular-privatelink

#### Tips: ワークスペースレベルの`token`作成
一点Tipsとして、ワークスペース作成時に`token`を指定しています。これにより、このTerraformを実行したサービスプリンシパルのトークンがワークスペースに作成されます。親の`outputs.tf`で出力されたこのトークンを使うことで、以降のワークスペースレベルの処理の自動化を楽かつ安全に行うことができます。

```terraform: aws_databricks_mws/main.tf - Databricksワークスペースの作成
resource "databricks_mws_workspaces" "this" {
  account_id     = var.databricks_account_id
  workspace_name = local.prefix
  aws_region     = var.region

  credentials_id           = databricks_mws_credentials.this.credentials_id
  storage_configuration_id = databricks_mws_storage_configurations.this.storage_configuration_id
  network_id               = databricks_mws_networks.this.network_id

  token {}
}
```

## アウトプット (`outputs.tf`)
メタストアのID、作成したワークスペースの管理者グループ名、ワークスペース名、サービスプリンシパルのトークン、ワークスペールURLを出力します。これらの情報を後続の`platform/02_workspace_config`の引数として用います。

```terraform: outputs.tf
output "databricks_metastore_id" {
  value = local.metastore_id
}

output "databricks_workspaces_details" {
  value = [for key, ws in module.aws_databricks_mws : {
    databricks_workspace_admin_group_name = ws.databricks_workspace_admin_group_name
    databricks_workspace_name             = ws.databricks_workspace_name
    databricks_workspace_token            = ws.databricks_workspace_token
    databricks_workspace_url              = ws.databricks_workspace_url
  }]
  sensitive = true
}
```

## 引数
本プロジェクトでは、Terraformのプラクティスに従い、引数のファイルについてはGitHubでバージョン管理していません。具体的には、`.gitignore`で`*.tfvars`を除外しています。

引数のファイルを作成せずに、`terraform apply`実行時にインタラクティブに引数を指定してももちろん良いですが、`01_workspace_setup`ディレクトリの直下に以下のような引数をまとめたファイル`terraform.tfvars`を作成すると楽に実行できます。内容はあくまでサンプル（ID系はすべてダミーの値）なので、自身のニーズに合わせて書き換えてください。

```terraform: 01_workspace_setup/terraform.tfvars
region = "ap-northeast-1"
databricks_account_id = "1234567a-304d-4e66-8c03-11b10d68ba23"
databricks_client_id     = "1234567b-304d-4e66-8c03-11b10d68ba23"
databricks_client_secret = "1234567c-304d-4e66-8c03-11b10d68ba23"

databricks_account_admin_group_name = "account admins"
databricks_account_admin_principal_ids = [
  "1234567890123456",
  "6543210987654321"
]

databricks_workspaces = {
  workspace1 = {
    # S3などのリソース名の重複を回避するためにグローバルユニークになるプレフィクスを指定する
    prefix                     = "random-prefix-domain1"
    vpc_cidr                   = "10.109.0.0/16"
    public_subnets_cidr        = ["10.109.2.0/23"]
    private_subnet_pair        = ["10.109.4.0/23", "10.109.6.0/23"]
    tags                       = {}
    workspace_admin_group_name = "domain1-admin-group"
    workspace_admin_user_ids = [
      "1234567890123456",
      "6543210987654321"
    ]
  },
  workspace2 = {
    prefix                     = "random-prefix-domain2"
    vpc_cidr                   = "10.110.0.0/16"
    public_subnets_cidr        = ["10.110.2.0/23"]
    private_subnet_pair        = ["10.110.4.0/23", "10.110.6.0/23"]
    tags                       = {}
    workspace_admin_group_name = "domain2-admin-group"
    workspace_admin_user_ids = [
      "1234567890123456",
      "6543210987654321"
    ]
  }
}
```

## Terraform実行
ファイルの説明は以上で終わりです。実際の実行方法について説明します。

### 事前準備
AWS CLIのインストール、AWS CLIを利用するためのIAMユーザーのアクセスキーを準備します。以下Qiita記事の事前準備のセクションを参考に準備してください。

https://qiita.com/Chanmoro/items/55bf0da3aaf37dc26f73#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99

### 実行
ローカルにリポジトリをクローンし、対象のディレクトリに移動します。

```bash:Terraform実行の事前準備
$ git clone https://github.com/nakazax/aws-databricks-terraform-specific-examples
$ cd aws-databricks-terraform-specific-examples/examples/data_mesh_example/platform/01_workspace_setup
```

続いてTerraformの初期化を行います。

```bash:Terraform初期化
$ terraform init

Initializing the backend...
Initializing modules...
- aws_databricks_mws in modules/aws_databricks_mws
- aws_databricks_mws.aws_infra in modules/aws_databricks_mws/modules/aws_infra

Initializing provider plugins...
- Reusing previous version of hashicorp/time from the dependency lock file
- Reusing previous version of databricks/databricks from the dependency lock file
- Reusing previous version of hashicorp/random from the dependency lock file
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/time v0.11.1...
- Installed hashicorp/time v0.11.1 (signed by HashiCorp)
- Installing databricks/databricks v1.38.0...
- Installed databricks/databricks v1.38.0 (self-signed, key ID 92A95A66446BCE3F)
- Installing hashicorp/random v3.6.0...
- Installed hashicorp/random v3.6.0 (signed by HashiCorp)
- Installing hashicorp/aws v5.40.0...
- Installed hashicorp/aws v5.40.0 (signed by HashiCorp)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

念のため`terraform plan`で作成・更新リソースのシミュレーションが問題ないかを確認します。

```bash:terraform plan
$ terraform plan

(中略)

Plan: 67 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + databricks_metastore_id       = (known after apply)
  + databricks_workspaces_details = (sensitive value)
```

ここまで問題なければ`terraform apply`を実行します。問題ないかの確認が表示されますので `yes` を入力してEnterを押します。

```bash:terraform apply
$ terraform apply

(中略)

Plan: 67 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + databricks_metastore_id       = (known after apply)
  + databricks_workspaces_details = (sensitive value)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes
```

上記実行後、3-4分待って、以下のように`Apply complete!`が表示されれば正常終了です。

```bash:terraform applyの実行結果
(中略)
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table_association.private_route_table_associations[1]: Creation complete after 1s [id=rtbassoc-0ea195aef64c0e05c]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table_association.private_route_table_associations[0]: Creation complete after 1s [id=rtbassoc-09550156d7158df9d]

Apply complete! Resources: 67 added, 0 changed, 0 destroyed.

Outputs:

databricks_metastore_id = "1234567d-304d-4e66-8c03-11b10d68ba23"
databricks_workspaces_details = <sensitive>
```

## 実行結果の確認
`terraform state list`コマンドで作成されたリソースを確認できます。上のセクションで記載した引数のサンプルを指定して`terraform apply`を実行した場合、2つのワークスペースとその関連リソースが作成されます。

```bash:terraform state list
$ terraform state list

databricks_group.account_admin
databricks_group_member.account_admin["1234567890123456"]
databricks_group_member.account_admin["6543210987654321"]
databricks_group_role.account_admin
databricks_metastore.this[0]
module.aws_databricks_mws["workspace1"].databricks_group.workspace_admin
module.aws_databricks_mws["workspace1"].databricks_group_member.admin_user["5222559431330616"]
module.aws_databricks_mws["workspace1"].databricks_group_member.admin_user["7422702450545826"]
module.aws_databricks_mws["workspace1"].databricks_metastore_assignment.this
module.aws_databricks_mws["workspace1"].databricks_mws_credentials.this
module.aws_databricks_mws["workspace1"].databricks_mws_networks.this
module.aws_databricks_mws["workspace1"].databricks_mws_permission_assignment.workspace_admin
module.aws_databricks_mws["workspace1"].databricks_mws_storage_configurations.this
module.aws_databricks_mws["workspace1"].databricks_mws_workspaces.this
module.aws_databricks_mws["workspace1"].random_string.naming
module.aws_databricks_mws["workspace1"].time_sleep.wait_iam_role
module.aws_databricks_mws["workspace1"].time_sleep.wait_metastore_assignment
module.aws_databricks_mws["workspace2"].databricks_group.workspace_admin
module.aws_databricks_mws["workspace2"].databricks_group_member.admin_user["5222559431330616"]
module.aws_databricks_mws["workspace2"].databricks_group_member.admin_user["7422702450545826"]
module.aws_databricks_mws["workspace2"].databricks_metastore_assignment.this
module.aws_databricks_mws["workspace2"].databricks_mws_credentials.this
module.aws_databricks_mws["workspace2"].databricks_mws_networks.this
module.aws_databricks_mws["workspace2"].databricks_mws_permission_assignment.workspace_admin
module.aws_databricks_mws["workspace2"].databricks_mws_storage_configurations.this
module.aws_databricks_mws["workspace2"].databricks_mws_workspaces.this
module.aws_databricks_mws["workspace2"].random_string.naming
module.aws_databricks_mws["workspace2"].time_sleep.wait_iam_role
module.aws_databricks_mws["workspace2"].time_sleep.wait_metastore_assignment
module.aws_databricks_mws["workspace1"].module.aws_infra.data.aws_availability_zones.available
module.aws_databricks_mws["workspace1"].module.aws_infra.data.databricks_aws_assume_role_policy.this
module.aws_databricks_mws["workspace1"].module.aws_infra.data.databricks_aws_bucket_policy.this
module.aws_databricks_mws["workspace1"].module.aws_infra.data.databricks_aws_crossaccount_policy.this
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_eip.nat_gateway_elastic_ips[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_iam_role.cross_account_role
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_iam_role_policy.this
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_internet_gateway.igw
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_nat_gateway.nat_gateways[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table.private_route_tables[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table.private_route_tables[1]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table.public_route_table
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table_association.private_route_table_associations[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table_association.private_route_table_associations[1]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_route_table_association.public_route_table_associations[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_s3_bucket.root_storage_bucket
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_s3_bucket_policy.root_bucket_policy
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_s3_bucket_public_access_block.root_storage_bucket
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_security_group.test_sg
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_subnet.private_subnets[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_subnet.private_subnets[1]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_subnet.public_subnets[0]
module.aws_databricks_mws["workspace1"].module.aws_infra.aws_vpc.main_vpc
module.aws_databricks_mws["workspace2"].module.aws_infra.data.aws_availability_zones.available
module.aws_databricks_mws["workspace2"].module.aws_infra.data.databricks_aws_assume_role_policy.this
module.aws_databricks_mws["workspace2"].module.aws_infra.data.databricks_aws_bucket_policy.this
module.aws_databricks_mws["workspace2"].module.aws_infra.data.databricks_aws_crossaccount_policy.this
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_eip.nat_gateway_elastic_ips[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_iam_role.cross_account_role
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_iam_role_policy.this
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_internet_gateway.igw
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_nat_gateway.nat_gateways[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table.private_route_tables[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table.private_route_tables[1]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table.public_route_table
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table_association.private_route_table_associations[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table_association.private_route_table_associations[1]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_route_table_association.public_route_table_associations[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_s3_bucket.root_storage_bucket
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_s3_bucket_policy.root_bucket_policy
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_s3_bucket_public_access_block.root_storage_bucket
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_security_group.test_sg
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_subnet.private_subnets[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_subnet.private_subnets[1]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_subnet.public_subnets[0]
module.aws_databricks_mws["workspace2"].module.aws_infra.aws_vpc.main_vpc
```

Databricksアカウントコンソールのワークスペースを見ると、複数のワークスペースが作成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/a9e6de10-e016-4e7d-535c-7b4a480898dc.png)

作成されたワークスペースの設定画面は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/2fbf3c7d-45ef-84ef-0258-5edc6dcab839.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/9a8488bb-7936-0746-5ecb-e885c260d758.png)

## アウトプットの確認
`terraform output -json`コマンドでアウトプットに定義した情報を見ることができます。`platform/02_workspace_config`の入力として使うので控えておきましょう。

```bash:terraform output -json
$ terraform output -json
{
  "databricks_metastore_id": {
    "sensitive": false,
    "type": "string",
    "value": "1234567d-304d-4e66-8c03-11b10d68ba23"
  },
  "databricks_workspaces_details": {
    "sensitive": true,
    "type": [
      "tuple",
      [
        [
          "object",
          {
            "databricks_workspace_admin_group_name": "string",
            "databricks_workspace_name": "string",
            "databricks_workspace_token": "string",
            "databricks_workspace_url": "string"
          }
        ],
        [
          "object",
          {
            "databricks_workspace_admin_group_name": "string",
            "databricks_workspace_name": "string",
            "databricks_workspace_token": "string",
            "databricks_workspace_url": "string"
          }
        ]
      ]
    ],
    "value": [
      {
        "databricks_workspace_admin_group_name": "domain1-admin-group",
        "databricks_workspace_name": "hinak-dbc-ws-apne1-domain1",
        "databricks_workspace_token": "1234567d-304d-4e66-8c03-11b10d68ba23",
        "databricks_workspace_url": "https://dbc-e29e9444-3722.cloud.databricks.com"
      },
      {
        "databricks_workspace_admin_group_name": "domain2-admin-group",
        "databricks_workspace_name": "hinak-dbc-ws-apne1-domain2",
        "databricks_workspace_token": "1234567e-304d-4e66-8c03-11b10d68ba23",
        "databricks_workspace_url": "https://dbc-3d5a5b8d-8350.cloud.databricks.com"
      }
    ]
  }
}
```

## (オプション) Terraformリソースの削除
`terraform destroy`コマンドでリソース一式を削除できます。

# `platform/02_workspace_config`
上記で作成したDatabricksワークスペースに共通的な設定・ポリシーを適用します。本サンプル実装では各ワークスペース管理者に`CREATE_CATALOG`などの権限を付与します。

## プロバイダー (`providers.tf`)
サンプル実装では、2つのDatabricksワークスペースのプロバイダーを定義しています。もっと沢山のワークスペースを作成した場合は、コピー & ペーストでプロバイダーを増やします。
`url`および`token`には、`platform/01_workspace_setup`のアウトプットを使うのがスムーズだと思います。

```terraform: providers.tf
terraform {
  required_providers {
    databricks = {
      source = "databricks/databricks"
    }
  }
}

provider "databricks" {
  alias = "domain1"
  host  = var.domain1.url
  token = var.domain1.token
}

provider "databricks" {
  alias = "domain2"
  host  = var.domain2.url
  token = var.domain2.token
}
```

:::note warn
**Terraformのプロバイダーの動的な定義**
Terraformでは`for_each`などと組み合わせてプロバイダーを動的に定義することは仕様上できないため、上記のように静的な形ですべてのプロバイダーを定義する必要があります。
:::

## メイン (`main.tf`)
各ワークスペースの管理者グループに対して、カタログ作成に必要な権限一式を付与します。より多くのワークスペースがある場合はリソースセクションをコピー & ペーストして増やします。

```terraform: main.tf
resource "databricks_grant" "domain1" {
  provider   = databricks.domain1
  metastore  = var.databricks_metastore_id
  principal  = var.domain1.admin_group_name
  privileges = ["CREATE_CATALOG", "CREATE_EXTERNAL_LOCATION", "CREATE_STORAGE_CREDENTIAL"]
}

resource "databricks_grant" "domain2" {
  provider   = databricks.domain2
  metastore  = var.databricks_metastore_id
  principal  = var.domain2.admin_group_name
  privileges = ["CREATE_CATALOG", "CREATE_EXTERNAL_LOCATION", "CREATE_STORAGE_CREDENTIAL"]
}
```

:::note warn
**メタストアレベルの操作に関する注意点**

- `CREATE_CATALOG`権限などの付与はメタストアレベルの操作になります
- メタストアレベルの操作は、メタストア管理者（Metastore Admin）が行うことができます
- `platform/01_workspace_setup`ではアカウント管理者グループ（account admins）をメタストア管理者に設定しています。そのため、Terraformを実行するプリンシパルがアカウント管理者グループに入っていれば問題なく実行できます
- 既存のメタストアを利用する場合にはTerraformを実行するプリンシパルがメタストア管理者の一員になっていることを確認し、なっていなければ変更が必要になります
:::

## 引数
以下は引数の例です。`platform/01_workspace_setup`のアウトプットをコピー & ペーストするのが簡単です。

```terraform: terraform.tfvars
databricks_metastore_id = "1234567d-304d-4e66-8c03-11b10d68ba23"

domain1 = {
  admin_group_name = "domain1-admin-group"
  token            = "1234567d-304d-4e66-8c03-11b10d68ba23"
  url              = "https://dbc-210868cc-1c64.cloud.databricks.com"
}

domain2 = {
  admin_group_name = "domain2-admin-group"
  token            = "1234567e-304d-4e66-8c03-11b10d68ba23"
  url              = "https://dbc-08e13b97-9770.cloud.databricks.com"
}
```

## Terraform実行
`platform/01_workspace_setup`と同じ要領で実行できます。

```bash:Terraform実行
$ cd aws-databricks-terraform-specific-examples/examples/data_mesh_example/platform/02_workspace_config
$ terraform init
$ terraform plan
$ terraform apply
```

## 実行結果
ワークスペースにアクセスし、カタログエクスプローラーからメタストアの権限を見ると、以下のように`CREATE CATALOG`などの権限がワークスペース管理者グループに付与されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/4a4d95f5-4a3a-8ea5-8c53-e264a8da1c55.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/2357a506-e0c3-7475-7c0f-55f6a2bc86fa.png)

# `domains/domain1`
以降は各データドメインのワークスペース管理者が、ドメインのニーズに合わせて任意のリソースの作成・管理を行なっていくことになります。

本サンプル実装の`domains/domain1`では、例として、ドメイン固有のデータ管理用のUnity Catalogのカタログと周辺リソースを作成・管理するtfファイルを含めてますので、その説明を簡単に記載します。

## プロバイダー (`providers.tf`)
自ドメインのDatabricksワークスペースをプロバイダーとして利用します。

```terraform: providers.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
    databricks = {
      source = "databricks/databricks"
    }
  }
}

provider "aws" {
  region = var.region
}

provider "databricks" {
  alias = "domain1"
  host  = var.domain1.url
  token = var.domain1.token
}
```

## メイン (`main.tf`)
モジュールを呼び出して、ドメイン固有のデータ管理用のUnity Catalogと周辺リソースを作成・管理します。

```terraform: main.tf
module "workspace_catalog1" {
  source = "../modules/workspace_catalog"
  providers = {
    aws        = aws
    databricks = databricks.domain1
  }
  prefix           = var.domain1.prefix
  catalog_name     = var.domain1.catalog_name
  admin_group_name = var.domain1.admin_group_name
}
```

## モジュール (`modules/workspace_catalog/main.tf`)
以下のサンプル実装をほぼそのまま流用、若干の改変を行なったモジュールです。

https://registry.terraform.io/providers/databricks/databricks/latest/docs/guides/unity-catalog

以下のリソースを作成・管理します。いずれもワークスペースレベルになります。

- Unity Catalogのストレージクレデンシャル
- カタログ用のS3バケット
- DatabricksのコントロールプレーンからS3にアクセスする際に利用するIAMロール & IAMポリシー
- Unity Catalogの外部ロケーション
- Unity Catalogのカタログ
- ワークスペース管理者グループにストレージクレデンシャル、外部ロケーション、カタログの`ALL_PRIVILEGES`を付与

長いので実物は以下のGitHubでご覧ください。

https://github.com/nakazax/aws-databricks-terraform-specific-examples/blob/main/examples/data_mesh_example/domains/modules/workspace_catalog/main.tf

## 引数
```terraform: terraform.tfvars
region = "ap-northeast-1"

domain1 = {
  admin_group_name = "domain1-admin-group"
  catalog_name     = "domain1_main"
  prefix           = "random-prefix-domain1"
  token            = "1234567d-304d-4e66-8c03-11b10d68ba23"
  url              = "https://dbc-210868cc-1c64.cloud.databricks.com"
}
```

## Terraform実行
`platform/01_workspace_setup`と同じ要領で実行できます。

```bash:Terraform実行
$ cd aws-databricks-terraform-specific-examples/examples/data_mesh_example/domains/domain1
$ terraform init
$ terraform plan
$ terraform apply
```

## 実行結果
ワークスペースのWeb UIでカタログエクスプローラーにアクセスし、ストレージクレデンシャル、外部ロケーション、カタログが作成されていることが確認できます。

### ストレージクレデンシャル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/0c43258f-d0d4-1d89-ca5b-18447cb2b3e0.png)

### 外部ロケーション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/fc03bef3-5a3c-5fcd-2447-645623e170d7.png)

### カタログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/68f46f68-0d40-6c5e-f9c9-d4adf8524a67.png)

カタログ作成時に`ISOLATED`を指定したため、`domain1`のワークスペースだけがこのカタログにアクセスできる状態になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/9b1ee01d-c80e-3e2c-7583-ada51ef7803c.png)

以上です。