3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SnowflakeAdvent Calendar 2024

Day 6

構築後にSnowflake環境をTerraform化してハマったところ v0.99

Posted at

この投稿はSnowflake Advent Calendar 2024の6日目です。

から早2ヶ月。(切り替えからは3ヶ月)

上記の通り切り替えて3ヶ月が経つので、「活用が捗った」的なことを書けると良かったのですが、イベントとしては「問題なく使えている」「無事にTerraform化した」ので後者を取り上げます。

前置き

  • 構築時は試行錯誤があるので、SnowSQLで設定していく
  • SnowSQL自体はフォルダ単位で残していき、terraform化するときの参考にする
  • 構築後にterraform importを使用するなどしてIaC管理する
  • snowflake provider versionは0.99.0を使用(執筆時最新版)

SnowSQLのみで行った内容は記載されているものの、リソースが混在していることもあり、自分以外の他のメンバーがメンテナンスしにくい状況でした。

ちなみに今回のterraform化は1ヶ月弱かかりました。(一部他の作業をやりつつ)

Terraform で Snowflake の何を管理するべきか

Terraform で Snowflake の何を管理するべきか などの記事を見て、最終的には以下の形になりました。

以下のリソース以外のすべて

  • SSOの設定、Slackの設定
  • ステージ
  • テーブル(外部テーブル、view含む)

Terraform化する上でハマったところ

基本はSnowSQLにメモしておいたものをベースに、Github Copilotに食わせて、うまく行けば取り込むし、だめだったらドキュメントを参照しながら粛々とやっていく感じでした。

  • user,schemaあたりはどうしても一度applyしないと差分がなくなりませんでした
  • procedure も一度delete & createが必要になりました

気になったのは上記程度で、特に致命的なことは無かったです。

roleまわりがややこしい

唯一ハマったのはroleまわりです。

上記3つの観点が複雑に折り重なって、見る情報を精査しないと良くわからなくなります。

最終的に、readonlyはこのような形になりました。

  • snowflake_role.tf & snowflake_database_role.tf
    resource "snowflake_grant_account_role" "readonly_role" {
      for_each = {
        redash   = snowflake_user.redash.name
        readonly = snowflake_user.readonly.name
      }
      role_name = snowflake_account_role.readonly_role.name
      user_name = each.value
    }
    
    resource "snowflake_grant_database_role" "readonly_role" {
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      parent_role_name   = snowflake_account_role.readonly_role.name
    }
    
    resource "snowflake_grant_privileges_to_account_role" "readonly_role" {
      for_each = {
        compute_wh = snowflake_warehouse.compute_wh.name
        speed      = snowflake_warehouse.speed.name
      }
      privileges        = ["USAGE"]
      account_role_name = snowflake_account_role.readonly_role.name
      on_account_object {
        object_type = "WAREHOUSE"
        object_name = each.value
      }
    }
    
    resource "snowflake_database_role" "readonly_role" {
      database = snowflake_database.gms_datalake.name
      name     = "READONLY_ROLE"
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_database" {
      privileges         = ["USAGE"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_database        = snowflake_database_role.readonly_role.database
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema" {
      for_each = {
        default   = snowflake_schema.default.fully_qualified_name
        stats     = snowflake_schema.stats.fully_qualified_name
        data_mart = snowflake_schema.data_mart.fully_qualified_name
        jpn       = snowflake_schema.jpn.fully_qualified_name
        # ...省略
      }
      privileges         = ["USAGE"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema {
        schema_name = each.value
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_tables" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        all {
          object_type_plural = "TABLES"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_views" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        all {
          object_type_plural = "VIEWS"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_tables_future" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        future {
          object_type_plural = "TABLES"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_views_future" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        future {
          object_type_plural = "VIEWS"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    

ちなみに弊社の場合は、共通で使うschemaとは別に、各国のサブシステムのデータがschema毎にあり、各国のユーザーに合わせたreadonly role & userを使うことで、他国の指すシステムのデータは見られないようにしています。そのため、roleの定義は結構長くなっています…

構築時はDatabase Roleを使っていなかったため、terraform化の時点で、Database Roleを作成し、最初に作ったRoleは動作確認しつつ削除しました。

たしかにDatabase Roleにまとめると、SHOW GRANTS TO ROLE ;がシンプルで良いです。

今後の課題

テーブル等々の定義をどこで管理するか

流石に素の状態でterraform化するのは、AthenaのときにGlueの定義をterraform化していたときのように大変な気がするので、OpenMetadataなどを検討したいです。

Task定義が可読性が低い

毎時、毎日の処理をSnowflakeの標準taskで回しています。
task数は20強あり、SnowflakeでのGUI上ではタスクグラフが確認できるのですが、tfからは確認ができず、可読性が低い状態です。

AWS Toolkit for Visual Studioのstep function表示 参考 のように可視化ができるといいなと思っています。
ルール的にはシンプルなので、拡張機能を作れないことは無いと思うのですがそこまでの余力がない…

終わりに

課題はありつつもとりあえずこれで構築 & 切り替えが完了したので、今月からは活用していくフェーズに入ってきます!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?