❄️

dbt入門(第7弾 後編): Snowflake × GitHub ActionsでCI/CD完全自動化 - 実践・運用編

に公開

🚀 はじめに

この記事は dbt入門(第7弾 後編 / CI/CD実践・運用編) です。

前編[1]では、3つのdbt運用アプローチの比較GitHub/GitHub Actionsの基礎知識Snowflake鍵ペア認証(JWT)の実装PR検証(CI)の実装(Slim CI) を解説しました。

後編では、本番デプロイ(CD)の実装失敗時のリカバリー・ロールバック運用Tips を扱います。
前編と同様に、GitHub Actions未経験でも追えるように「概念 → 手順 → 実装例」の順で進めます。

前編
第7弾 前編:CI/CD基礎・準備編
https://zenn.dev/yujmatsu/articles/20260104_sf_dbt_cicd_part1

後編でわかること

  • 本番デプロイ(CD)の実装(mainマージ時に自動デプロイ)
  • 承認ゲートの設定(GitHub Environmentsで本番デプロイ前に承認)
  • 失敗時のリカバリー・ロールバック(Snowflake Time Travel活用)
  • 実装コードまとめ(CDワークフロー、メタデータ記録マクロなど)
  • ハマりどころ7つ(実務で詰まりやすいポイントと対処法)
  • 運用Tips(コスト最適化、セキュリティ、CI高速化、docs自動公開)

🚢 本番デプロイ(CD)の実装

mainブランチへマージされたら、本番環境へ自動デプロイします。

図1: CD全体フロー
図1: mainマージ→GitHub Actions CD起動→承認ゲート(任意)→Snowflake本番へデプロイ→通知の流れ。

CDの目的

  • 手動デプロイの排除:mainマージ後、自動で本番環境へ反映
  • デプロイの一貫性:毎回同じ手順で実行され、ヒューマンエラーを減らす
  • 監査証跡:いつ、誰が、どのコミットを反映したかが残る
  • リリース高速化:PRマージから反映までを短縮(運用上の待ちを減らす)

Slim CIを成立させるための「初回デプロイ」

前編で紹介したSlim CIの --defer は、未変更モデルを 既存の参照先(多くは本番) に向けます。
そのため、最初に一度は本番(参照先)にモデルが存在する状態を作る必要があります。

  • 初回だけは手動でもOK(例:ローカルで dbt build --target prod
  • 以降は、このセクションのCDで mainマージをトリガーに自動反映できます

補足
初回から完全自動にしたい場合は、後述のCDワークフローを workflow_dispatch(手動実行)にも対応させると便利です。


ワークフロー設計(トリガー: push to main)

.github/workflows/dbt_cd.yml を作成します。
前編のCIと同じく、profiles.yml.templateをコピーして使う構成にしておくと管理がラクです。

ポイント

  • 承認ゲートを使う場合は、environment: production を指定します(後述)
  • 連続マージが起きてもデプロイが競合しないよう、concurrency を入れるのが安全です
cat > .github/workflows/dbt_cd.yml << 'EOF'
name: dbt CD (Production Deployment)

on:
  push:
    branches: [main]
    paths:
      - "models/**"
      - "tests/**"
      - "macros/**"
      - "seeds/**"
      - "snapshots/**"
      - "dbt_project.yml"
      - "packages.yml"
      - "profiles.yml.template"
      - ".github/workflows/**"

permissions:
  contents: read

concurrency:
  group: dbt-cd-production
  cancel-in-progress: false  # 本番はキャンセルせず順番に実行する想定

jobs:
  dbt-cd:
    runs-on: ubuntu-latest
    environment: production  # 承認ゲート(任意): 不要ならこの行を削除

    env:
      DBT_PROFILES_DIR: .

      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
      SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
      SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
      SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dbt (pin recommended)
        run: |
          pip install --upgrade pip
          pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"

      - name: Create profiles.yml from template
        run: cp profiles.yml.template profiles.yml

      - name: Setup Snowflake private key
        run: |
          printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
          chmod 600 rsa_key.p8

      - name: Install dbt packages
        run: dbt deps

      - name: Pre-deployment check (compile)
        run: dbt compile --target prod

      # ※「デプロイ前テスト」は運用方針により追加(本文で解説)
      # - name: Pre-deployment smoke tests (optional)
      #   run: dbt test --target prod --select tag:smoke

      - name: Deploy (dbt build)
        id: dbt_build
        run: |
          # フルビルド(全モデル・全テストを実行)
          dbt build --target prod

      - name: Post-deployment verification
        if: success()
        run: |
          echo "✅ Deployment completed successfully at $(date)"
          echo "📦 Commit: $GITHUB_SHA"
          echo "👤 Actor: $GITHUB_ACTOR"
          echo "🎯 Target: prod"
          echo "🗄️  Database: $SNOWFLAKE_DATABASE"

      - name: Deployment failed
        if: failure()
        run: |
          echo "❌ Deployment failed at $(date)"
          echo "📦 Commit: $GITHUB_SHA"
          echo "👤 Actor: $GITHUB_ACTOR"
          exit 1

      - name: Clean up private key
        if: always()
        run: rm -f rsa_key.p8 profiles.yml

      # Slack通知(任意): 使う場合は Secrets に SLACK_WEBHOOK_URL を追加
      - name: Notify Slack (success)
        if: success()
        uses: slackapi/slack-github-action@v1.27.0
        with:
          payload: |
            {
              "text": "✅ dbt Production Deployment Successful",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*dbt Production Deployment Successful* ✅\n\n- Commit: `${{ github.sha }}`\n- Actor : `${{ github.actor }}`\n- Run   : https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

      - name: Notify Slack (failure)
        if: failure()
        uses: slackapi/slack-github-action@v1.27.0
        with:
          payload: |
            {
              "text": "❌ dbt Production Deployment Failed",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*dbt Production Deployment Failed* ❌\n\n- Commit: `${{ github.sha }}`\n- Actor : `${{ github.actor }}`\n- Run   : https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\nログを確認し、必要ならロールバックを検討してください。"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
EOF

ワークフローの解説

1) トリガー設定(push to main)

on:
  push:
    branches: [main]
  • mainへのpush(≒PRマージ) でCDが動きます
  • paths: を入れることで、READMEだけの更新などで不要なデプロイが走るのを防げます

2) 承認ゲート(environment: production)

environment: production

この行を追加すると、本番デプロイ前に人間の承認が必要になります(後述)。
学習用で不要なら削除してOKです。

3) デプロイの中心は dbt build に寄せる

- name: Deploy (dbt build)
  run: dbt build --target prod
  • dbt buildrun + test をまとめて行います
  • dbt rundbt test を分けるより、ワークフローをシンプルに保ちやすいです

補足
「デプロイ前にチェックしたい」場合は、compile を必ず入れるのがおすすめです(構文エラーを早期に潰せる)。

4) 連続マージ対策(concurrency)

concurrency:
  group: dbt-cd-production
  cancel-in-progress: false
  • 連続でmainにマージが起きても、本番デプロイが競合しにくい構成です
  • 本番は「途中キャンセルより順次実行」を優先することが多いので false にしています

(任意)デプロイ前テストの考え方

「デプロイ前に本番データで dbt test したい」ケースもありますが、運用では次の点に注意が必要です。

  • 新規モデル/新規テストが追加された直後だと、デプロイ前にテストが落ちることがあります
  • 「デプロイ前にやるなら、対象を絞った smoke test にする」のが現実的です

例:tag:smoke をつけたテストだけ先に実行する

- name: Pre-deployment smoke tests (optional)
  run: dbt test --target prod --select tag:smoke

運用のコツ
smokeは「本番の既存データに対して、毎回チェックしたい最小セット」に絞るのがおすすめです。

5) Slack通知

- name: Notify Slack (Success)
  if: success()
  uses: slackapi/slack-github-action@v1.27.0
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
    payload: |
      {
        "text": "✅ dbt Production Deployment Successful",
        ...
      }
  • デプロイ成功・失敗をSlackに通知
  • Slack Incoming WebhookのURLをSLACK_WEBHOOK_URLとしてGitHub Secretsに登録

Slack Webhook URLの取得方法

  1. Slackワークスペース → Apps → "Incoming Webhooks" を検索・追加
  2. 通知先チャンネルを選択(例: #data-engineering
  3. Webhook URLをコピー(例: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
  4. GitHub Secrets に SLACK_WEBHOOK_URL として登録

✅ 承認ゲート(GitHub Environments)の設定

本番デプロイ前に人間による承認を必須にする場合、GitHub Environmentsを使います。

図2: 承認ゲートのフロー
図2: mainマージ後、GitHub EnvironmentsでCDが一時停止し、承認されるまでデプロイが保留される。

設定手順

Step 1: Environment作成

  1. GitHubリポジトリ → Settings → Environments
  2. 「New environment」をクリック
  3. Name: production
  4. 「Configure environment」をクリック

Step 2: 承認者設定

  1. Environment protection rules
  2. Required reviewers
  3. 「Add reviewers」で承認者を追加(例:チームリーダー)
  4. 「Save protection rules」

Step 3: 動作確認

  1. mainへPRをマージ
  2. GitHub Actionsの dbt CD を開く
  3. 「Review deployments」→ 承認者が「Approve and deploy」

補足
承認が長期間されないと、待機のままになります。運用ルール(承認SLA)もセットで決めるのがおすすめです。


🧾 デプロイログを残す(メタデータテーブル)

「いつ、誰が、どのコミットをデプロイしたか」を追えるようにします。
まずは 最小構成 としてテーブル1つで十分です。

Step 1: メタデータテーブル作成(Snowflakeで実行)

CREATE TABLE IF NOT EXISTS DBT_DB.DBT_PROD.DEPLOYMENT_HISTORY (
  deployment_id NUMBER AUTOINCREMENT,
  git_sha STRING,
  git_branch STRING,
  deployed_by STRING,
  deployed_at TIMESTAMP_LTZ DEFAULT CURRENT_TIMESTAMP(),
  dbt_version STRING,
  status STRING  -- 'success' / 'failure'
);

注意
SnowflakeのPRIMARY KEYは「宣言」できても強制されません。
監査用途ならこの程度でOKです(厳密にしたい場合は別途設計)。

Step 2: 記録用マクロ(macros/log_deployment.sql

{% macro log_deployment_info(status) %}
  {% set sql %}
    INSERT INTO {{ target.database }}.{{ target.schema }}.DEPLOYMENT_HISTORY
    (git_sha, git_branch, deployed_by, dbt_version, status)
    VALUES (
      '{{ env_var("GITHUB_SHA", "unknown") }}',
      '{{ env_var("GITHUB_REF_NAME", "unknown") }}',
      '{{ env_var("GITHUB_ACTOR", "unknown") }}',
      '{{ dbt_version }}',
      '{{ status }}'
    )
  {% endset %}
  {% do run_query(sql) %}
  {% do log("Deployment logged with status: " ~ status, info=True) %}
{% endmacro %}

Step 3: CDワークフローで呼び出す

dbt build の後に、成功/失敗を記録します。

- name: Deploy (dbt build)
  id: dbt_build
  run: dbt build --target prod

- name: Record deployment (success)
  if: success()
  env:
    GITHUB_SHA: ${{ github.sha }}
    GITHUB_REF_NAME: ${{ github.ref_name }}
    GITHUB_ACTOR: ${{ github.actor }}
  run: dbt run-operation log_deployment_info --args "{"status": "success"}" --target prod

- name: Record deployment (failure)
  if: failure()
  env:
    GITHUB_SHA: ${{ github.sha }}
    GITHUB_REF_NAME: ${{ github.ref_name }}
    GITHUB_ACTOR: ${{ github.actor }}
  run: dbt run-operation log_deployment_info --args "{"status": "failure"}" --target prod

運用ポイント
「同じコミットを何度もデプロイしたくない」場合は、
DEPLOYMENT_HISTORYで git_sha を確認してガードする運用にできます(後述)。


🔄 失敗時のリカバリー・ロールバック

CI/CD導入後も失敗は起きます。重要なのは 失敗時に素早く戻せる仕組み を用意しておくことです。

図3: 失敗時のリカバリーフロー
図3: CI失敗→修正→再実行、CD失敗→ロールバック→修正→再デプロイの流れ。

CI失敗時の対処(よくある2パターン)

ケース1: SQLコンパイルエラー

Compilation Error in model stg_customers (models/staging/stg_customers.sql)
  column "customer_id" does not exist

対処の流れ

  1. ログからモデル名を特定(例:stg_customers
  2. ローカルで dbt compile --select stg_customers を実行して確認
  3. target/compiled/... のコンパイル済SQLを見て原因を修正
  4. PRへpush → CIが再実行(自動)

ケース2: テスト失敗

Failure in test unique_stg_customers_customer_id (models/staging/schema.yml)
  Got 2 results, expected 0.

対処の流れ

  1. ローカルで dbt test --select stg_customers を実行して再現
  2. PRスキーマのデータを確認(例:重複の特定)
    SELECT customer_id, COUNT(*)
    FROM DBT_PR_123.stg_customers
    GROUP BY customer_id
    HAVING COUNT(*) > 1;
    
  3. モデルロジック or ソース側の問題を修正
  4. PR更新 → CI再実行

CD失敗時のロールバック(Snowflake Time Travel)

本番デプロイが失敗した場合、Snowflakeの Time Travel を使って過去の状態に戻せます。

Time Travelの保持期間
版や設定によって保持期間は変わります。導入時は必ず最新の公式ドキュメントで確認してください。

ロールバック例1: 1時間前の状態に戻す

CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
  CLONE DBT_DB.DBT_PROD.CUSTOMERS AT(OFFSET => -3600);

ロールバック例2: 特定時刻に戻す

CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
  CLONE DBT_DB.DBT_PROD.CUSTOMERS AT(TIMESTAMP => '2026-01-04 10:00:00'::TIMESTAMP_LTZ);

ロールバック例3: 特定のQuery IDの直前に戻す

CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
  CLONE DBT_DB.DBT_PROD.CUSTOMERS BEFORE(STATEMENT => '01a1b2c3-0000-0000-0000-000000000000');

注意(重要)
CREATE OR REPLACE が絡むと、状況によっては「期待した過去版に戻れない」ことがあります。
本番運用では、ロールバック手順を事前に演習して「戻せること」を確認しておくのがおすすめです。


📄 実装コードまとめ(後編で追加したもの)

前編の内容に加えて、後編で登場したファイルをまとめます。
(前編のCIワークフローや鍵ペア認証周りは、前編記事を参照してください)

.github/workflows/dbt_cd.yml(CD)

name: dbt CD (Production Deployment)

on:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: dbt-cd-production
  cancel-in-progress: false

jobs:
  dbt-cd:
    runs-on: ubuntu-latest
    environment: production

    env:
      DBT_PROFILES_DIR: .

      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
      SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
      SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
      SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dbt (pin recommended)
        run: |
          pip install --upgrade pip
          pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"

      - name: Create profiles.yml
        run: cp profiles.yml.template profiles.yml

      - name: Setup Snowflake private key
        run: |
          printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
          chmod 600 rsa_key.p8

      - name: Install dbt packages
        run: dbt deps

      - name: Pre-deployment check (compile)
        run: dbt compile --target prod

      - name: Deploy (dbt build)
        id: dbt_build
        run: |
          # フルビルド(全モデル・全テストを実行)
          dbt build --target prod
          
      - name: Clean up private key
        if: always()
        run: rm -f rsa_key.p8 profiles.yml

macros/log_deployment.sql

{% macro log_deployment_info(status) %}
  {% set sql %}
    INSERT INTO {{ target.database }}.{{ target.schema }}.DEPLOYMENT_HISTORY
    (git_sha, git_branch, deployed_by, dbt_version, status)
    VALUES (
      '{{ env_var("GITHUB_SHA", "unknown") }}',
      '{{ env_var("GITHUB_REF_NAME", "unknown") }}',
      '{{ env_var("GITHUB_ACTOR", "unknown") }}',
      '{{ dbt_version }}',
      '{{ status }}'
    )
  {% endset %}
  {% do run_query(sql) %}
  {% do log("Deployment logged with status: " ~ status, info=True) %}
{% endmacro %}

🔧 ハマりどころ(実務で詰まる7つ)

CI/CD導入時に詰まりやすいポイントと対処法をまとめます。

1) 認証エラー(account identifier / region)

エラー例

250001: Could not connect to Snowflake backend after 0 attempt(s).

原因SNOWFLAKE_ACCOUNT の形式が違うことが多いです。

確認

SELECT CURRENT_ACCOUNT(), CURRENT_REGION();

対処

  • GitHub Secretsの SNOWFLAKE_ACCOUNT を修正
  • account_locator.region 形式(例:abc12345.us-east-1)や、組織名形式など、利用中の形式に合わせる

2) 秘密鍵のフォーマットエラー(改行が消える)

エラー例

Could not deserialize the private key

対処

  • Secretsには -----BEGIN PRIVATE KEY----- から END まで 改行を保持して貼り付け
  • ワークフローでは printf "%s" で書き出す(echo より安全)
printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > /tmp/snowflake_key.p8

3) profiles.ymlの環境変数が入らない

エラー例

Env var required but not provided: 'SNOWFLAKE_ACCOUNT'

対処

  • Secrets名(大文字小文字)を確認
  • ワークフローで env に明示的に渡す(前編/後編ともにこの方式)

4) PRスキーマの削除が想定どおりに動かない(+schema設定)

dbt_project.yml+schema: を使っていると、CIの一時スキーマが

  • DBT_PR_123
  • DBT_PR_123_STAGING
  • DBT_PR_123_MARTS

のように 複数に増えることがあります(dbtのデフォルト命名規則)。
この場合、単純に DROP SCHEMA DBT_PR_123 だけだと取り残しが出ます。

対処(おすすめ)

  • CIターゲット(target.name == ci)では「常に target.schema に寄せる」ように generate_schema_name を上書きする
-- macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) %}
  {% if target.name == "ci" %}
    {{ target.schema }}
  {% else %}
    {{ default__generate_schema_name(custom_schema_name, node) }}
  {% endif %}
{% endmacro %}

これでCIは DBT_PR_123 に集約でき、クリーンアップが簡単になります。


5) dbtプロジェクトがサブディレクトリにある

モノレポ構成などでdbtがサブディレクトリにあると、そのままだと失敗します。

対処(例)

defaults:
  run:
    working-directory: ./dbt_project

6) ログの見方(GitHub Actions / Snowflake)

GitHub Actions

  • 該当ワークフロー → 各ステップのログを展開

dbt詳細ログ

dbt build --target prod --debug

Snowflake側の確認

  • SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY は反映に遅延があることがあります
  • すぐ見たい場合は INFORMATION_SCHEMA.QUERY_HISTORY() の利用も検討してください

7) コスト最適化(CI用Warehouseを小さくする)

対処例

  1. CI専用Warehouseを小さくする(例:X-Small)
  2. AUTO_SUSPENDを短くする(例:60秒)
  3. Slim CIで実行範囲を絞る
  4. pip/dbt packagesをキャッシュする

コストは環境や契約で変動します。あくまで目安として、継続的にモニタリングしてください。


🎯 運用Tips

1) CIの実行時間最適化(キャッシュ)

pipキャッシュ

- uses: actions/setup-python@v5
  with:
    python-version: "3.11"
    cache: "pip"

dbt packagesキャッシュ(任意)

- name: Cache dbt packages
  uses: actions/cache@v4
  with:
    path: dbt_packages
    key: dbt-packages-${{ hashFiles('packages.yml') }}

2) セキュリティ(Secrets管理と最小権限)

  • 本番は production Environment Secrets を使い、アクセス可能範囲を絞る
  • CI用ロールと本番用ロールを分離する(最小権限)
  • 秘密鍵は定期ローテーション(例:90日ごと)を検討

3) モニタリング・アラート

  • CD失敗はSlack通知(後述の例)
  • Snowflakeのクレジット消費はWarehouse単位で定期確認

4) dbt docsの自動公開(発展)

dbt docs generate をCI/CDで実行し、GitHub PagesやS3で公開できます。
(docs/lineageの活用は第6弾を参照)


🧪 実機検証:新しいmartモデルでCI/CD動作確認

ここまでの内容が実際に動作することを確認するため、新しいmartモデルを追加してCI/CDパイプラインを実行します。

検証用の .github/workflows/dbt_cd.yml は以下をコピーしてください。
※承認部分と通知の部分を削除しています。

.github/workflows/dbt_cd.yml
name: dbt CD (Production Deployment)

on:
  push:
    branches: [main]
    paths:
      - "models/**"
      - "tests/**"
      - "macros/**"
      - "seeds/**"
      - "snapshots/**"
      - "dbt_project.yml"
      - "packages.yml"
      - "profiles.yml.template"
      - ".github/workflows/**"

permissions:
  contents: read

concurrency:
  group: dbt-cd-production
  cancel-in-progress: false  # 本番はキャンセルせず順番に実行する想定

jobs:
  dbt-cd:
    runs-on: ubuntu-latest

    env:
      DBT_PROFILES_DIR: .

      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
      SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
      SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
      SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dbt (pin recommended)
        run: |
          pip install --upgrade pip
          pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"

      - name: Create profiles.yml from template
        run: cp profiles.yml.template profiles.yml

      - name: Setup Snowflake private key
        run: |
          printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
          chmod 600 rsa_key.p8

      - name: Install dbt packages
        run: dbt deps

      - name: Pre-deployment check (compile)
        run: dbt compile --target prod

      - name: Deploy (dbt build)
        id: dbt_build
        run: |
          # フルビルド(全モデル・全テストを実行)
          dbt build --target prod

      - name: Post-deployment verification
        if: success()
        run: |
          echo "✅ Deployment completed successfully at $(date)"
          echo "📦 Commit: $GITHUB_SHA"
          echo "👤 Actor: $GITHUB_ACTOR"
          echo "🎯 Target: prod"
          echo "🗄️  Database: $SNOWFLAKE_DATABASE"

      - name: Deployment failed
        if: failure()
        run: |
          echo "❌ Deployment failed at $(date)"
          echo "📦 Commit: $GITHUB_SHA"
          echo "👤 Actor: $GITHUB_ACTOR"
          exit 1

      - name: Clean up private key
        if: always()
        run: rm -f rsa_key.p8 profiles.yml

検証の流れ

  1. 事前準備:dbtサンプルモデルの削除(推奨)
  2. 新しいmartモデル(mart_order_summary.sql)を作成
  3. ローカルでテスト実行
  4. ブランチ作成・コミット・プッシュ
  5. PR作成
  6. GitHub Actions CI実行確認
  7. PRマージ
  8. GitHub Actions CD実行確認
  9. Snowflakeで本番データ確認

Step 0: 事前準備(推奨)- dbtサンプルモデルの削除

dbtプロジェクト初期化時に作成される models/example/ ディレクトリのサンプルモデルには、学習用に意図的にNULL値が含まれています。CI/CDでテストが失敗する原因になるため、削除することを推奨します。

# 1. サンプルモデルディレクトリを削除
rm -rf models/example/

# 2. dbt_project.yml から example 設定を削除
# 以下のセクションを削除またはコメントアウト
# models:
#   my_dbt_project:
#     example:
#       +materialized: view

ポイント:この作業は一度だけ実行すればOKです。すでに削除済みの場合はスキップしてください。


Step 1: 新しいmartモデルを作成

既存の stg_orders モデルを使って、日次の注文サマリを集計するmartモデルを作成します。

前提:第3弾で作成した stg_orders が存在することを確認してください。存在しない場合は、以下を先に実行:

# ローカル環境で
dbt run --select stg_orders --target dev

models/marts/ ディレクトリを作成

mkdir -p models/marts

models/marts/mart_order_summary.sql を作成

{{ config(
    materialized='table',
    tags=['mart', 'daily']
) }}

WITH order_base AS (
    SELECT
        DATE(order_date) AS order_date,
        order_id,
        customer_id,
        amount
    FROM {{ ref('stg_orders') }}
)

SELECT
    order_date,
    COUNT(DISTINCT order_id) AS total_orders,
    COUNT(DISTINCT customer_id) AS unique_customers,
    SUM(amount) AS total_revenue,
    AVG(amount) AS avg_order_value,
    MIN(amount) AS min_order_value,
    MAX(amount) AS max_order_value
FROM order_base
GROUP BY order_date
ORDER BY order_date DESC

(任意)テストを追加

models/marts/schema.yml を作成:

version: 2

models:
  - name: mart_order_summary
    description: "日次の注文サマリ(注文数、顧客数、売上合計など)"
    columns:
      - name: order_date
        description: "注文日"
        tests:
          - not_null
          - unique
      - name: total_orders
        description: "注文数"
        tests:
          - not_null
      - name: total_revenue
        description: "売上合計"
        tests:
          - not_null

Step 2: ローカルでテスト実行

# モデルを実行
dbt run --select mart_order_summary --target dev

# テストを実行
dbt test --select mart_order_summary --target dev

期待される結果:

1 of 1 OK created sql table model PUBLIC.mart_order_summary ......... [SUCCESS 1 in X.XXs]

1 of 3 PASS not_null_mart_order_summary_order_date .................. [PASS in X.XXs]
2 of 3 PASS unique_mart_order_summary_order_date .................... [PASS in X.XXs]
3 of 3 PASS not_null_mart_order_summary_total_orders ............... [PASS in X.XXs]

Snowflakeで確認

SELECT * FROM DBT_DB.PUBLIC.MART_ORDER_SUMMARY LIMIT 10;

Step 3: ブランチ作成・コミット・プッシュ

# 新しいブランチを作成
git checkout -b feature/add-mart-order-summary

# ファイルをステージング
git add models/marts/

# コミット
git commit -m "Add mart_order_summary model for daily order aggregation"

# GitHubへプッシュ
git push origin feature/add-mart-order-summary

Step 4: PR作成

  1. GitHubリポジトリのページへアクセス
  2. 「Compare & pull request」をクリック
  3. PR タイトル:Add mart_order_summary model
  4. PR 本文(例):
## 概要
日次の注文サマリを集計するmartモデルを追加しました。

## 変更内容
- `models/marts/mart_order_summary.sql` を追加
- `models/marts/schema.yml` にテスト定義を追加

## テスト
- ローカル環境で `dbt run --select mart_order_summary` 実行確認
- テストもすべてパス

## 確認事項
- [ ] CI(GitHub Actions)が成功すること
- [ ] マージ後、本番環境にデプロイされること
  1. 「Create pull request」をクリック

Step 5: GitHub Actions CI実行確認

PRを作成すると、前編で設定した dbt CI(Slim CI) が自動実行されます。

確認ポイント

  1. GitHubリポジトリ → Actions タブ
  2. 「dbt CI (Slim CI)」ワークフローが実行中であることを確認
  3. 各ステップのログを展開して確認:
    • ✅ Checkout code
    • ✅ Set up Python
    • ✅ Install dbt
    • ✅ Create profiles.yml
    • ✅ Setup Snowflake private key
    • ✅ Install dbt packages
    • ✅ Create PR Schema
    • ✅ Run dbt build (Slim CI)
    • ✅ Drop PR Schema
    • ✅ Clean up private key

期待される結果

  • dbt build --select state:modified+ --defer --state ./prod_state --target ci が実行される
  • mart_order_summary が新規モデルとして検出され、ビルド・テストされる
  • PRスキーマ(例:DBT_PR_123)が作成され、最後に削除される
  • すべてのステップが ✅ 成功

Snowflakeで確認(CIが成功している間)

-- PRスキーマが作成されていることを確認(CI実行中のみ)
SHOW SCHEMAS LIKE 'DBT_PR_%' IN DATABASE DBT_DB;

-- CI完了後はPRスキーマが削除されていることを確認
SHOW SCHEMAS LIKE 'DBT_PR_%' IN DATABASE DBT_DB;
-- (結果:0件)

図4: PRに「✅ dbt CI (Slim CI) success」とコメントが追加される
図4: PRに「✅ dbt CI (Slim CI) success」とコメントが追加される

図5: dbt CIの詳細な処理結果
図5: dbt CIの詳細な処理結果


Step 6: PRマージ

CIが成功したら、PRをマージします。

  1. PR画面で「Merge pull request」をクリック
  2. 「Confirm merge」をクリック
  3. (任意)ブランチを削除:「Delete branch」をクリック

Step 7: GitHub Actions CD実行確認

PRがmainにマージされると、dbt CD(Production Deployment) が自動実行されます。

確認ポイント

  1. GitHubリポジトリ → Actions タブ
  2. 「dbt CD (Production Deployment)」ワークフローが実行中であることを確認
  3. 各ステップのログを展開して確認:
    • ✅ Checkout code
    • ✅ Set up Python
    • ✅ Install dbt
    • ✅ Create profiles.yml from template
    • ✅ Setup Snowflake private key
    • ✅ Install dbt packages
    • ✅ Pre-deployment check (compile)
    • ✅ Deploy (dbt build)
    • ✅ Post-deployment verification
    • ✅ Clean up private key

期待される結果

  • dbt build --target prod が実行される
  • mart_order_summary が本番スキーマ(DBT_DB.DBT_PROD)に作成される
  • すべてのテストがパス
  • Post-deployment verification でコミットSHA、実行者が出力される

図6: dbt CDの詳細な処理結果
図6: dbt CDの詳細な処理結果


Step 8: Snowflakeで本番データ確認

CD成功後、Snowflakeで本番データを確認します。

-- 本番スキーマのテーブル一覧を確認
SHOW TABLES IN SCHEMA DBT_DB.DBT_PROD;

-- mart_order_summary が作成されていることを確認
SELECT * FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY LIMIT 10;

-- データ内容の確認
SELECT
    order_date,
    total_orders,
    unique_customers,
    total_revenue,
    avg_order_value
FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY
ORDER BY order_date DESC
LIMIT 5;

期待される結果

  • MART_ORDER_SUMMARY テーブルが存在する
  • 日次の注文サマリデータが格納されている
  • total_orderstotal_revenue などの集計値が正しく計算されている

図7: Snowflake側の確認結果
図7: Snowflake側の確認結果


検証完了!

おめでとうございます!新しいmartモデルを追加し、CI/CDパイプラインが正常に動作することを確認できました。

確認できたこと

  • ✅ ローカルで新しいモデルを開発・テスト
  • ✅ ブランチ作成・コミット・プッシュ
  • ✅ PR作成で自動的にCIが起動
  • ✅ Slim CIが変更箇所のみをテスト
  • ✅ PRスキーマが自動的に作成・削除される
  • ✅ PRマージで自動的にCDが起動
  • ✅ 本番環境へ自動デプロイ
  • ✅ Snowflakeで本番データを確認

この一連の流れが、GitHub ActionsでdbtのCI/CDパイプラインの実体です。


次の実験アイデア

さらに理解を深めるために、以下の実験も試してみてください:

1) CI失敗を体験する

意図的にエラーを起こしてCIの動作を確認:

-- models/marts/mart_order_summary.sql に構文エラーを追加
SELECT * FROM non_existent_table;

→ PRを作成 → CIが失敗 → ログで原因を確認 → 修正 → CI再実行

2) テスト失敗を体験する

テストを追加して失敗させる:

# models/marts/schema.yml
  - name: mart_order_summary
    columns:
      - name: total_revenue
        tests:
          - not_null
          - dbt_utils.expression_is_true:
              expression: "> 0"  # 意図的に厳しい条件

→ PRを作成 → CIでテスト失敗 → 原因を確認 → 修正

3) 本番デプロイ後のロールバック演習

Time Travelでロールバックを試す:

-- 現在の状態を確認
SELECT COUNT(*) FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY;

-- 5分前の状態に戻す
CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.MART_ORDER_SUMMARY
  CLONE DBT_DB.DBT_PROD.MART_ORDER_SUMMARY AT(OFFSET => -300);

-- ロールバックされたことを確認
SELECT COUNT(*) FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY;

📝 まとめ

CI/CD導入で得られた効果

この記事で構築したCI/CDパイプラインにより、次のような効果が得られます。

  • レビュー品質向上

    • PR作成時に自動テストが実行され、レビュー前に問題を検出
    • 「このPRは動くのか?」という不安が解消される
  • デプロイ時間短縮

    • mainマージ後、自動で本番デプロイ(手動実行の手間がゼロ)
    • 承認ゲートで「いつ本番に反映されたか」が明確
  • 本番汚染の防止

    • PR検証は一時スキーマ(DBT_PR_123)で実行され、本番DBに影響しない
  • ロールバック容易性

    • Time Travelで過去の状態に即座に戻せる
    • デプロイ履歴をメタデータで管理し、いつでも追跡可能
  • 監査証跡

    • いつ、誰が、何をデプロイしたかが記録される
    • コンプライアンス要件にも対応

次のステップ

CI/CDが整ったら、次は以下も検討できます。

大規模運用への移行:

  • モノレポ管理(複数dbtプロジェクトを1リポジトリで管理)
  • 並列実行(dbt run --threads 16
  • ジョブスケジューリング(Airflow / Dagster統合)

dbt Cloud移行の検討:

  • dbt Coreから dbt Cloud への移行を検討
  • dbt Cloud のCI/CD機能(Slim CI / Job Scheduler)を活用
  • コスト vs 運用工数のトレードオフを評価

Data Observability導入:

  • データ品質監視(elementary / Monte Carlo / Soda)
  • テスト失敗率のSLO設定(第4弾テスト戦略参照)
  • Slackアラート連携

マルチ環境対応:

  • staging環境の追加(本番デプロイ前の統合テスト)
  • 環境ごとのパラメータ管理(dbt Cloud variables / GitHub Environments)

セキュリティ強化:

  • OIDC認証への移行(秘密鍵不要)
  • SOC 2 / ISO 27001 対応

さいごに

前編・後編を通じて、GitHub ActionsでdbtのCI/CDパイプラインを完全自動化する方法を解説しました。

重要なポイント

  1. 3つのアプローチを理解し、自チームに合った方法を選ぶ
  2. 鍵ペア認証でセキュアにSnowflakeへ接続
  3. Slim CIで変更箇所のみテスト(高速化・コスト削減)
  4. 承認ゲートで本番デプロイを制御
  5. Time Travelで失敗時のロールバックを迅速化

この記事が、あなたのチームのdbt運用を次のレベルに引き上げる助けになれば幸いです!

🔗 参考リンク

Snowflake公式ドキュメント

dbt Labs公式ドキュメント

GitHub Actions

関連記事

過去の記事

脚注
  1. 第7弾 前編:CI/CD基礎・準備編 ↩︎

Discussion