🏗️

Snowflake リソースの Terragrunt 管理方法を改めた話

はじめに

こんにちは!シンプルフォームの山岸です。

弊社は2024年6月に Snowflake 導入を決定し、データ基盤を従来利用してきた Amazon Redshift から移行しました。検証着手当時、トライアルアカウントを開設して最初に取り組んだことは、IaC 管理方法を設計・整備することです。弊社では AWS 等のクラウドリソースの IaC 管理に Terraform + Terragrunt を利用していたので、Snowflake リソースでも基本的な設計を踏襲しました。その時の検討内容について、以下の記事でご紹介しています。

それからしばらく運用してみて「ここはこうした方が良かったな」と思う箇所が色々と出てきたので、あるべき Terragrunt ディレクトリ構造について改めて考えてみました。設計方針について大枠が固まったので、本記事ではその内容の一部をご紹介できればと思います。

Terragrunt を使用するメリット・注意点

本題に入る前に、Terragrunt を使用するメリット・注意点について少し触れたいと思います。
Terragrunt 自体の説明は以下でも扱っているので、詳細については割愛します。

Snowflake リソースの IaC 管理において、今では CREATE OR ALTER のように SQL で完結できるような機能も出ていますが、やはり Terraform を利用するのが最もスタンダードな方法ではないかと思います。

では Terraform に加えて、Terragrunt を利用するメリットは何でしょうか。色々あるとは思いますが、個人的に特に大きいと考えているのは以下の点です。

  • Terragrunt 独自関数である find_in_parent_folders() により、(アカウントごとの設定情報のような)共通コンテキストの重複記述を避けることができる。これにより tfstate (≒ Working Directory) を分割しやすくなる。
  • tfstate を分割することで管理対象リソースが少なくなるため、plan/apply 時に不要なリソース参照を削減でき、実行時間も短くなる。また、複数メンバーが開発するような場合に競合が起きづらくなる。
  • 各 tfstate が管理対象とするリソースの数・種類が少なくなるので、変更の影響を限定的にでき、Provider Version を上げるなどの対応がしやすい。

逆に、注意すべきポイントとしては以下のようなものがあると思います。

  • tfstate を細かく分割できてしまう分、どのリソースをどの tfstate で管理しているのかを把握しやすいよう、事前の入念な設計が必要になる。
  • tfstate がいくらでも増えてしまう想定で CI/CD 基盤を整備する必要がある。

Terragrunt ディレクトリ構造の変更

本題である Terragrunt ディレクトリ構造の変更内容について以下に説明します。

全体構造(変更なし)

まず、アカウントごとディレクトリまでの全体的な構造については従来から変えておらず、冒頭にご紹介したこちらの記事の通り以下のようにしています。

  • /terraform/snowflake 直下に terragrunt.hcl (親) を配置する
  • Terraform モジュールは modules/ に作成する
  • Terraform モジュールを呼び出す terragrunt.hcl (子) は envs/ に作成する

アカウント内構造 - Before

今回変更したのはアカウントごとディレクトリの中の構造です。

変更前は以下のようにしていました。端的にまとめると、Account-Level オブジェクトも Schema-Level オブジェクトも関係なく、リソースタイプ毎に平たく並べていました。

.
├── account_vars.yaml
├── database/
│   └── {database_name}/ terragrunt.hcl
├── schema/
│   └── {database_name}
│       └── {schema_name}/ terragrunt.hcl
├── view/
│   └── {database_name}/
│       └── {schema_name}/
│           └── {use}/ terragrunt.hcl
├── function/
│   └── {database_name}
│       └── {schema_name}
│           └── {use}/ terragrunt.hcl
└── warehouse/
    └── {use}/ terragrunt.hcl

アカウント内構造 - After

変更後のディレクトリ構造は以下の通りです。Securable Objects Hierarchy [1] を強く意識した構造にしました。すなわち、構築対象リソースの Object-Level (Account-Level, Database-Level, Schema-Level) によって、terragrunt.hcl (子) の所在を振り分けるようにしました。

.
├── account_vars.yaml
├── account_objects
│   ├── namespace_vars.yaml
│   ├── account_role
│   ├── database
│   │   └── {database_name}
│   ├── user
│   └── warehouse
├── database_objects
│   └── {database_name}
│       ├── namespace_vars.yaml
│       ├── database_role
│       └── schema
│           └── {schema_name}
└── schema_objects
    └── {database_name}
        └── {schema_name}
            ├── namespace_vars.yaml
            ├── function
            └── view

namespace_vars.yaml

上記ツリーにある通り、namespace_vars.yaml という特殊な設定ファイルを各所に配置しています。配置場所ごとの内容は以下の通りです。(詳細は後述)

account_objects/namespace_vars.yaml
object_level: account_objects
database_objects/{database_name}/namespace_vars.yaml
object_level: database_objects
database_name: {database_name}
schema_objects/{database_name}/{schema_name}/namespace_vars.yaml
object_level: schema_objects
database_name: {database_name}
schema_name: {schema_name}

terragrunt.hcl (子)

Snowflake UDF を例にとると、terragrunt.hcl (子) の実装は例えば以下のようになります。
ポイントは、UDF が帰属するデータベース・スキーマの情報を namespace_vars.yaml から取得している点です。

schema_objects/{database_name}/{schema_name}/function/sql/default/terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  common_vars    = yamldecode(file(find_in_parent_folders("common_vars.yaml")))
  account_vars   = yamldecode(file(find_in_parent_folders("account_vars.yaml")))
  namespace_vars = yamldecode(file(find_in_parent_folders("namespace_vars.yaml")))

  account = local.account_vars.account

  object_level  = local.namespace_vars.object_level
  database_name = local.namespace_vars.database_name
  schema_name   = local.namespace_vars.schema_name

  language = "sql"
  use      = "default"

  module_path = "function/${local.language}//${local.use}"
}

terraform {
  source = "${dirname(find_in_parent_folders())}/modules/${local.account.type}/${local.object_level}/${local.module_path}/"
}

inputs = {
  database_name = local.database_name
  schema_name   = local.schema_name
}

変更意図

Object-Level ディレクトリ

検証着手当時、Object-Level にあたるディレクトリを設けなかった理由としては以下のようなものがありました。

  • ディレクトリが一階層増えてしまう
  • 開発時に作成対象リソースがどの Object-Level なのかを意識しなければならず、却って認知不可を高めるのではないかという懸念がある

しかし、このうち後者に関しては見立てが誤っていたかなと思っています。作成対象リソースの Object-Level を意識しないことは通常ないですし、寧ろ Object-Level に関係なく平たく並んでいると混乱すると感じたので、中間に Object-Level ディレクトリを設けることにしました。

名前空間コンテキストの共有

前述の通り、Terragrunt を利用するメリットの一つとして「共通コンテキストの重複記述を避けることができる」点を挙げました。では Snowflake リソースの IaC において重要なコンテキストは何かと言えば、データベース・スキーマのような名前空間だと思います。

名前空間の情報を namespace_vars.yaml に定義しておき、これを各 terragrunt.hcl (子) ファイルから、find_in_parent_folders() 関数を使用して読み取ることで、データベース名・スキーマ名をハードコードしなくても正確な値を動的に取得できます。

terragrunt.hcl は他の場所のものをコピー&ペーストした上で編集、ということをよくやると思いますが、名前空間の情報を動的に取得するようにしておくことで、修正工数を省いたり、意図しないデータベース・スキーマにリソースを作成してしまう、といった事故を防げます。

所感

上記変更に伴っての所感ですが、ディレクトリ階層が一段深くなってしまったものの、以下のようなメリットを享受できていると思います。

  • 開発のしやすさ ... namespace_vars.yaml の内容さえ正しければ、特に何もしなくても正確な名前空間の情報を取得できるので、開発の効率性・安全性が向上しました。
  • コロケーション ... 同一スキーマ内オブジェクトの terragrunt.hcl ファイルは近くに配置されるので、開発中のディレクトリ階層移動の回数が減りました。
  • 認知負荷 ... Schema-Level オブジェクトのディレクトリ階層が、Snowsight 上の Data メニューで確認できる階層構造と近いものになるので、直感的に理解しやすくなりました。

落穂拾い

例外ケースの扱い

ここまででディレクトリ構造の設計方針について説明してきましたが、上記はあくまで原則であって、例外も認めることにしています。

例えば、Schame を作成したら多くの場合、その中に含まれる各種スキーマオブジェクトに対する Database Role も一緒に作成すると思います。原則に則れば schema/, database_role/ それぞれでリソースを定義することになりますが、手間が増えるだけなので Schema モジュールの中でデフォルトの Database Role 群も一緒に作れるようにしています。

このように、適宜例外を認めるようにしておいた方が開発効率上のメリットは大きいと思いますが、一方で「どのリソースをどこで作るのか」のルールはしっかり決めた上で遵守しないと、どこでリソース定義されているのか分からないといった事態に陥るので注意が必要そうです。

Terraform 操作における Key-Pair 認証対応

本題とは関係ありませんが、以前の記事では Snowflake への認証をパスワード認証で行う方法でご紹介してしまいました。今後の仕様変更でパスワード単体での認証は出来なくなる予定なので、公式ドキュメントを参考に Terraform 操作時も Key-Pair 認証を利用するようにしましょう。

さいごに

Snowflake × Terraform についてはたくさんの良記事が見つかりますが、Snowflake × Terragrunt の組合せは Web 上の情報がまだまだ少なく、筆者も試行錯誤しながらより良い方法を探っている状況です。本記事が Terragrunt を使っている・気になっているという方の参考になれば幸いであると同時に、「ここはこうするのが良いよ」といったものがあれば教えて頂けると喜びます。

最後まで読んで頂き、ありがとうございました。

脚注
  1. セキュリティ保護可能なオブジェクト - Snowflake Documentation ↩︎

Snowflake Data Heroes

Discussion