SELECTSELECT

SELECT

Terraformで実現するSnowflake運用の効率化(2024)

By Gary JamesFeb 10, 202413 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

Terraformとは

Terraformは、Infrastructure as Code(IaC ― GUIをポチポチ操作する必要なし!)を実現するツールで、プロバイダー(通常はSnowflake、AWS、dbt Cloudなどサービスごとに用意されています)を介して各種サービスの構成を管理します。

プロバイダーはTerraformにとってPythonでいうパッケージのような存在で、特定の用途向けに作られた再利用可能なインターフェースがあらかじめまとめられたものです。この例えを続けるなら、Terraform RegistryはPyPIに相当すると言えるでしょう。

SnowflakeのインフラをTerraformで管理するメリット

SnowflakeインフラをTerraformで管理することを検討すべき、説得力のある理由が3つあります。

1. 構成をバージョン管理できる

Terraformを使えば、インフラ構成はGithubなどのバージョン管理リポジトリに保存されます。これにより、Githubでコードを管理する際の定番のメリット ― 簡単なロールバック、変更内容のプレビュー、長期的なコード履歴、そしてリリース前にチームメンバーから受けられる頼もしいレビュー ― をそのまま享受できます。

Githubの話を別にしても、Terraform自体がインフラリソースの状態を独自に保持しており、デプロイのたびに差分を提示してくれるため、意図しない変更を防ぐ役割も果たしてくれます。

2. スケーラビリティ

Terraformは構成の再利用を可能にし、Snowflakeのデプロイを容易にスケールさせられます。最も大きな視点で言えば、複数のSnowflakeアカウントを一箇所からまとめて管理できる点はTerraformの大きな強みです。さらに、ループモジュールを使い、共通のテンプレート構成でリソースをスケーラブルに作成・更新・削除できる機能は、広範な管理作業にかかる時間を大幅に短縮してくれます。

また、社内のコラボレーションやベストプラクティスのスケールにも役立ちます。リポジトリやチーム、さらには職種をまたいだ共通言語を提供してくれるので、昨今のデータチームには非常にありがたい存在です。組織全体で共通のツールを使えば、皆が同じプレイブックに沿って動けるため、個別のデプロイパイプラインを維持する手間も減らせます。

3. 依存関係の管理が楽になる

Terraformは定義したオブジェクトをもとにリソース間の依存関係グラフを構築するため、ストリームで使われているテーブルを消そうとした場合など、下流で必要とされているリソースを削除しようとすると警告を出してくれます。

Terraform(またはIaC全般)は自社に向いているか

どんなに雑な実装であっても、IaCツールは少なくとも一つのことを必ずやらせてくれます。それは、セットアップを書き残させることです。ドキュメントが嫌いな人はいませんよね?しかもそれがバージョン管理下にあれば、その構成がなぜ追加されたのか、いつ追加されたのかについて何らかの記録が残っている可能性が高く、いずれも有用なコンテキストになります。

一方で、IaCはその性質上、操作したいプラットフォームから一段抽象化された存在です。モジュールのような手法を多用するほど構成は実体から離れていき、他のメンバーにとって分かりにくくなったり、最悪の場合はスタックを脆くしてしまう可能性もあります。抽象化は慎重かつ戦略的に行うべきです。

このサービスの構成は本当に重要か。労力に見合う価値はあるか? 🍊

インフラのあらゆる側面には、設定ミスやダウンタイムといったリスクが付き物です。既存・新規を問わず構成をIaCに移行するには相応の投資が必要なので、踏み出す前にそのコンポーネントが自社にとってどれほど重要かを見極めましょう。

セットアップと保守に時間を投じられますか?Snowflakeはそれに値するほどビジネスの中核を担っていますか?Snowflakeの復旧時間は重要な要素ですか?既存のSnowflakeリソースをTerraformへ移行する必要があるかも考慮すべきでしょうか?

運用担当者はコードでの管理を好むか?

管理者によっては、変更のたびにコードやバージョン管理を経由したくない(あるいはそのスキルがない)場合もあります。ミスをしても許容範囲なら、無理にコード化して柔軟性を損なう必要はありません。

とはいえ、Terraform(や他のIaC)は開発チーム間の共通言語にもなり、組織に一貫性と再利用性(Terraform Modulesを参照)をもたらします。管理業務を民主化し、シニアメンバーに集中しがちな負荷を分散させる効果も期待できます。

プロバイダーは要件を満たしているか。そもそもプロバイダーは存在するか?

Terraformにすべてのサービス向けのプロバイダーが揃っているわけではありませんし、存在していても、新機能のリリースからプロバイダーへの反映までにタイムラグが生じることがあります。プロバイダーで作業を始める前、あるいは自作の道を選ぶ前に、利用可能な機能を確認しておく価値があります。

TerraformでSnowflakeを使う方法

ローカル開発でも、Codespacesでも、本番環境のセットアップでも、最初のステップはほぼ共通です。それでは、Terraformプロバイダー全般に通じる基本概念をいくつか紹介していきましょう。

Snowflake Terraformプロバイダー

Terraform Registryを眺めると、Snowflake関連のプロバイダーモジュールがいくつも見つかりますが、ここで取り上げるのは現在Snowflake自身がメンテナンスしている公式のSnowflake Terraform Registryです。

セットアップ

Snowflake Terraformプロジェクトを始めるには:

  • Terraformをインストールします ― ドキュメントを参照。例えばMac OS XのHomebrew経由なら以下のとおりです:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform -help
  • 新しいディレクトリ/リポジトリを作成します
mkdir snowflake-terraform-example
cd snowflake-terraform-example
  • [versions.tf](http://versions.tf)ファイルを作成します。ここには使用するTerraformプロバイダーのバージョンを記述します。今回の例では以下のような内容になります:
terraform {
  required_version = ">= 1.0.0"
  required_providers {
    snowflake = {
      source  = "snowflake-labs/snowflake"
      version = "0.84.1"
    }
  }
}
  • プロバイダーを設定するため、[providers.tf](http://providers.tf)ファイルを作成します。ここで使用するプロバイダーを構成します。動作確認時は認証情報をここに直接書いても構いませんが、リポジトリに保存する場合は環境変数を使う方法がベストです(シークレットを外部に晒さないために!)。例えば以下のようになります:
provider "snowflake" {
  role = "SYSADMIN"
}

Github ActionsでSnowflake向けTerraformをデプロイする方法

認証情報のセットアップ

Github Actions上でTerraform経由でSnowflakeを操作するには、Snowflakeへログインするための認証情報が必要です。ユーザー名/パスワード、秘密鍵、OAuthなど選択肢はいくつかありますが、ここではユーザー名/パスワードを使います。その他の認証方法についてはプロバイダーのドキュメントを参照してください。

認証情報をリポジトリレベルのGithub Secretsに保存するには、次の手順を踏みます:Your Repository > Settings > Secrets and variables > Actions > New repository secret

以下の値を設定します:

  • SNOWFLAKE_ACCOUNT - リージョン/プロバイダー情報を含むアカウント識別子。例えばselect_dev-production、レガシーアカウントならselect123.eu-west-1.aws
  • SNOWFLAKE_USER - ログインユーザー名
  • SNOWFLAKE_PASSWORD - ログインパスワード

Snowflake terraform github actions secrets

CIで実行したいチェック

Terraformを含むリポジトリでインフラ変更を行う際、提案された変更をレビューするためにCIへ追加しておくと便利なステップがいくつかあります:

  • terraform fmt -check - Terraform構成のフォーマットをチェックします。Terraformの魅力の一つは可読性なので、これは欠かせないステップです。-checkを付けるとフォーマットが正しくない場合に失敗します。付けない場合はフォーマットを自動修正してくれます(CIよりも開発中に行うのが望ましいです)
  • terraform validate - 基本的な検証を実行します。リモートサービスは呼び出さず、属性名などをチェックします
  • terraform plan - 開発中にも役立ちますが、最新のTerraform構成とサービス側のリモートステートを比較し、どのような変更が適用されるかを示してくれます(実際の適用は行いません)。CIで非常に有用なステップで、何が変更されるかが明確に分かるため、承認前にレビューすべきです

これらをまとめると、Github ActionsのCIパイプラインの例は次のようになります:

name: Terraform Snowflake CI

on:
  pull_request:
    branches:
      - main

env:
  SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
  SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
  SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}

jobs:
  RunChecks:
    name: Run Checks

コードを展開

上記を.github/workflows/ci.ymlなどのファイルにコピー&ペーストすれば、次回プルリクエストを作成した際にブランチに対してチェックが実行されます。Github Actions上では以下のように表示されます:

Snowflake terraform github actions workflow

Github Actionsにおけるプルリクエスト時のCIパイプラインの例

(オプション)環境の完全なレプリカを複数用意できるなら、terraform applyステップをCIに追加することも可能です。利点は、Terraformプロバイダーではなく実際の変更適用時にSnowflake側で発生するエラーをキャッチできること、Snowflake上で手動検証ができること、そして開発フローを一般的なエンジニアリングのフローに近づけられることです。

欠点は、CI環境とデプロイ環境を分離するために別のSnowflakeアカウント、あるいは少なくとも別の認証情報セットが必要になりやすい点ですが、これは本記事の範囲を超えます。

とはいえ、念のためci.ymlに追加する場合は、最後に以下のようなブロックを追記するだけです:

      - name: Terraform apply
        id: terraform-apply
        run: terraform apply -auto-approve

変更のデプロイ

ここまで問題がなく、変更内容にも全員が納得していると仮定すれば、デプロイパイプライン経由でこれらを適用できます。

  • terraform apply -auto-approve - インフラに変更を適用します。Actions経由で実行する際、-auto-approveを付けることで、コマンドライン実行時に出るはずのプロンプトを介さずにTerraformが処理を続行できます。

非常にシンプルなデプロイステップです。Github Actionsでは.github/workflows/deployment.ymlのような場所に以下のように記述します:

name: Terraform Snowflake Deployment

on:
  push:
    branches:
      - main

env:
  SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
  SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
  SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}

jobs:
  RunChecks:
    name: Run Checks

コードを展開

Github Actions向けのTerraform Stateの保存

インフラの状態を記憶しておくため、Terraformは現在の(あるいは本来そうあるべき)全リソースとその構成を記載したステートファイルを作成します。

さて、プルリクエストやGithub経由で変更を行うとき、Github Actionは現在のリソース状態にアクセスする必要があります。これは前回のterraform applyステップで適用された結果のインフラ状態です。では、前回のアクションで生成された状態にアクセスできるようにするには、どうすればよいのでしょうか?

そこで登場するのがTerraform Backendsです(Terraform Cloudを使っていない方向け)。これを使うと、ステートファイルをローカルストレージではなく、より永続的な場所に保存するよう構成できます。

セットアップを始めるには、Terraformプロジェクトにバックエンドブロックを追加する必要があります。例えば[backend.tf](http://backend.tf)ファイルです。以下はAmazon S3バックエンド向けのブロックの例です(現時点ではDynamoDBの設定はなし)。

terraform {
  backend "s3" {
    bucket = "terraform"
    key    = "state/snowflake.tfstate"
    region = "eu-west-1"
  }
}

ここまでは簡単ですが、次にやや厄介な部分 ― これをデプロイパイプラインへ組み込む工程が出てきます。ありがたいことに、AWSへの認証さえ通れば、リモートステートの更新はTerraformが面倒を見てくれます!

そのためには、AWSアカウント用のGithub Secretsを次の3つ追加します:

  • AWS_ACCESS_KEY_ID - AWSアカウントのアクセスキー識別子
  • AWS_SECRET_ACCESS_KEY - アクセスキーシークレット
  • AWS_REGION - AWSリージョン(例:eu-west-1)

そして、Terraformコマンドを実行する前に以下のステップ(または同等の認証処理)を含むようci.yml/deployment.ymlを更新します。

- name: Authenticate against production account
  uses: "aws-actions/configure-aws-credentials@v2"
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ${{ secrets.AWS_REGION }}

すべてをまとめると、Terraform Snowflake Github Actionsワークフローの大まかなパイプラインは以下のような形になります:

Snowflake Terraform Workflow in Github Actions and Amazon S3

Snowflake Terraformの実例

どこから始めればよいか迷っている方のために、Snowflakeの基本的な要素を扱う簡単な例をいくつか紹介します。まずはウェアハウスを作成しましょう(もちろんX-smallで 😉)。

resource "snowflake_warehouse" "reporting_warehouse" {
  name           = "reporting_wh"
  comment        = "Warehouse for reporting and BI tools"
  warehouse_size = "x-small"
}

これでSnowflakeのデータをクエリする手段は手に入りましたが、データを格納する場所がまだないので、定番のデータベースを追加しましょう。

resource "snowflake_database" "reporting" {
  comment                     = "Reporting database"
  data_retention_time_in_days = 30
  name                        = "REPORTING"
}

いい感じです。最後にSnowflakeのロールを作成し、レポート用のユーザーに付与しておきましょう。

1

2resource "snowflake_role" "reporter" {

3  name    = "REPORTER"

4  comment = "This role is limited to read only querying on the reporting database"

5}

6

7resource "snowflake_role_grants" "reporter" {

8  role_name = snowflake_role.reporter.name

9  roles = [\
\
10  ]

11  users = [\
\
12    snowflake_user.reporting_user.name\
\
13  ]

14}

この最後の例では、Terraformがオブジェクト間の参照をどう表現するかが分かります。snowflake_role_grantssnowflake_rolesnowflake_userリソースに依存しており、これがTerraformがSQLの実行順序を決定する仕組みです。

さらなる例

本記事の動作する実例は、以下のGithubリポジトリで確認できます。フォークしてCodespaceを作成するのが手早く始められる方法です!

GtheSheep/terraform-snowflake-example

SnowflakeでのTerraformの実践的なセットアップ手順については、Snowflake公式の入門ガイド Terraforming Snowflake もぜひご覧ください。

SnowflakeとTerraformを組み合わせる際の課題

どんなツールにも落とし穴はあり、Terraformも例外ではありません。Snowflake特有の挙動や使い方がそれを助長することもあるので、いくつか挙げてみましょう。

リソースの完全なカバレッジ

Snowflake Terraformプロバイダーを支えている素晴らしいチームはありますが、Snowflakeの最新機能に追随するまで多少時間がかかることがあります。あるいは、設計上の判断としてあえてTerraform経由での操作を許可していないケースもあります(これは比較的稀ですが)。いずれの場合も、Snowflake側で直接管理する作業が残ることを意味します。

加えて、一部のリソースはSnowflakeが提供する細かな機能までは完全に網羅していないことがあり、リソースをデプロイした後にSnowflakeで「追加の」SQLコマンドを実行する必要が生じる場合もあります。

例えば、GRANT SELECT ON ALL TABLES IN SCHEMA <schema> TO ROLE <role>に相当する機能は長らくプロバイダーで直接利用できず、スキーマ内のオブジェクトを反復処理する方法を工夫するか、別途SQLを実行する必要がありました。幸い現在はon_allパラメータが用意されています!

所有権とライフサイクル

どのSnowflakeリソースをTerraformで管理するかを決める際は、それぞれのライフサイクルと所有権を考慮してください。

ライフサイクルとは、テストやPOCのためにサッと立ち上げてすぐ撤収するだけのものなのか、それが手間に見合うのか、という話でもあります。しかしそれだけにとどまらず、一時テーブルや、Terraformのデプロイとは無関係に頻繁に削除・再作成されるテーブル(dbt、君のことだよ 👀)についても考えてみてください。こうした活動はSnowflakeとTerraformのステートを大きく乖離させ、デプロイを面倒な状況に陥らせかねません 🥒。

所有権については、これらのオブジェクトを真に所有しているのはどのプロセスかを明確にしてください。そのプロセスが動作する環境を囲い込むためにTerraformを使う、という発想もあります(dbtがスキーマを管理する土台のデータベースをTerraformで用意する、など 🤷‍♂️)。

例として、生データのロードツールAirbyteと、変換ツールdbtがあるとしましょう:

  • Airbyteは一般的にターゲットスキーマを指定してデータをロードし、その後はそのSnowflakeスキーマ内のテーブルを必要に応じてカラムやテーブルを追加しつつ所有・管理させます(ソース側で利用可能なすべてのリソースを対象とすると仮定します)。この場合、テーブルをTerraformで管理してもAirbyteの活動ですぐに古くなってしまうため避けるべきですが、スキーマ自体の作成とアクセス制御はAirbyteから見ると不変なので、Terraformで管理できます。
  • dbtは次にデータを複数のSnowflakeスキーマへ変換し、それぞれを作成してテーブルを構築します。ここではdbtがスキーマを完全に所有し、アクセス制御まで処理してくれるので、スキーマをTerraformで管理しても意味がありません。一方、dbtが活動するエリアであるデータベースはTerraformでプロビジョニングできます。

これら2つのプロセスはTerraformパイプラインとは完全に独立して動くため、ステートはこれらのプロセスによる変更を把握できず、terraform planで大量のノイズが発生したり、最悪の場合はAirbyteやdbtの変更を上書きしてしまったりする恐れがあります!

Snowflake object ownership in a basic ELT scenario and where Terraform may be applicable

複数リポジトリの管理

複数の場所からリソースが必要とされたり操作されたりするようになると、状況は少し厄介になります。

先に述べたとおり、Snowflakeリソースの所有権をどこに置くかを明確に定義しておくことが重要です。定義が衝突すると、別々のデプロイメントが互いの構成を上書きしてしまう可能性があります。例えば、2つのリポジトリが同じデータベースのUSAGEを異なるロールに付与する場合などです(最近ではenable_multiple_grantsパラメータによって、ある程度緩和されています)。

依存関係はTerraformモジュールやデータソースからの読み込みなどで管理できますが、こうした柔軟性が制御不能に広がるのを防ぐためにも、リソース管理の構造についてしっかりとした計画を立てておきましょう。例えば、中央リポジトリでSnowflakeのロール/ユーザー/グラント/ウェアハウスなどアカウントの中核要素を一括管理し、それ以外のチームには必要に応じて独自にデータベース/タスク/プロシージャをプロビジョニングしてもらう、といった構成です。

Gary James・Senior Analytics Engineer at Beauty Pie

Garyはメイクアップ&スキンケアブランドBeauty Pieのシニアアナリティクスエンジニアです。「データ」と名の付くあらゆる肩書きを10年以上にわたって経験してきた彼は、データ/エンジニアリング関連のすべてをこよなく愛しています。Garyはデータスタック全般にわたるさまざまなオープンソースプロジェクトに精力的に貢献しており、dbt cloud Terraformプロバイダーの作者でもあります。さらにdbt、elementary data、lightdash、Meltanoなどエコシステム内の他のプロジェクトにも貢献しています。