【Terraform de Azure】 Azure Synapse Analytics を 作成してみました

## 概要

「Infrastructure as Code」ということで、Terraform を用いて Azure Synapse Analytics を作成し、Synapse Studio に接続し SQLプール を確認してみます。

### ローカル環境

- macOS Monterey 12.3
- python 3.8.12
- Azure CLI 2.34.1
- terraform v1.0.11

### 前提条件

1. Azure環境がすでに用意されていること（テナント/サブスクリプション）
2. ローカル環境に「azure cli」がインストールされていること
3. ローカル環境に「terraform」環境が構成されていること
4. TerraformでAzure上に環境構築するための[サービスプリンシパルが作成](https://qiita.com/turupon/items/5d024d43a60cd101be94)されており、Terraform のためのローカル環境変数が定義されていること

---

## Azure Synapse Analytics を作成してみる

### terraform 定義ファイルの作成

プロバイダの定義

```main.tf
# プロバイダーの定義
terraform {
  required_providers {
    azurerm =  "~> 2.33"
  }
}

provider "azurerm" {
  features {}
  tenant_id       = var.ARM_TENANT_ID
  subscription_id = var.ARM_SUBSCRIPTION_ID
  client_id       = var.ARM_CLIENT_ID
  client_secret   = var.ARM_CLIENT_SECRET
}


# リソースグループ
resource "azurerm_resource_group" "this" {
  name     = var.resource_group_name
  location = var.region
  tags     = var.tags_def
}
```

パラメータ定義ファイル

```variables.tf
# 環境変数（Azureサービスプリンシパル）
variable ARM_TENANT_ID {}
variable ARM_SUBSCRIPTION_ID {}
variable ARM_CLIENT_ID {}
variable ARM_CLIENT_SECRET {}

# タグ情報
variable tags_def {
  default = {
    owner      = "ituru"
    period     = "2022-06-30"
    Environment = "CP_Demo"
  }
}

# 各種パラメータ 
variable resource_group_name {}
variable region {}

variable storage_account {}
variable gen2_name {}
variable workspace_name {}
variable sql_pool_name {}
variable sql_admin {}
variable sql_admin_pass {}

variable fw_rule01 {}
variable home_tokyo_ip {}
variable fw_rule02 {}
variable hq_tokyo_ip {}
variable fw_rule03 {}
variable all_azure_ip {}
```

パラメータ値定義ファイル

```terraform.tfvars
# 環境変数の定義（Azureサービスプリンシパル）
ARM_TENANT_ID       = "zzzzzzzz-cccc-4645-5757-zzzzzzzzzzzz"
ARM_SUBSCRIPTION_ID = "xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz"
ARM_CLIENT_ID       = "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx"
ARM_CLIENT_SECRET   = "hogehogehogehogehogehogehogehogege"

# パラメータ値の定義
resource_group_name = "rg_ituru_synapse01"          // リソースグループ名
region              = "japaneast"                   // 利用リージョン

storage_account     = "iturusynapse"                // ストレージアカウント名
gen2_name           = "iturucpgen2"                 // Data Lake Gen2名
workspace_name      = "cpworkspace01"               // ワークスペース名
sql_pool_name       = "cpdemosqldb"                 // SQL Pool名
sql_admin           = "adminadmin"                  // Synapse管理者名
sql_admin_pass      = "HogeHogeHoge1!"              // Synapse管理者パスワード

fw_rule01           = "Home_Tokyo"                  // Firewallルール名_01
home_tokyo_ip       = "133.133.133.133"             // 東京宅のIPアドレス
fw_rule02           = "HQ_Tokyo"                    // Firewallルール名_02
hq_tokyo_ip         = "155.155.155.155"             // 本社のIPアドレス
fw_rule03           = "AllowAllWindowsAzureIps"     // Firewallルール名_03
all_azure_ip        = "0.0.0.0"                     // AzureのIPアドレス
```

Synapse定義ファイル

```Synapse.tf
# Storage Account
resource "azurerm_storage_account" "this" {
  name                      = var.storage_account
  resource_group_name       = azurerm_resource_group.this.name
  location                  = azurerm_resource_group.this.location
  tags                      = var.tags_def
  account_tier              = "Standard"
  account_replication_type  = "LRS"
  account_kind              = "StorageV2"
  is_hns_enabled            = "true"
}

# Data Lake Gen2
resource "azurerm_storage_data_lake_gen2_filesystem" "this" {
  name                = var.gen2_name
  storage_account_id  = azurerm_storage_account.this.id
}

# Synapse Analytics Workspace
resource "azurerm_synapse_workspace" "this" {
  name                                  = var.workspace_name
  resource_group_name                   = azurerm_resource_group.this.name
  location                              = azurerm_resource_group.this.location
  storage_data_lake_gen2_filesystem_id  = azurerm_storage_data_lake_gen2_filesystem.this.id
  sql_administrator_login               = var.sql_admin
  sql_administrator_login_password      = var.sql_admin_pass
  tags                                  = var.tags_def
}

# Synapse Analytics SQL Pool
resource "azurerm_synapse_sql_pool" "this" {
  name                  = var.sql_pool_name
  synapse_workspace_id  = azurerm_synapse_workspace.this.id
  sku_name              = "DW100c"
  create_mode           = "Default"
  collation             = "Japanese_CI_AS"
  tags                  = var.tags_def
}

# Manages a Firewall Rule for Synapse Analytics
resource "azurerm_synapse_firewall_rule" "rule01" {
  name                  = var.fw_rule01
  synapse_workspace_id  = azurerm_synapse_workspace.this.id
  start_ip_address      = var.home_tokyo_ip
  end_ip_address        = var.home_tokyo_ip
}

resource "azurerm_synapse_firewall_rule" "rule02" {
  name                  = var.fw_rule02
  synapse_workspace_id  = azurerm_synapse_workspace.this.id
  start_ip_address      = var.hq_tokyo_ip
  end_ip_address        = var.hq_tokyo_ip
}

resource "azurerm_synapse_firewall_rule" "rule03" {
  name                  = var.fw_rule03
  synapse_workspace_id  = azurerm_synapse_workspace.this.id
  start_ip_address      = var.all_azure_ip
  end_ip_address        = var.all_azure_ip
}

## storage account への roleの割当
data "azurerm_client_config" "current" {}
resource "azurerm_role_assignment" "assign01" {
  scope                = azurerm_storage_account.this.id
  role_definition_name = "Storage Blob Data Contributor"
  principal_id         = data.azurerm_client_config.current.object_id
}

resource "azurerm_role_assignment" "assign02" {
  scope                = azurerm_storage_account.this.id
  role_definition_name = "Storage Blob Data Contributor"
  principal_id         = azurerm_synapse_workspace.this.identity[0].principal_id
}
```

### terraform の実行

```
## init
$ terraform init
    :
Terraform has been successfully initialized!

## plan
$ terraform plan
    :
Plan: 10 to add, 0 to change, 0 to destroy.

## apply
$ terraform apply
    :
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
```

### ローカルの作業ディレクトの状況

```
$ tree -a
.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── azurerm
│                   └── 2.99.0
│                       └── darwin_amd64
│                           └── terraform-provider-azurerm_v2.99.0_x5
├── .terraform.lock.hcl
├── Synapse.tf
├── main.tf
├── outputs.tf
├── terraform.tfstate
├── terraform.tfstate.backup
├── terraform.tfvars
└── variables.tf
```

---

## terraform 実行後の確認

### Azure CLI からの確認

```
## 作成先サブスクリプションへの接続
$ az account set --subscription '<Subscription_Name>'


## 作成した Synapse Workspace の確認
$ az synapse workspace list --resource-group rg_ituru_synapse01 -o table
Location    ManagedResourceGroupName                                         Name           ProvisioningState    PublicNetworkAccess    ResourceGroup       SqlAdministratorLogin    WorkspaceUid
----------  ---------------------------------------------------------------  -------------  -------------------  ---------------------  ------------------  -----------------------  ------------------------------------
japaneast   synapseworkspace-managedrg-ae333c64-00dd-469f-b06a-0495ac3619e2  cpworkspace01  Succeeded            Enabled                rg_ituru_synapse01  adminadmin               478d9805-2f84-438b-85c8-0738cecfa3b9


## 作成した Synapse Sql Pool の確認
$ az synapse sql pool list --resource-group rg_ituru_synapse01 --workspace-name cpworkspace01 
[
  {
    "collation": "Japanese_CI_AS",
    "createMode": null,
    "creationDate": "2022-05-19T23:57:36.547000+00:00",
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_synapse01/providers/Microsoft.Synapse/workspaces/cpworkspace01/sqlPools/cpsqlpool01",
    "location": "japaneast",
    "maxSizeBytes": 263882790666240,
    "name": "cpsqlpool01",
    "provisioningState": "Succeeded",
    "recoverableDatabaseId": null,
    "resourceGroup": "rg_ituru_synapse01",
    "restorePointInTime": null,
    "sku": {
      "capacity": 0,
      "name": "DW100c",
      "tier": null
    },
    "sourceDatabaseDeletionDate": null,
    "sourceDatabaseId": null,
    "status": "Online",
    "storageAccountType": "GRS",
    "tags": {},
    "type": "Microsoft.Synapse/workspaces/sqlPools"
  }
]


## Firewallルール一覧表示
$ az synapse workspace firewall-rule list --resource-group rg_ituru_synapse01 --workspace-name cpworkspace01
[
  {
    "endIpAddress": "0.0.0.0",
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_synapse01/providers/Microsoft.Synapse/workspaces/cpworkspace01/firewallRules/AllowAllWindowsAzureIps",
    "name": "AllowAllWindowsAzureIps",
    "provisioningState": "Succeeded",
    "resourceGroup": "rg_ituru_synapse01",
    "startIpAddress": "0.0.0.0",
    "type": "Microsoft.Synapse/workspaces/firewallRules"
  },
  {
    "endIpAddress": "133.133.133.133",
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_synapse01/providers/Microsoft.Synapse/workspaces/cpworkspace01/firewallRules/Home_Tokyo",
    "name": "Home_Tokyo",
    "provisioningState": "Succeeded",
    "resourceGroup": "rg_ituru_synapse01",
    "startIpAddress": "133.133.133.133",
    "type": "Microsoft.Synapse/workspaces/firewallRules"
  },
  {
    "endIpAddress": "155.155.155.155",
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_synapse01/providers/Microsoft.Synapse/workspaces/cpworkspace01/firewallRules/HQ_Tokyo",
    "name": "HQ_Tokyo",
    "provisioningState": "Succeeded",
    "resourceGroup": "rg_ituru_synapse01",
    "startIpAddress": "155.155.155.155",
    "type": "Microsoft.Synapse/workspaces/firewallRules"
  }
]
```

### Synapse Studio への接続

Azure Portal へサインインし、上記で作成した Synapse Workspace リソースの 「概要」の画面にある「作業の開始」から「Synapse Studio を開く」を押します。

新たに Synapse Studio の画面が表示されるときに、ポップアップの「読み込めませんでした」エラー画面されます。画面左の「管理」アイコンを選択すると、切り替わった画面上部に「サーバーレス SQL プール (組み込み) へのアクセス権がありません。アクセス権を取得するには、このワークスペースの Synapse 管理者にお問い合わせください」との警告メッセージが表示されます。

正常に Azure Studio を利用するために、Synapse管理者として、Azure Portal にサインインしている自分自身を登録する必要がありますので、画面左の「セキュリティ」-「アクセス制御」を選択し、自身を Synapse管理者 として追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/328363c6-0d94-daa8-d732-53cbbea6e3f9.png)

追加後、約3〜5分後に　Azure Workplace と Terraformサービスプリンシパル と合わせて Synapse管理者としてアクセス権が付与されたことを確認できます。また、画面上部に表示されていた警告メッセージが消えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/fc271f0d-b764-19e0-ff71-339ed49fbf0d.png)

Synapse Studio の画面左側の「セキュリティ」-「資格情報」を選択することにより資格情報を確認することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/4fe17597-c1af-793e-9b1c-58daa40a5873.png)

#### SQLプールの確認

Synapse Studio の画面左側の「Analyticsプール」-「SQLプール」を選択することにより、組み込みのSQLプールと作成したSQLプールがオンラインになっていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/ee941c9c-a4c5-ae82-5836-c6a920f30133.png)

---

### その他

#### Terraformサービスプリンシパルへの権限追加

本件では、作成するストレージアカウントに、追加で「ストレージBLOB共同作成者」のロールの割当が必要となるため、Terraformサービスプリンシパルに「所有者」の権限を付与しました。

```
## 必要なスコープに必要なロールを割り与えるための appId の取得
$ APP_ID=$(az ad sp list --display-name sp_itutu_terraform --query '[].{ID:appId}' --output tsv)

## 必要なスコープに必要なロールを割り与えるための サブスクリプションID の取得
$ SUBS_ID=$(az account show --query "id" --output tsv)

## 必要なスコープに必要なロールの割当て
$ az role assignment create --assignee $APP_ID --scope /subscriptions/$SUBS_ID --role Owner
```

#### 作成したリソースの削除

```
## destroy
$ terraform destroy
```

---

### まとめ

これで、Terraform でサクッと Azure環境上に Azure Synapse Analytics を作成できました。 Synapse Studio のポップアップのエラーをなくすために少々てこずりましたが、半IaC化もいいですね。


### 参考記事

以下の記事を参考にさせていただきました。
[azurerm_synapse_sql_pool](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/synapse_sql_pool)
[azurerm_synapse_firewall_rule](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/synapse_firewall_rule)
[az synapse](https://docs.microsoft.com/ja-jp/cli/azure/synapse?view=azure-cli-latest)
[ワークスペースのマネージド ID にアクセス許可を付与する](https://docs.microsoft.com/ja-jp/azure/synapse-analytics/security/how-to-grant-workspace-managed-identity-permissions#grant-the-managed-identity-permissions-to-adls-gen2-storage-account)