TerraformでSnowflakeのリソースを作成してみた

テラスノ
2021.06.25

大阪オフィスの玉井です。

Infrastructure as Code(IaC)を実現するためのツールとしておなじみのTerraformですが、実はSnowflakeにも対応しています。というわけで、実際にやってみました。

概要など

厳密にいうと、Chan Zuckerberg InitiativeがSnowflake用のproviderを開発しており、それを利用して、TerraformでSnowflakeを管理することができます。

下記のウェビナーで詳しい説明があるのでどうぞ。

ちなみに、「DevOps: Terraforming Snowflake」という名前の、Snowflake公式のチュートリアルがあります(私もやりました)。

やってみた

環境

  • macOS Big Sur 11.4
  • Terraform v1.0.0

やってみる内容

「開発者に、個人用のSnowflakeリソース一式を用意してあげる」っていうのを想定して、下記をTerraformで一気に作ってみたいと思います。

  • ユーザー
  • ロール
  • DB
  • スキーマ
  • 仮想ウェアハウス

ちなみに、テーブルは今回つくりません(理由は後述)。

Snowflakeとの認証

Terraformが(ユーザーに代わって)Snowflakeのリソースを作成するので、まずはTerraform用のユーザーを作成します。割当ロールについて、今回はとりあえずSYSADMINSECURITYADMINを使えるようにしていますが、本番運用する際は、より適切なカスタムロールを作って、それを割り当てる方が良いです。

CREATE USER "tf-snow" DEFAULT_ROLE=PUBLIC MUST_CHANGE_PASSWORD=FALSE;
GRANT ROLE SYSADMIN TO USER "tf-snow";
GRANT ROLE SECURITYADMIN TO USER "tf-snow";

続いて、Terraformが使用するSnowflakeに対する認証情報の準備です。公式ドキュメントを見るに、tfファイルにベタ書きすることもできそうですが、今回は、冒頭で紹介したチュートリアルにならって、環境変数に認証に必要な情報を入れて、それをProvider側で使用する方法をとります。

環境変数にぶちこんでいきます。

> export SNOWFLAKE_USER="tf-snow"
> export SNOWFLAKE_ACCOUNT="Snowflakeのアカウント名(URLの最初)"
> export SNOWFLAKE_REGION="Snowflakeのリージョン名(URLの真ん中)"

コード本体

今回のコードは以下の通り。今回はとりあえず全部main.tfに書いています。

terraform {
  required_providers {
    snowflake = {
      source  = "chanzuckerberg/snowflake"
      version = "0.22.0"
    }
  }
}

provider "snowflake" {
  role = "SYSADMIN"
}

resource "snowflake_database" "db" {
  name = "TIGER_DB"
}

resource "snowflake_schema" "schema" {
  database            = snowflake_database.db.name
  name                = "TIGER_SCHEMA"
  is_managed          = false
  data_retention_days = 90
}

resource "snowflake_warehouse" "warehouse" {
  name                = "TIGER_WAREHOUSE"
  initially_suspended = true
  warehouse_size      = "xsmall"
  max_cluster_count   = 1
  min_cluster_count   = 1
  auto_suspend        = 60
}

provider "snowflake" {
  alias = "security_admin"
  role  = "SECURITYADMIN"
}

resource "snowflake_role" "role" {
  provider = snowflake.security_admin
  name     = "TIGER_ROLE"
}

resource "snowflake_database_grant" "grant" {
  database_name     = snowflake_database.db.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
}

resource "snowflake_schema_grant" "grant" {
  database_name     = snowflake_database.db.name
  schema_name       = snowflake_schema.schema.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
}

resource "snowflake_warehouse_grant" "grant" {
  warehouse_name    = snowflake_warehouse.warehouse.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
}

resource "snowflake_user" "user" {
  provider          = snowflake.security_admin
  name              = "SATORU_SAYAMA"
  default_warehouse = snowflake_warehouse.warehouse.name
  default_role      = snowflake_role.role.name
  default_namespace = "${snowflake_database.db.name}.${snowflake_schema.schema.name}"
}

resource "snowflake_role_grants" "grants" {
  role_name = snowflake_role.role.name
  users     = [snowflake_user.user.name]
}

コードの中身自体は読めばそのままわかるものばかりだと思います。仮想ウェアハウスの自動サスペンド時間など、細かい設定が可能です。リソースによっては、ロールを使い分ける必要があるので、Multiple Providersで対応しているところがポイントでしょうか。

実行する

実行はTerraform側の話なので、普通にやります(Snowflakeだからといって特殊なことはない)。

> terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of chanzuckerberg/snowflake from the dependency lock file
- Using previously-installed chanzuckerberg/snowflake v0.22.0

Terraform has been successfully initialized!

...(略)
> terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # snowflake_database.db will be created
  + resource "snowflake_database" "db" {
      + data_retention_time_in_days = (known after apply)
      + id                          = (known after apply)
      + name                        = "TIGER_DB"
    }

  # snowflake_database_grant.grant will be created
  + resource "snowflake_database_grant" "grant" {
      + database_name     = "TIGER_DB"
      + id                = (known after apply)
      + privilege         = "USAGE"
      + roles             = [
          + "TIGER_ROLE",
        ]
      + with_grant_option = false
    }

 ...(中略)...

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

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

snowflake_role.role: Creating...
snowflake_database.db: Creating...
snowflake_warehouse.warehouse: Creating...
snowflake_role.role: Creation complete after 1s [id=TIGER_ROLE]
snowflake_warehouse.warehouse: Creation complete after 1s [id=TIGER_WAREHOUSE]

...(中略)...

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

確認する

Snowflake側を確認します。いや〜見事に作成できていますね。

補足など

テーブル

このProviderには、テーブルを作成するResourceもちゃんとあります(カラムも指定できる)。しかし、テーブルとなってくると、データそのものをどうするか?ということを考える必要が出てきます。それはすなわち「どういうデータ分析をするのか?」ということにつながってくるのですが、Terraformの段階で、そこも一緒に考えるのは、あまり得策ではないと考えたので、テーブル作成は省きました。テーブルやビューの作成や変換は別手段(dbtなど)でやったほうがいいと思います。

他にもやれそうなこと

その他のリソース

このProviderは、他にもStage、File Format、Pipe、Streamなどの、Snowflake独自のリソースにも対応しています。これらもTerraformで管理することで、より複雑なSnowflake環境を簡単に用意することができそうです。

変数を使う

今回のように「その人のための環境を用意する」という目的の場合、作成するリソース名に変数を使用し、実行時に名前を入れることで、汎用的に使用できるようにできそうです。

おわりに

Snowflakeも、運用規模が大きくなると、手動でリソースを作成するのが辛くなってきますし、ヒューマンエラーのリスクもあります。リソース設定のクエリを準備するのもアリですが、Terraformの方が、(クエリよりも)コードの可読性が高かったり、CI/CDに組み込めたりするので、気になった方は是非やってみてください。