TerraformによるDatabricksのアクセス制御管理
- Databricks Terraform Provider によってDatabricksのほぼ全てのリソースを管理することができる。本記事では、前回紹介したDatabricksのアクセス制御設定をTerraformで管理する
Terraformの概要
- Databricks Providerを利用する前にTerraformのアーキテクチャについて簡単に触れておく
- パフォーマンスやリソース間の依存関係などの観点から、Terraform自体が実態のリソースの状態を把握するために状態を管理するためのファイル(tfstate)を作成する
- tfstateは、デフォルトではterraform.tfstateというファイル名でローカルに保存する。s3などにリモートデータストアで管理することで、他ユーザーと共有することができる
- terraform plan を実行すると、tfstateとソースコードで記述した定義との差分を表示する
- terraform apply を実行すると実際にリソースに対してplanで表示した差分を適用し、リソースに関連する情報が tfstate に保存される
- terraform destroy を実行するとリソースを削除し、 tfstate の内容も削除される
- terraform import を実行すると既に構築済みの既存のリソースをTerraformの管理にするためtfstateへリソースに関する情報が保存される

本稿では、DatabricksのCatalogは既に作成済として、terraform importコマンドで取り込んで管理することにする。CatalogをTerraformで作成することで、自動的にtfstateで管理できるようになるが、CatalogをTerraformで作成するには、その上位のUnityCatalogとメタストアもTerraformの管理下に置く必要がある
Databricks Providerのセットアップ
次のいずれかの方法でDatabricksのリソースにアクセスするためのセットアップを行う
- Databricks CLI資格情報によるセットアップ
databricks configure --tokenで作成された資格情報ファイル(デフォルトは~/.databrickscfg)を利用する
provider "databricks" {
alias = "mws"
config_file = "C:/Users/sugah/.databrickscfg"
}
- ホスト名とパーソナルアクセストークン(PAT)によるセットアップ
- ワークスペースのURLとPATを利用する
provider "databricks" {
host = "http://abc-cdef-ghi.cloud.databricks.com"
token = "dapitokenhere"
}
- ホスト名、ユーザー名、パスワードによるセットアップ
- アカウントレベルのユーザーの資格情報を利用するため、ワークスペースの作成などのアカウントレベルの操作を行う場合に利用する
provider "databricks" {
alias = "mws"
host = "https://accounts.cloud.databricks.com"
username = var.databricks_account_username
password = var.databricks_account_password
}
Databricks ワークスペースの作成
- Databricksのワークスペースを作成するためには、VPCなどのネットワーク設定、Databricks社とのクロスアカウントロール資格情報、ルートバケットとストレージ資格情報を構築する必要がある
- まず最初にDatabricks Providerをアカウントレベルのユーザーの資格情報を利用してセットアップする
- 次のような機密情報を管理するファイル(ここではデフォルトの名前で
terraform.tfvars作成する)
- 次のような機密情報を管理するファイル(ここではデフォルトの名前で
databricks_account_id="a4xxxxxx-xxxx-xxxx-xxxx-xx9999931x3"
databricks_account_username="xxxx@xxxxxxxxxx"
databricks_account_password="xxxxxxxxxx"
- 外部パラメータとデフォルトのパラメータ情報を管理する
variables.tfファイルを作成する
variable "databricks_account_id" {}
variable "databricks_account_username" {}
variable "databricks_account_password" {}
variable "cidr_block" {
default = "10.4.0.0/16"
}
variable "region" {
default = "ap-northeast-1"
}
locals {
prefix = "data-domain"
}
providers.tfファイルを作成する- tfstateを管理するs3バケットは事前に作成済とする(
cluetechnologies-terraform-state)
- tfstateを管理するs3バケットは事前に作成済とする(
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.64.0"
}
databricks = {
source = "databricks/databricks"
}
}
backend "s3" {
bucket = "cluetechnologies-terraform-state"
key = "databricks/terraform.tfstate"
encrypt = true
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
env = "databricks-terraform-poc"
}
}
}
provider "databricks" {
alias = "mws"
host = "https://accounts.cloud.databricks.com"
account_id = var.databricks_account_id
username = var.databricks_account_username
password = var.databricks_account_password
}
- VPCとサブネットを作成してdatabricks_mws_networksリソースとして登録する
networks.tf
data "aws_availability_zones" "available" {}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.70.0"
name = local.prefix
cidr = var.cidr_block
azs = data.aws_availability_zones.available.names
enable_dns_hostnames = true
enable_nat_gateway = true
create_igw = true
public_subnets = [cidrsubnet(var.cidr_block, 3, 0)]
private_subnets = [cidrsubnet(var.cidr_block, 3, 1),
cidrsubnet(var.cidr_block, 3, 2)]
default_security_group_egress = [{
cidr_blocks = "0.0.0.0/0"
}]
default_security_group_ingress = [{
description = "Allow all internal TCP and UDP"
self = true
}]
}
resource "databricks_mws_networks" "this" {
provider = databricks.mws
account_id = var.databricks_account_id
network_name = "${local.prefix}-network"
security_group_ids = [module.vpc.default_security_group_id]
subnet_ids = module.vpc.private_subnets
vpc_id = module.vpc.vpc_id
}
- ルートバケットの作成
- DBFSワークスペースストレージ用のs3バケットを作成する。このプロバイダーには、必要な IAM ポリシー テンプレートを含むdatabricks_aws_bucket_policyがあります
strage_config.tf
resource "aws_s3_bucket" "root_storage_bucket" {
bucket = "cluetechnologies-databirkcs-${local.prefix}-rootbucket"
force_destroy = true
}
data "databricks_aws_bucket_policy" "this" {
bucket = aws_s3_bucket.root_storage_bucket.bucket
}
resource "aws_s3_bucket_policy" "root_bucket_policy" {
bucket = aws_s3_bucket.root_storage_bucket.id
policy = data.databricks_aws_bucket_policy.this.json
}
resource "databricks_mws_storage_configurations" "this" {
provider = databricks.mws
account_id = var.databricks_account_id
bucket_name = aws_s3_bucket.root_storage_bucket.bucket
storage_configuration_name = "${local.prefix}-storage"
}
- クロスアカウントIAMロールを作成する
- DatabricksへAWSアカウント内で必要なアクションを許可する
credentials.tf
data "databricks_aws_assume_role_policy" "this" {
external_id = var.databricks_account_id
}
resource "aws_iam_role" "cross_account_role" {
name = "${local.prefix}-crossaccount"
assume_role_policy = data.databricks_aws_assume_role_policy.this.json
description = "Grants Databricks full access to VPC resources"
}
data "databricks_aws_crossaccount_policy" "this" {}
resource "aws_iam_role_policy" "this" {
name = "${local.prefix}-policy"
role = aws_iam_role.cross_account_role.id
policy = data.databricks_aws_crossaccount_policy.this.json
}
resource "databricks_mws_credentials" "this" {
provider = databricks.mws
account_id = var.databricks_account_id
role_arn = aws_iam_role.cross_account_role.arn
credentials_name = "${local.prefix}-creds"
depends_on = [time_sleep.wait]
}
resource "time_sleep" "wait" {
depends_on = [
aws_iam_role.cross_account_role,
aws_iam_role_policy.this
]
create_duration = "10s"
}
- E2 ワークスペースを作成する。作成したワークスペースURLを表示しています
workspace.tf
resource "databricks_mws_workspaces" "this" {
depends_on = [
databricks_mws_credentials.this,
databricks_mws_storage_configurations.this,
databricks_mws_networks.this
]
provider = databricks.mws
account_id = var.databricks_account_id
aws_region = var.region
workspace_name = local.prefix
deployment_name = local.prefix
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
}
// export host to be used by other modules
output "databricks_host" {
value = databricks_mws_workspaces.this.workspace_url
}
- Terraformの実行
terraform init
terraform plan
terraform apply
- 作成したリソースの一覧を確認する
terraform state list
data.aws_availability_zones.available
data.databricks_aws_assume_role_policy.this
・・・省略
module.vpc.aws_subnet.public[0]
module.vpc.aws_vpc.this[0]
ワークスペースの管理
- ワークスペースを作成するコードとワークスペースを管理するコードとの間で混同を避けるために、別の Terraform モジュールに配置する
- 以下の手順ではワークスペースを管理するためのサービスプリンシパルの認証トークンで作業を行う
サービスプリンシパルと認証トークンの作成
- Terraform用サービスプリンシパルを作成し、ワークスペースへ割り当てた後、認証トークンを作成する
service-principal.tf
provider "databricks" {
alias = "created_workspace"
host = databricks_mws_workspaces.this.workspace_url
account_id = var.databricks_account_id
username = var.databricks_account_username
password = var.databricks_account_password
}
# アカウントレベルのサービスプリンシパルを作成する
resource "databricks_service_principal" "terraform" {
provider = databricks.mws
display_name = "terraform"
}
# サービスプリンシパルをワークスペースに割り当てる
resource "databricks_mws_permission_assignment" "add_terraform" {
provider = databricks.mws
workspace_id = databricks_mws_workspaces.this.workspace_id
principal_id = databricks_service_principal.terraform.id
permissions = ["ADMIN"]
}
# サービスプリンシパルにトークン利用権限を付与する
resource "databricks_permissions" "token_usage" {
provider = databricks.created_workspace
authorization = "tokens"
access_control {
service_principal_name = databricks_service_principal.terraform.application_id
permission_level = "CAN_USE"
}
}
# サービスプリンシパルに管理権限を追加
resource "databricks_entitlements" "terraform" {
provider = databricks.created_workspace
service_principal_id = databricks_service_principal.terraform.id
allow_cluster_create = true
allow_instance_pool_create = true
databricks_sql_access = true
workspace_access = true
}
# サービスプリンシパルの認証トークンを作成する
resource "databricks_obo_token" "this" {
provider = databricks.created_workspace
depends_on = [databricks_permissions.token_usage]
application_id = databricks_service_principal.terraform.application_id
comment = "PAT on behalf of ${databricks_service_principal.terraform.display_name}"
lifetime_seconds = 3600
}
# 認証トークンを出力する
output "obo_token" {
value = databricks_obo_token.this.token_value
sensitive = true
}
発行した認証トークンを確認する
- Terraformでは機密情報(
sensitive)は結果のJsonから非表示となるが、以下のコマンドで機密情報(認証トークン)を表示させることができる
terraform output -raw obo_token
dapi99999999999999999999999999999999
ワークスペース管理用のプロバイダー
- 認証用にワークスペースURLとサービスプリンシパルのトークンを設定するため
terraform.tfvarsファイルを作成する
databricks_account_host="xxx-xxx.cloud.databricks.com"
databricks_account_token="dapi99999999999999999999999999999999"
- ワークスペース用のプロジェクトフォルダを作成し、
providers.tfを作成する
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.64.0"
}
databricks = {
source = "databricks/databricks"
}
}
backend "s3" {
bucket = "cluetechnologies-terraform-state"
key = "databricks/terraform.tfstate"
encrypt = true
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
env = "databricks-terraform-poc"
}
}
}
# サービスプリンシパルでDatabricksプロバイダーを初期化する
provider "databricks" {
alias = "workspace"
host = var.databricks_host
token = var.databricks_token
}
# デフォルトパラメータ
data "databricks_current_user" "me" {}
data "databricks_spark_version" "latest" {}
data "databricks_node_type" "smallest" {
local_disk = true
}
UnityCatalogをTerraformで管理する
- 前回紹介したDatabricksのアクセス制御設定の管理者用グループと公開用グループに対してUnityCagalogのカタログとスキーマの権限設定を行う
- UnityCatalogのカタログをTerraformで管理するためにいくつか選択肢がある
resource "databricks_catalog"でTerraformからカタログを新規作成するterraform import databricks_catalog.<cagalog> <name>でTerraformに既存のカタログをインポートする
- 前者はUnityCatalogのメタストアが管理されていないと作成できないため、UnityCatalogのメタストアがTerraformの管理下にない場合は後者を選択する必要がある。
- 本手順では、既存のカタログをインポートする手順を示す。Databricksコンソールからカタログを作成する
- インポート先の空のカタログ定義を作成しておく。
catalog.tf
resource "databricks_catalog" "domain" {
}
- 既存のカタログをTerraformにインポートする
terraform import databricks_catalog.domain domain
databricks_catalog.domain: Importing from ID "domain"...
・・・略
Import successful!
- tfstateにリソース情報が反映されていることを確認する
terraform state show databricks_catalog.domain
# databricks_catalog.domain:
resource "databricks_catalog" "domain" {
id = "domain"
metastore_id = "6634edc1-c4b5-4111-9b6a-9d8c4f564a96"
name = "domain"
owner = "68f36ce3-4810-4f0c-8f6f-00e954f439ae"
}
- インポート後、カタログの定義を編集する
resource "databricks_catalog" "domain" {
name = "domain"
}
- 定義内容に反映されていることを確認する。
No changes.が出力されていれば反映されている
>terraform plan
・・・略
No changes. Your infrastructure matches the configuration.
domainカタログのALL_PRIVILEGES権限をadmin_groupに付与するcatalog_grant.tf
data "databricks_group" "catalog_admins" {
display_name = "admin_group"
}
resource "databricks_grants" "catalog" {
depends_on = [ databricks_catalog.domain ]
catalog = databricks_catalog.domain.name
grant {
principal = data.databricks_group.catalog_admins.display_name
privileges = [ "ALL_PRIVILEGES" ]
}
}
スキーマを作成
- データ公開用のスキーマ(
product_data)を作成するschema.tf
resource "databricks_schema" "schema_public" {
provider = databricks.workspace
catalog_name = databricks_catalog.domain.id
name = "product_data"
comment = "this database is managed by terraform"
}
product_dataスキーマの参照権限(SELECT)をpublic_groupに付与するschema_grant.tf
data "databricks_group" "schema_public" {
display_name = "public_group"
}
resource "databricks_grants" "schema_public" {
depends_on = [ databricks_schema.schema_public ]
schema = "${databricks_catalog.domain.name}.${databricks_schema.schema_public.name}"
grant {
principal = data.databricks_group.schema_public.display_name
privileges = [ "SELECT" ]
}
}
ストレージ資格情報と外部ロケーションを作成
- ストレージ資格情報の作成に必要なIAMロールを作成して、ストレージ資格情報と外部ロケーションを作成する
variable "external_storage_label" {}
variable "external_storage_location_label" {}
data "aws_iam_policy_document" "passrole_for_unity_catalog" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
identifiers = ["arn:aws:iam::414351767826:role/unity-catalog-prod-UCMasterRole-14S5ZJVKOTYTL"]
type = "AWS"
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.databricks_account_id]
}
}
}
resource "aws_s3_bucket" "external" {
bucket = "${local.prefix}-${var.external_storage_label}"
acl = "private"
versioning {
enabled = false
}
force_destroy = true
tags = merge(local.tags, {
Name = "${local.prefix}-${var.external_storage_label}"
})
}
resource "aws_s3_bucket_public_access_block" "external" {
bucket = aws_s3_bucket.external.id
ignore_public_acls = true
depends_on = [aws_s3_bucket.external]
}
resource "aws_iam_policy" "external_data_access" {
policy = jsonencode({
Version = "2012-10-17"
Id = "${aws_s3_bucket.external.id}-access"
Statement = [
{
"Action" : [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
"s3:PutObjectAcl",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource" : [
aws_s3_bucket.external.arn,
"${aws_s3_bucket.external.arn}/*"
],
"Effect" : "Allow"
}
]
})
tags = merge(local.tags, {
Name = "${local.prefix}-unity-catalog ${var.external_storage_label} access IAM policy"
})
}
resource "aws_iam_role" "external_data_access" {
name = "${local.prefix}-external-access"
assume_role_policy = data.aws_iam_policy_document.passrole_for_unity_catalog.json
managed_policy_arns = [aws_iam_policy.external_data_access.arn]
tags = merge(local.tags, {
Name = "${local.prefix}-unity-catalog ${var.external_storage_label} access IAM role"
})
}
# ストレージ資格情報を作成
resource "databricks_storage_credential" "external" {
name = aws_iam_role.external_data_access.name
aws_iam_role {
role_arn = aws_iam_role.external_data_access.arn
}
}
# 外部ロケーションを作成
resource "databricks_external_location" "raw" {
name = "${var.external_storage_label}"
url = "s3://${local.prefix}-${var.external_storage_label}/${var.external_storage_location_label}"
credential_name = databricks_storage_credential.external.id
}
- パラメータファイル
external-storage.auto.tfvarsを作成する
external_storage_label = "external"
external_storage_location_label = "raw"
- 作成したストレージ資格情報と外部ロケーションの
ALL_PRIVILEGES権限をadmin_groupに付与する
locals {
external_storage_admins_display_name = "admin_group"
external_storage_privileges = "ALL_PRIVILEGES"
}
data "databricks_group" "external_storage_admins" {
display_name = local.external_storage_admins_display_name
}
# 管理者グループにストレージ資格情報の権限を付与
resource "databricks_grants" "external_storage_credential" {
storage_credential = databricks_storage_credential.external.id
grant {
principal = local.external_storage_admins_display_name
privileges = [local.external_storage_privileges]
}
}
# 管理者グループに外部ロケーションの権限を付与
resource "databricks_grants" "external_storage" {
external_location = databricks_external_location.raw.id
grant {
principal = local.external_storage_admins_display_name
privileges = [local.external_storage_privileges]
}
}
リソースの削除
- ワークスペース内のリソースを削除する(ワークスペース管理用プロジェクトで実行する)
terraform destroy
- カタログを削除するには、関連する情報も一括で削除しておく必要がある
- 以下のコマンドでDatabricksのカタログを削除する
drop catalog domain cascade;
