dbt Projects on Snowflake でアカウントレベルで環境が分離されている場合の開発を試してみる
はじめに
Snowflake で環境を分ける際、アカウントレベルで分離することがあります。この場合の、dbt Projects on Snowflake での開発を試した内容を本記事でまとめてみます。
前提条件
ここでは以下の設定で環境が分離されているとします。
- 環境:アカウントレベルで分離
- 本番環境アカウント
- 開発環境アカウント
- 使用するデータベース構成
- 本番環境
- データベース:
prd_db - スキーマ:
raw:dbt から参照する加工前のデータを格納dbt_output:dbt で開発した内容の本番環境でのデプロイ先dbt_objects:スキーマレベルのオブジェクトである dbt project object のデプロイ先
- ロール:
prd_dbt_role - ウェアハウス:
prd_dbt_whs - GitHub Actions 経由でのデプロイ用ユーザー:
git_user
- データベース:
- 開発環境
- データベース:
dev_db - スキーマ:
raw:dbt から参照する加工前のデータを格納dbt_<user名>:開発者用のスキーマ
- ロール:
dev_dbt_role - ウェアハウス:
dev_dbt_whs
- データベース:
- 本番環境
各環境のrawスキーマには、同じ名称のテーブルが存在するとします。サンプルデータにはこちらのクイックスタートでも使用される jaffle shop のデータを使用します。
また、以降の手順は以下の記事を参考としており、特に各種オブジェクトの定義、権限付与、ワークフロー定義もこちらの記事をベースとさせていただいています。詳細な手順も記載があるので、あわせてご参照ください
事前準備
各アカウントに各種オブジェクトやサンプルデータを用意します。
開発環境アカウント
開発環境アカウントでの各種オブジェクトの作成と権限付与
USE ROLE ACCOUNTADMIN;
-- 変数の設定
set env_name = 'dev';
SET db_name = concat($env_name,'_db');
SET wh_name = concat($env_name,'_dbt_whs');
SET role_name = concat($env_name,'_dbt_role');
SET schema_name = concat($db_name,'.raw');
SET user_schema_name = concat($db_name,'.dbt_tyasuhara');
-- warehouses作成
CREATE WAREHOUSE IF NOT EXISTS identifier($wh_name)
WITH
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
-- 基本データベースとスキーマ作成
CREATE DATABASE identifier($db_name);
CREATE SCHEMA identifier($schema_name); -- rawデータ用スキーマ
CREATE SCHEMA identifier($user_schema_name); -- 開発者用スキーマ
-- テーブル作成とデータロード
USE SCHEMA identifier($schema_name);
CREATE TABLE customers (
id INTEGER,
first_name VARCHAR,
last_name VARCHAR
);
COPY INTO customers (id, first_name, last_name)
FROM 's3://dbt-tutorial-public/jaffle_shop_customers.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
CREATE TABLE orders (
id INTEGER,
user_id INTEGER,
order_date DATE,
status VARCHAR,
_etl_loaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COPY INTO orders (id, user_id, order_date, status)
FROM 's3://dbt-tutorial-public/jaffle_shop_orders.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
CREATE TABLE payment (
id INTEGER,
orderid INTEGER,
paymentmethod VARCHAR,
status VARCHAR,
amount INTEGER,
created DATE,
_batched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COPY INTO payment (id, orderid, paymentmethod, status, amount, created)
FROM 's3://dbt-tutorial-public/stripe_payments.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
-- 権限付与
-- ロール作成
USE ROLE SECURITYADMIN;
CREATE ROLE identifier($role_name);
GRANT ROLE identifier($role_name) TO ROLE SYSADMIN;
-- 権限付与:warehouse
GRANT USAGE,OPERATE ON WAREHOUSE identifier($wh_name) TO ROLE identifier($role_name);
-- 権限付与:rawデータへの読み取り
GRANT USAGE ON DATABASE identifier($db_name) TO ROLE identifier($role_name);
GRANT USAGE ON SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON ALL TABLES IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON FUTURE TABLES IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON ALL VIEWS IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON FUTURE VIEWS IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
-- 権限付与:開発者スキーマへの書き込み権限
GRANT USAGE ON SCHEMA identifier($user_schema_name) TO ROLE identifier($role_name);
GRANT CREATE TABLE ON SCHEMA identifier($user_schema_name) TO ROLE identifier($role_name);
GRANT CREATE VIEW ON SCHEMA identifier($user_schema_name) TO ROLE identifier($role_name);
本番環境アカウント
本番環境アカウントでの各種オブジェクトの作成と権限付与
USE ROLE ACCOUNTADMIN;
-- 変数の設定
set env_name = 'prd';
SET db_name = concat($env_name,'_db');
SET wh_name = concat($env_name,'_dbt_whs');
SET role_name = concat($env_name,'_dbt_role');
SET schema_name = concat($db_name,'.raw');
SET dbt_schema_name = concat($db_name,'.dbt_output');
-- warehouses作成
CREATE WAREHOUSE IF NOT EXISTS identifier($wh_name)
WITH
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
-- 基本データベースとスキーマ作成
CREATE DATABASE identifier($db_name);
CREATE SCHEMA identifier($schema_name); -- rawデータ用スキーマ
CREATE SCHEMA identifier($dbt_schema_name); -- dbt出力用スキーマ
-- テーブル作成とデータロード
USE SCHEMA identifier($schema_name);
CREATE TABLE customers (
id INTEGER,
first_name VARCHAR,
last_name VARCHAR
);
COPY INTO customers (id, first_name, last_name)
FROM 's3://dbt-tutorial-public/jaffle_shop_customers.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
CREATE TABLE orders (
id INTEGER,
user_id INTEGER,
order_date DATE,
status VARCHAR,
_etl_loaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COPY INTO orders (id, user_id, order_date, status)
FROM 's3://dbt-tutorial-public/jaffle_shop_orders.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
CREATE TABLE payment (
id INTEGER,
orderid INTEGER,
paymentmethod VARCHAR,
status VARCHAR,
amount INTEGER,
created DATE,
_batched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COPY INTO payment (id, orderid, paymentmethod, status, amount, created)
FROM 's3://dbt-tutorial-public/stripe_payments.csv'
FILE_FORMAT = (
TYPE = 'CSV',
FIELD_DELIMITER = ',',
SKIP_HEADER = 1
);
-- 権限付与
-- ロール作成
USE ROLE SECURITYADMIN;
CREATE ROLE identifier($role_name);
GRANT ROLE identifier($role_name) TO ROLE SYSADMIN;
-- 権限付与:warehouse
GRANT USAGE,OPERATE ON WAREHOUSE identifier($wh_name) TO ROLE identifier($role_name);
-- 権限付与:rawデータへの読み取り
GRANT USAGE ON DATABASE identifier($db_name) TO ROLE identifier($role_name);
GRANT USAGE ON SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON ALL TABLES IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON FUTURE TABLES IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON ALL VIEWS IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
GRANT SELECT ON FUTURE VIEWS IN SCHEMA identifier($schema_name) TO ROLE identifier($role_name);
-- 権限付与:dbt出力用スキーマへの書き込み権限
GRANT USAGE ON SCHEMA identifier($dbt_schema_name) TO ROLE identifier($role_name);
GRANT CREATE TABLE ON SCHEMA identifier($dbt_schema_name) TO ROLE identifier($role_name);
GRANT CREATE VIEW ON SCHEMA identifier($dbt_schema_name) TO ROLE identifier($role_name);
本番環境では、dbt オブジェクトのデプロイ先スキーマとこのスキーマに対するデプロイ権限を追加で付与します。
USE ROLE ACCOUNTADMIN;
CREATE SCHEMA prd_db.dbt_objects;
GRANT USAGE ON SCHEMA prd_db.dbt_objects TO ROLE prd_dbt_role;
GRANT CREATE STAGE ON SCHEMA prd_db.dbt_objects TO ROLE prd_dbt_role; -- Snowflake CLI経由のデプロイの場合、一時的なステージを作成するため
-- dbt project のデプロイ権限
GRANT CREATE DBT PROJECT ON SCHEMA prd_db.dbt_objects TO ROLE prd_dbt_role;
本番環境へのデプロイは GitHub Actions 経由で行うので、サービスユーザーを作成します。
-- dbtデプロイ用のサービスユーザーを作成
CREATE OR REPLACE USER git_user
DEFAULT_ROLE = prd_dbt_role
DEFAULT_SECONDARY_ROLES = ()
TYPE = SERVICE
;
GRANT ROLE prd_dbt_role TO USER git_user;
ALTER USER git_user SET RSA_PUBLIC_KEY = 'MII・・・';
-- ネットワークポリシーを作成
CREATE OR REPLACE NETWORK POLICY github_actions_network_policy ALLOWED_NETWORK_RULE_LIST = (
'SNOWFLAKE.NETWORK_SECURITY.GITHUBACTIONS_GLOBAL'
);
-- ユーザーレベルでポリシーを適用
ALTER USER git_user SET NETWORK_POLICY = github_actions_network_policy;
開発環境アカウントで dbt projects を作成
リモートリポジトリに GitHub を利用するため OAuth2 認証のための API 統合を作成します。
USE ROLE ACCOUNTADMIN;
-- OAuth2認証用 API Integrationの定義と権限付与
CREATE OR REPLACE API INTEGRATION oauth2_api_integration
API_PROVIDER = git_https_api
API_ALLOWED_PREFIXES = (
'https://github.com/<アカウント名>/'
)
API_USER_AUTHENTICATION = (TYPE = SNOWFLAKE_GITHUB_APP)
ENABLED = TRUE;
GRANT USAGE ON INTEGRATION oauth2_api_integration TO ROLE dbt_dev_role;
続けて、GitHub 側で README を追加した検証用のリポジトリを作成し、このリポジトリと連携する Workspace を作成し、作成した Workspace 上で dbt Projct を作成します。

dbt での各種設定・開発
本番環境用 profile の設定
profiles.ymlに開発環境用の内容は追加されているので、ここは特に変更せず、本番環境用のプロファイルを追加します。詳細は後述しますが、本番環境用の設定を同じファイルに追記した場合 GitHub Actions 経由でのデプロイ時にエラーとなったため、ここでは本番環境用のプロファイル格納先フォルダを追加し、同じprofiles.ymlの名称で別ファイルとして作成しました。
mutiaccount_test:
target: prd
outputs:
prd:
type: snowflake
role: PRD_DBT_ROLE
warehouse: PRD_DBT_WHS
database: PRD_DB
schema: DBT_OUTPUT
環境分離用の変数を追加
今回の設定では、環境ごとにデータベースの名称(<env>_db)が異なります。ここでは以下のようにdbt_project.ymlで変数を定義し、Source 定義ファイルから参照することとしました。
name: mutiaccount_test
version: 1.0.0
config-version: 2
profile: mutiaccount_test
model-paths:
- models
analysis-paths:
- analyses
test-paths:
- tests
seed-paths:
- seeds
macro-paths:
- macros
snapshot-paths:
- snapshots
models:
mutiaccount_test:
+materialized: view
vars:
# ターゲットごとに切り替えたい設定の定義
dev:
database_name: dev_db # 開発用データベース名
prd:
database_name: prd_db # 本番用データベース名
Source 定義
各環境で対応する名称のデータベース内で、同じ名称のスキーマとテーブルが存在する設定のため、以下の Source 定義としました。このくらいであれば、変数を定義するまでもないかもしれませんが、ここでは以下のようにプロジェクトで設定した変数を参照させるようにしています。
version: 2
sources:
- name: jaffle_shop
description: This is a replica of the Postgres database used by our app
database: "{{ var('prd')['database_name'] if target.name == 'prd' else var('dev')['database_name'] }}"
schema: raw
tables:
- name: customers
description: One record per customer.
- name: orders
description: One record per order. Includes cancelled and deleted orders.
各種モデルを開発し開発環境でテスト
各種モデルを追加し、開発環境(Workspace)で実行します。コンパイル後に表示される DAG は下図のようになります。

Source による参照を含むモデルのコンパイル後のコードを見ると、ターゲット変数に基づき、開発環境のデータベースを参照していることが確認できます。

Run すると上記のモデルが開発環境用のスキーマ(dev_db.dbt_<user名>)に作成されます。
GitHub Actions 経由でのデプロイ
開発環境でテストした内容を、GitHub Actions 経由で本番環境アカウントにデプロイします。
config.toml を追加
デプロイには Snowflake CLI を使用するので、config.tomlという構成ファイルを追加し Snowflake CLI の接続を構成します。ここではリポジトリのルート階層にconfigフォルダを追加しそこにconfig.tomlを作成しました。
default_connection_name = "workflow"
[connections.workflow]
リモートリポジトリでシークレットを追加
GitHub Actions から Snowflake CLI での認証に使用する各種情報をシークレットとして追加します。ここでは下図の3つのシークレットを追加しました。
注意点として、Snowflake アカウントはデプロイ先となる本番環境アカウントの情報です。

GitHub Actions のワークフローファイルを追加
リポジトリのルート階層に.github/workflowsフォルダを作成し、ワークフロー定義を追加します。
name: Deploy dbt Project to Snowflake
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy-to-snowflake:
runs-on: ubuntu-latest
env:
SNOWFLAKE_CONNECTIONS_WORKFLOW_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_CONNECTIONS_WORKFLOW_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_CONNECTIONS_WORKFLOW_PRIVATE_KEY_RAW: ${{ secrets.SNOWFLAKE_PRIVATEKEY }}
SNOWFLAKE_CONNECTIONS_WORKFLOW_WAREHOUSE: "prd_dbt_whs"
SNOWFLAKE_CONNECTIONS_WORKFLOW_ROLE: "prd_dbt_role"
SNOWFLAKE_CONNECTIONS_WORKFLOW_AUTHENTICATOR: "SNOWFLAKE_JWT"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dbt-snowflake
run: pip install "dbt-snowflake~=1.9.0"
- name: Install Snowflake CLI
uses: Snowflake-Labs/snowflake-cli-action@v1.5
with:
cli-version: "latest"
default-config-file-path: 'config/config.toml'
- name: Run dbt deps
run: dbt deps --project-dir mutiaccount_test
- name: Deploy to Snowflake
run: |
# プロジェクトの場所 (dbt_project.ymlがある場所)
DBT_PROJECT_DIR="mutiaccount_test"
# プロファイル設定ファイルがある場所
PROFILES_CONFIG_DIR="mutiaccount_test/deploy_profile"
snow dbt deploy DBT_DEPLOYMENT_PROD \
--force \
--source ${DBT_PROJECT_DIR} \
--profiles-dir ${PROFILES_CONFIG_DIR} \
--connection workflow \
--database PRD_DB \
--schema DBT_OBJECTS
# モデルの実行
snow dbt execute PRD_DB.DBT_OBJECTS.DBT_DEPLOYMENT_PROD run
この状態でワークフローを実行すると本番環境アカウントの指定のデータベーススキーマに dbt projects オブジェクトと各種モデルが作成されます。

Source 定義を参照するモデルのコンパイル後の SQL を見るとターゲット変数から本番環境のデータベースを参照するように制御されています。

profiles.yml の分離
「本番環境用 profile の設定」に記載したように、ここでは本番環境用のプロファイルを別ファイルとして作成しています。当初は同じ YAML ファイルに記載していたのですが、この場合、ワークフロー実行時に下図のエラーとなりました。本番環境アカウントでは開発環境用のロールは存在しないため、ここではファイルそのものをわけることで対応しました。

また、dbt projects オブジェクトのデプロイには snow dbt deploy を使用しますが、オプションの
--profiles-dirでprofiles.ymlを含むディレクトリへのパスを指定できます。
ファイルそのものを指定できない、またファイル名もprofiles.ymlである必要があるため、ここでは別のフォルダに同じファイル名(deploy_profile/profiles.yml)で本番環境用のプロファイルを作成しました。
モデルを追加
最後に、モデルを追加し、一連の開発を試してみます。
開発環境(Workspace)でモデル(customers_copy.sql)を追加し、テストします。

DAG は下図のようになります。

開発環境で確認できたら、リモートリポジトリに push し main ブランチに変更をマージします。

マージ後、ワークフローが起動します。

本番環境アカウントを確認すると、指定のデータベーススキーマにモデルが追加されます。

さいごに
Snowflake のアカウントレベルで環境が分離されていると想定し、dbt Projects on Snowflake での一連の開発を試してみました。profiles.ymlの設定など一部躓く所はありましたが、意図する操作を行えました。
こちらの内容が何かの参考になれば幸いです。







