はじめに
Snowflakeについて、これまでSnowSightからガシガシ構築していたのですが、
今後のことも考えてIaCやGit管理、CI/CDの勉強をしていきたいと考えました。
本記事では、Snowflake環境をTerraformで構築する流れを、
実際に試しながら整理しています。
あわせて、ロールベースの権限設計や、
Providerを分けた構成についても簡単に触れています。
環境
- Windows 11 Pro
- WSL2(Ubuntu 24.04)
- Terraform v1.14.8
本記事では、TerraformはWSL上で実行しています。
前提
- Snowflakeはアカウントを作成済みである前提とします
もしまだアカウントが未作成の場合はトライアル30日無料で始めることが可能です
- Terraformがローカル環境で実行できる状態であることを前提とします
上記作業にあたり、以下の記事を参考にさせていただきました。
TerraformでSnowflake上にリソースを作成する手順
tfenvについては今回WSL上に構築するため以下の記事を参考に環境構築を行いました。
wsl ubuntuにtfenvインストール
全体の流れ
今回の構築の流れは以下の通りです。
- Terraform用ユーザの作成
- キーペアの作成と設定
- 環境構築準備
- Terraformによる環境構築
- まとめ
今回作成する環境
今回Terraformを使用して作成するリソース、環境は以下の通りです。
検証用途のためシンプルな構成としつつ、実務を意識して「部門単位」でデータを分けた構成とします。
本記事では、MARKETING と SALES の2つの部門を想定し、それぞれのDBとロールを作成します。
また、Snowflakeではユーザに直接権限を付与するのではなく、
ロールに権限を持たせてユーザに割り当てる形で管理するのが一般的です。
【ユーザ】
| 用途 | ユーザ名 | 割り当てロール |
|---|---|---|
| (マーケ)作業用ユーザ | marketing_app_user | marketing_writer_role |
| (マーケ)参照用ユーザ | marketing_readonly_user | marketing_reader_role |
| (営業)作業用ユーザ | sales_app_user | sales_writer_role |
| (営業)参照用ユーザ | sales_readonly_user | sales_reader_role |
【ロール】
部門ごとにロールを分け、「参照」と「書込」の権限を分離します。
さらに、上位ロールでまとめることにより拡張しやすい構成とします。
■ 上位ロール(部門単位)
| ロール名 | 用途 |
|---|---|
| marketing_role | MARKETING部門の権限をまとめるロール |
| sales_role | SALES部門の権限をまとめるロール |
■ 下位ロール(権限単位)
| ロール名 | 用途 | 権限 |
|---|---|---|
| marketing_writer_role | MARKETING DBへの書込ロール | SELECT / INSERT / UPDATE / DELETE |
| marketing_reader_role | MARKETING DBの参照ロール | SELECTのみ |
| sales_writer_role | SALES DBへの書込ロール | SELECT / INSERT / UPDATE / DELETE |
| sales_reader_role | SALES DBの参照ロール | SELECTのみ |
■ ロールの関係
marketing_role
├─ marketing_writer_role
└─ marketing_reader_role
sales_role
├─ sales_writer_role
└─ sales_reader_role
【DB】
| 用途 | DB名 |
|---|---|
| マーケティング部門DB | MARKETING |
| 営業部門DB | SALES |
【スキーマ】
各DB内に以下のスキーマを作成します。
| 用途 | スキーマ名 |
|---|---|
| 生データ | raw |
| 整形データ | staging |
| 各種分析、公開用 | mart |
| 自由利用 | sandbox |
【ウェアハウス】
Snowflakeでは、クエリ実行やデータ処理の計算資源としてウェアハウスを利用します。
今回はこちらも部門単位で分ける構成とし、各ロールに対して利用権限を付与します。
| 用途 | ウェアハウス名 |
|---|---|
| マーケティング部門用 | MARKETING_WH |
| 営業部門用 | SALES_WH |
認証方式について
Snowflakeではキーペア認証を利用できます。
キーペア認証では、秘密鍵をクライアント側で保持し、対応する公開鍵をSnowflakeユーザーに設定して認証を行います。
本記事ではTerraformからの接続方式としてキーペア認証を使用します。
Terraformファイル構成
今回のTerraformコードは、役割ごとにファイルを分けて管理します。
snowflake-terraform/
├─ main.tf
├─ provider.tf
├─ variables.tf
├─ roles.tf
├─ users.tf
├─ warehouses.tf
├─ databases.tf
├─ grants.tf
├─ outputs.tf
└─ versions.tf
| ファイル名 | 内容 | 使用Provider |
|---|---|---|
| versions.tf | Terraform本体とSnowflake Providerのバージョン定義 | - |
| provider.tf | Snowflakeへの接続設定 | - |
| variables.tf | 接続情報や共通値の変数定義 | - |
| main.tf | 共通設定やローカル値の定義 | - |
| roles.tf | ロールおよびロール継承の定義 | SECURITYADMIN |
| users.tf | ユーザ作成とロール割り当て | SECURITYADMIN |
| grants.tf | 各ロールへの権限付与 | SECURITYADMIN |
| warehouses.tf | ウェアハウス作成 | SYSADMIN |
| databases.tf | DBおよびスキーマ作成 | SYSADMIN |
| outputs.tf | 確認用の出力 | - |
各ファイルの役割
versions.tf
Terraform本体とProviderのバージョンを固定するファイルです。
terraform {
required_version = ">= 1.1.5"
required_providers {
snowflake = {
source = "snowflakedb/snowflake"
version = "2.14.1"
}
}
}
provider.tf
Snowflakeへ接続するための設定を書きます。
キーペア認証を利用するため、authenticator に SNOWFLAKE_JWT を指定し、
秘密鍵を file() 関数で読み込んでいます。
※Snowflake Providerでは、private_key を指定することで
キーペア認証として扱われます。
本記事では検証用途として組み込みロールを利用し、
TerraformのProviderをロールごとに分けて構成しています。
各ロールの責務は以下の通りです。
- SECURITYADMIN:ユーザ・ロール・権限管理
- SYSADMIN:DB・スキーマ・ウェアハウス作成
実務では、Providerを分ける方法だけでなく、
Terraform専用のロールを作成し、必要最小限の権限のみを付与する構成も検討するとよいかと思います。
※今回は分かりやすさを優先し、各リソースに対して provider を明示的に指定する形としました。
Terraformでは module 化して provider を整理する方法もありますが、
今後の課題とします。
provider "snowflake" {
alias = "securityadmin"
organization_name = var.snowflake_organization
account_name = var.snowflake_account
user = var.snowflake_user
role = "SECURITYADMIN"
warehouse = var.snowflake_warehouse
authenticator = "SNOWFLAKE_JWT"
private_key = file(var.snowflake_private_key_path)
}
provider "snowflake" {
alias = "sysadmin"
organization_name = var.snowflake_organization
account_name = var.snowflake_account
user = var.snowflake_user
role = "SYSADMIN"
warehouse = var.snowflake_warehouse
authenticator = "SNOWFLAKE_JWT"
private_key = file(var.snowflake_private_key_path)
}
variables.tf
接続情報や共通値を変数化します。
これらの値は .env ファイルで TF_VAR_ として設定し、
Terraform実行時に読み込む形としています。
※Terraform専用ロールの使用を想定してsnowflake_roleも記載しております
variable "snowflake_organization" {
description = "Snowflake組織名"
type = string
}
variable "snowflake_account" {
description = "Snowflakeアカウント識別子"
type = string
}
variable "snowflake_user" {
description = "Terraformで利用するユーザ"
type = string
}
variable "snowflake_role" {
description = "接続時に利用するロール"
type = string
}
variable "snowflake_warehouse" {
description = "接続時に利用するウェアハウス"
type = string
}
variable "snowflake_private_key_path" {
description = "秘密鍵のパス"
type = string
sensitive = true
}
roles.tf
ロールの定義を行います。
# -----------------------------
# MARKETING ROLES
# -----------------------------
resource "snowflake_account_role" "marketing_role" {
provider = snowflake.securityadmin
name = "MARKETING_ROLE"
}
resource "snowflake_account_role" "marketing_writer_role" {
provider = snowflake.securityadmin
name = "MARKETING_WRITER_ROLE"
}
resource "snowflake_account_role" "marketing_reader_role" {
provider = snowflake.securityadmin
name = "MARKETING_READER_ROLE"
}
# ロール継承
resource "snowflake_grant_account_role" "marketing_writer_to_parent" {
provider = snowflake.securityadmin
role_name = snowflake_account_role.marketing_writer_role.name
parent_role_name = snowflake_account_role.marketing_role.name
}
resource "snowflake_grant_account_role" "marketing_reader_to_parent" {
provider = snowflake.securityadmin
role_name = snowflake_account_role.marketing_reader_role.name
parent_role_name = snowflake_account_role.marketing_role.name
}
# -----------------------------
# SALES ROLES
# -----------------------------
resource "snowflake_account_role" "sales_role" {
provider = snowflake.securityadmin
name = "SALES_ROLE"
}
resource "snowflake_account_role" "sales_writer_role" {
provider = snowflake.securityadmin
name = "SALES_WRITER_ROLE"
}
resource "snowflake_account_role" "sales_reader_role" {
provider = snowflake.securityadmin
name = "SALES_READER_ROLE"
}
# ロール継承
resource "snowflake_grant_account_role" "sales_writer_to_parent" {
provider = snowflake.securityadmin
role_name = snowflake_account_role.sales_writer_role.name
parent_role_name = snowflake_account_role.sales_role.name
}
resource "snowflake_grant_account_role" "sales_reader_to_parent" {
provider = snowflake.securityadmin
role_name = snowflake_account_role.sales_reader_role.name
parent_role_name = snowflake_account_role.sales_role.name
}
users.tf
ユーザ定義を行います。
※実際に試す場合はPWを変更してください。
また、実際の運用ではPW認証だけでなくMFAやSSOを利用する構成が一般的です。
# -----------------------------
# MARKETING USERS
# -----------------------------
resource "snowflake_user" "marketing_app_user" {
provider = snowflake.securityadmin
name = "MARKETING_APP_USER"
login_name = "MARKETING_APP_USER"
default_role = snowflake_account_role.marketing_writer_role.name
password = "TempPassword123!"
}
resource "snowflake_user" "marketing_readonly_user" {
provider = snowflake.securityadmin
name = "MARKETING_READONLY_USER"
login_name = "MARKETING_READONLY_USER"
default_role = snowflake_account_role.marketing_reader_role.name
password = "TempPassword123!"
}
# -----------------------------
# SALES USERS
# -----------------------------
resource "snowflake_user" "sales_app_user" {
provider = snowflake.securityadmin
name = "SALES_APP_USER"
login_name = "SALES_APP_USER"
default_role = snowflake_account_role.sales_writer_role.name
password = "TempPassword123!"
}
resource "snowflake_user" "sales_readonly_user" {
provider = snowflake.securityadmin
name = "SALES_READONLY_USER"
login_name = "SALES_READONLY_USER"
default_role = snowflake_account_role.sales_reader_role.name
password = "TempPassword123!"
}
databases.tf
データベース、スキーマを定義します
# -----------------------------
# MARKETING DATABASE
# -----------------------------
resource "snowflake_database" "marketing" {
provider = snowflake.sysadmin
name = "MARKETING"
}
resource "snowflake_schema" "marketing_raw" {
provider = snowflake.sysadmin
database = snowflake_database.marketing.name
name = "RAW"
}
resource "snowflake_schema" "marketing_staging" {
provider = snowflake.sysadmin
database = snowflake_database.marketing.name
name = "STAGING"
}
resource "snowflake_schema" "marketing_mart" {
provider = snowflake.sysadmin
database = snowflake_database.marketing.name
name = "MART"
}
resource "snowflake_schema" "marketing_sandbox" {
provider = snowflake.sysadmin
database = snowflake_database.marketing.name
name = "SANDBOX"
}
# -----------------------------
# SALES DATABASE
# -----------------------------
resource "snowflake_database" "sales" {
provider = snowflake.sysadmin
name = "SALES"
}
resource "snowflake_schema" "sales_raw" {
provider = snowflake.sysadmin
database = snowflake_database.sales.name
name = "RAW"
}
resource "snowflake_schema" "sales_staging" {
provider = snowflake.sysadmin
database = snowflake_database.sales.name
name = "STAGING"
}
resource "snowflake_schema" "sales_mart" {
provider = snowflake.sysadmin
database = snowflake_database.sales.name
name = "MART"
}
resource "snowflake_schema" "sales_sandbox" {
provider = snowflake.sysadmin
database = snowflake_database.sales.name
name = "SANDBOX"
}
warehouses.tf
ウェアハウスを定義します。
今回は検証用途の為、auto_suspendを最小の60秒に設定しています。
resource "snowflake_warehouse" "marketing_wh" {
provider = snowflake.sysadmin
name = "MARKETING_WH"
warehouse_size = "XSMALL"
auto_suspend = 60
auto_resume = true
}
resource "snowflake_warehouse" "sales_wh" {
provider = snowflake.sysadmin
name = "SALES_WH"
warehouse_size = "XSMALL"
auto_suspend = 60
auto_resume = true
}
grants.tf
権限を定義します。
今回は権限設計の詳細な説明は割愛しますが、
最低限利用できる形になるよう、ロールに対してDB、スキーマ、テーブル、ウェアハウスの権限を付与します。
また、将来追加されるテーブルにも権限が自動で付与されるように、
future tables に対する権限もあわせて設定します。
本記事では MARKETING 側のみ記載していますが、SALES 側も同様の考え方で付与します。
※重複部分をlocalsとfor_eachを使用して短くできますが今回は割愛させていただきます。
# --------------------------------------
# MARKETING: DATABASE
# --------------------------------------
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_db_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_account_object {
object_type = "DATABASE"
object_name = snowflake_database.marketing.name
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_db_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_account_object {
object_type = "DATABASE"
object_name = snowflake_database.marketing.name
}
}
# --------------------------------------
# MARKETING: ALL SCHEMAS
# --------------------------------------
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_all_schemas_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
all {
object_type_plural = "SCHEMAS"
in_database = snowflake_database.marketing.name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_all_schemas_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
all {
object_type_plural = "SCHEMAS"
in_database = snowflake_database.marketing.name
}
}
}
# --------------------------------------
# MARKETING: ALL TABLES IN ALL TARGET SCHEMAS
# ※ RAW / STAGING / MART / SANDBOX を個別に付与
# --------------------------------------
# RAW
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_raw_all_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_raw.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_raw_future_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_raw.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_raw_all_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_raw.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_raw_future_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_raw.fully_qualified_name
}
}
}
# STAGING
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_staging_all_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_staging.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_staging_future_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_staging.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_staging_all_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_staging.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_staging_future_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_staging.fully_qualified_name
}
}
}
# MART
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_mart_all_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_mart.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_mart_future_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_mart.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_mart_all_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_mart.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_mart_future_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_mart.fully_qualified_name
}
}
}
# SANDBOX
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_sandbox_all_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_sandbox.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_sandbox_future_tables_select" {
provider = snowflake.securityadmin
privileges = ["SELECT"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_sandbox.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_sandbox_all_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_sandbox.fully_qualified_name
}
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_sandbox_future_tables_dml" {
provider = snowflake.securityadmin
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = snowflake_schema.marketing_sandbox.fully_qualified_name
}
}
}
# --------------------------------------
# MARKETING: WAREHOUSE
# --------------------------------------
resource "snowflake_grant_privileges_to_account_role" "marketing_reader_wh_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_reader_role.name
on_account_object {
object_type = "WAREHOUSE"
object_name = snowflake_warehouse.marketing_wh.name
}
}
resource "snowflake_grant_privileges_to_account_role" "marketing_writer_wh_usage" {
provider = snowflake.securityadmin
privileges = ["USAGE"]
account_role_name = snowflake_account_role.marketing_writer_role.name
on_account_object {
object_type = "WAREHOUSE"
object_name = snowflake_warehouse.marketing_wh.name
}
}
認証情報の管理
TerraformからSnowflakeへ接続するための認証情報は、.env ファイルにまとめて管理します。
本記事では TF_VAR_ を利用してTerraform変数として受け取る形にしています。
認証情報や環境依存の値は .tfvars ではなく、
環境変数として管理することで、コードと分離することができます。
.env の例は以下の通りです。
※Terraform専用ロールの使用を想定してsnowflake_roleも記載しております
export TF_VAR_snowflake_organization="xxxxxxx"
export TF_VAR_snowflake_account="xxxxx"
export TF_VAR_snowflake_user="terraform_user"
export TF_VAR_snowflake_role="SECURITYADMIN"
export TF_VAR_snowflake_warehouse="COMPUTE_WH"
export TF_VAR_snowflake_private_key_path="/home/<user>/.ssh/snowflake_key.pem"
環境変数の読み込み
Terraformでは TF_VAR_変数名 の形式で環境変数を定義することで、
Terraformの変数に値を渡すことができます。
.env ファイルを利用する場合は、以下のように読み込みます。
set -a
source .env
set +a
1 Terraform用ユーザの作成
TerraformからSnowflakeを操作するためのユーザを作成します。
Terraformから利用するユーザはサービスユーザとして扱うため、
パスワードは設定せず、キーペア認証のみで接続します。
サービスユーザは、人が直接ログインして利用するユーザではなく、
システムやツールからのアクセス専用として利用されるユーザです。
本記事では検証を目的としているため、DEFAULT_ROLE には SYSADMIN を指定し、
SYSADMINロールとSECURITYADMINロールを付与しています。
実務では、Terraform専用のロールを作成し、
どのリソースをTerraformで作成・管理するかを検討したうえで、
必要最小限の権限のみを付与する構成がよいかと思います。
WAREHOUSE についても同様に、実務では専用のWHを作成して適用した方が、
コスト管理はしやすいかと思います。
Snowflake上で terraform_user を作成します。
CREATE USER terraform_user
TYPE = SERVICE
DEFAULT_ROLE = SECURITYADMIN
DEFAULT_WAREHOUSE = COMPUTE_WH
COMMENT = "terraform用サービスユーザ";
GRANT ROLE SYSADMIN TO USER terraform_user;
GRANT ROLE SECURITYADMIN TO USER terraform_user;
GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE SECURITYADMIN;
2 キーペアの作成と設定
TerraformからSnowflakeへ接続するために、キーペア認証を設定します。
2-1 キーペアの作成
ローカル環境にてキーペアを作成します。
cd ~/.ssh
openssl genrsa -out snowflake_key.pem 2048
openssl rsa -in snowflake_key.pem -pubout -out snowflake_key.pub
2-2 キーペア設定
terraform_userに公開鍵を設定します。
今回3-1で作成したsnowflake_key.pubの内容を設定します。
ALTER USER terraform_user SET RSA_PUBLIC_KEY='公開鍵';
設定する際の注意点として
- -----BEGIN PUBLIC KEY-----から-----END PUBLIC KEY-----までの間を設定します
- 公開鍵はそのままの状態だと改行が含まれているため、Snowflakeに設定する際は1行に整形します
整形する場合は以下のコマンドが便利です。
sed '1d;$d' snowflake_key.pub | tr -d '\n'
3 環境構築準備
前述したファイル構成を準備し、以下を修正します。
3-1 envの接続情報
以下SQLをSnowflake上で実施し、組織名とアカウント名を確認して反映します。
SELECT LOWER(current_organization_name()) as your_org_name, LOWER(current_account_name()) as your_account_name;
秘密鍵のPathを指定します。
3-2 users.tfのパスワード
パスワードを基準を満たしたパスワードへと変更します。
14文字以上、1文字以上の特殊文字、大文字、小文字を含むこと
4 Terraformによる環境構築
4-1 環境変数の読み込み
Terraform実行前に、.env ファイルを読み込みます。
set -a
source .env
set +a
4-2 Terraformの初期化
Terraformを実行する前に、初期化を行います。
terraform init
このコマンドでは、以下の処理が行われます。
必要なProviderのダウンロード
バックエンドの初期化
初回のみ実行すればOKです。
4-3 実行計画の確認
次に、作成されるリソースの内容を確認します。
terraform plan
ここでエラーが出ていないことを確認します。
4-4 リソースの作成
問題がなければ、以下のコマンドで実際に環境を作成します。
terraform apply
実行すると確認メッセージが表示されるため、yes を入力して処理を進めます。
4-5 動作確認
Terraformの実行が完了したら、Snowflake上でリソースが作成されていることを確認します。
一例としてMARKETINGDBのMARTスキーマを見てみると
きちんとMARKETING_READER_ROLEには参照権限が、
MARKETING_WRITER_ROLEには参照、挿入、更新、削除権限が
付与されていることがわかります。
全てFUTURE TABLEとなっているため、今後スキーマ配下に
作成された全てのテーブルにこれらの権限が自動的に付与されます。
5 まとめ
Terraformを利用することで、Snowflakeの環境をコードで管理できるようになりました。
手動での構築と比較して、
- 再現性がある
- 構成の変更履歴を管理できる
- 環境差分を把握しやすい
といったメリットがあります。
今回はシンプルな構成で実装しましたが、
実務ではロール設計や権限管理、環境分離などを考慮する必要があります。
また、今の区切り方ではterraform構成ファイルが簡単に肥大化し、
DBが増えた場合の更新ポイントも分散していて作業ミスを誘発しやすい構成になりがちだと感じました。
特に、部門やDBが増えた場合には変更箇所が複数ファイルに分散し、
保守性の観点で課題があると感じています。
そのあたりのベストプラクティスについても模索していきたいと考えています。
参考文献
https://registry.terraform.io/providers/snowflakedb/snowflake/latest/docs
https://www.snowflake.com/en/developers/guides/terraforming-snowflake/#0

