dbt-coreで開発したModelをGitHub Actionsで定期実行してみた

2023.05.08

さがらです。

dbt-coreで開発したModelをGitHub Actionsで定期実行してみたので、検証した内容を本記事でまとめてみます。

本検証の意義

はじめに、なぜ「dbt-coreで開発したModelをGitHub Actionsで定期実行する意義があるのか」について簡単に述べます。

前提としてdbtは、SaaSの「dbt Cloud」と、OSSの「dbt-core」が提供されております。これらの違いについてはこちらの記事が参考になりますのでぜひ併せてご覧ください。

そして大きな違いとして、dbt-coreにはジョブスケジューラがないということが挙げられます。dbtで開発したModelは日々更新されるデータソースに合わせて定期実行する必要があるため、dbt-coreを採用する場合はジョブスケジューラが必須です。

ここでジョブスケジューラというと、Airflow・Dagster・PrefectといったOSSでしたり、Cloud ComposerやMWAAといった各クラウドプロバイダーによるAirflowのマネージドサービス、AWS Step FunctionsやGoogle Cloud Workflowsなどのクラウドプロバイダーが提供するサービスがあります。ただ、どれもサーバー管理や利用費などで金銭面のコストがかかったり、各ツール固有の機能を身につけるための学習コストが発生します。

そこで、GitHub Actionsをdbtのジョブスケジューラとして使う方法があります!GitHub ActionsはGitHubに付随するCI/CD向けのワークフローの自動化機能ですが、定義したワークフローを定期実行することも可能です。また、dbt-coreを使う方はGitHubなどのリモートリポジトリを併せて使うことになると思いますので、GitHubを使う流れで自然とGitHub Actionsを使用することが出来ます。

そのため、ジョブスケジューラに必要な各種コストを抑えつつ、GitHub Actionsの知識をそのまま活かしてdbt-coreを定期実行できることがこの組み合わせのメリットであり、本検証の意義となります。

注意事項

「じゃあdbt-coreの処理は全部GitHub Actionsで定期実行すればいいじゃん!」と思ってしまう方もいるかもしれませんが、そんなことは絶対にないです。具体的には、下記のようなデメリットもあります。

  • dbtのジョブの実行時間や頻度によっては、あっという間にGitHub Actionsの無料枠を消費し、多額の請求が発生する可能性がある
    • GitHub Actionsの課金については、こちらの公式Docも併せてご覧ください。
  • GitHub Actionsのジョブスケジューラとしての使い勝手や管理機能は、特別高いものではない
    • こちらの公式Docにもあるように、負荷が集中している時間帯だと遅延する可能性があります。

これらのデメリットを考慮すると、dbt-coreをGitHub Actionsでジョブスケジューラとして動かしたいケースは限られてくるとは思います…(上述の意義とは…)

ただ、それでもdbt-coreをGitHub Actionsで動かしたいケースはあると思っていて、「dbt-coreを小規模でPoCしたく、1日1回などの低頻度でdbtのModelを実行したい」みたいなケースでは役立つと思います。

また、ジョブスケジューラという観点からは外れますが、Pull RequestやMainブランチへのPush時など、特定のイベント発生時にdbtコマンドを実行したい場合にもGitHub Actionsは役に立ちます。(これは多くのdbt-coreユーザーが行っているパターンですね。)

やること

BigQuery向けにdbt-coreでModelを開発したと仮定し、開発したModelをGitHub Actionsで実行することを考えてみます。

ポイントは大きく以下の3つとなります。それぞれについて、後述していきます。

  • 開発環境と本番環境における環境変数
  • profiles.ymlの定義
  • workflowsフォルダに保存するGitHub Actions用の.ymlの定義

検証環境

  • OS:Ubuntu 20.04(Windows10、WSL2上で実行)
  • dbt-bigquery:1.5.0
  • Python:3.11.3

参考情報

今回の検証にあたっては、下記の記事を参考にさせていただきました。

開発環境と本番環境における環境変数

まず、BigQueryへの認証情報について、dbtからBigQueryに接続する際はWorkload Identityが使用できないため、サービスアカウントを使って認証するしかありません。

そのため、このサービスアカウント含めた各種認証情報やdbtで使用するデータベース・スキーマをどうやって管理するかが一つポイントになってきます。

この対策として、開発環境となるローカルと、本番環境となるGitHub上で、同じ名称で認証に関する環境変数やSecretを定義し、それを参照するようにしました。

具体的には、以下の4つについて、定義しています。私はプロジェクト名やデータセット名も環境変数にしていますが、最低限サービスアカウントのキーに関する情報は環境変数やSecretで保持しておく必要があります。

  • DBT_GOOGLE_BIGQUERY_KEYFILE:サービスアカウントのキーファイルについての情報 ※下記の通りローカルとGitHubで登録内容が違うので注意
    • ローカルの環境変数:キーファイルへのパス
    • GitHubのSecrets:キーファイルの中身をコピーして貼り付け
  • DBT_GOOGLE_PROJECT:出力先となるBigQuery上のプロジェクト名
  • DBT_GOOGLE_BIGQUERY_DATASET_DEV:開発環境で出力先となる、データセット名
  • DBT_GOOGLE_BIGQUERY_DATASET_PROD:本番環境で出力先となる、データセット名

以下は、実際に私が行った内容の例です。

  • ローカル:.bashrcに以下の内容を追記
export DBT_GOOGLE_BIGQUERY_KEYFILE="<キーファイルへのパス>"
export DBT_GOOGLE_PROJECT="<出力先となるBigQuery上のプロジェクト名>"
export DBT_GOOGLE_BIGQUERY_DATASET_DEV="開発環境で出力先となる、データセット名"
export DBT_GOOGLE_BIGQUERY_DATASET_PROD="本番環境で出力先となる、データセット名"
  • GitHub:対象リポジトリのSecrets and variablesに各内容を保存

profiles.ymlの定義

次に、使用するBigQueryの認証情報や使用するプロジェクト・データセットを記載するprofiles.ymlについてです。

先程定義した環境変数も参照しつつ、以下のように定義しました。targetはdbtコマンドを実行するときに指定することができ、devだと開発環境のデータセット、prodだと本番環境のデータセット、に出力するようになっています。

jaffle_shop:
  outputs:
    dev:
      dataset: "{{ env_var('DBT_GOOGLE_BIGQUERY_DATASET_DEV') }}"
      job_execution_timeout_seconds: 300
      job_retries: 1
      keyfile: "{{ env_var('DBT_GOOGLE_BIGQUERY_KEYFILE') }}"
      location: US
      method: service-account
      priority: interactive
      project: "{{ env_var('DBT_GOOGLE_PROJECT') }}"
      threads: 4
      type: bigquery
    prod:
      dataset: "{{ env_var('DBT_GOOGLE_BIGQUERY_DATASET_PROD') }}"
      job_execution_timeout_seconds: 300
      job_retries: 1
      keyfile: "{{ env_var('DBT_GOOGLE_BIGQUERY_KEYFILE') }}"
      location: US
      method: service-account
      priority: interactive
      project: "{{ env_var('DBT_GOOGLE_PROJECT') }}"
      threads: 4
      type: bigquery
  target: dev

workflowsフォルダに保存するGitHub Actions用の.ymlの定義

次は、GitHub Actions用の設定となります。

dbtで使用するリポジトリのルートディレクトリにおいて、.github/workflows/---.ymlという形で新しいyamlファイルを作り、以下のように定義します。

やっていることとしては、GitHub上で定義した環境変数やSecretsを参照し、サービスアカウントのキーをGitHub Actionsのランナーのローカルに配置してBigQueryへの認証ができるようにし、dbt-bigqueryをインストールして、各種dbtコマンドを実行する、という流れです。

ポイントとして、サービスアカウントのキーファイルをローカルに一度置かないと行けないのですが、一時的なランナーとはいえセキュリティの観点で気になる方もいると思うため、最後のstepで対象のキーファイルを削除するコマンドを打っています。

name: scheduled_run

on:
  schedule:
    # 動作確認のため、5分置きに設定
    - cron:  '*/5 * * * *'
  # 手動実行用の記述
  workflow_dispatch:
env:
  DBT_PROFILES_DIR: ./
  DBT_GOOGLE_PROJECT: ${{ vars.DBT_GOOGLE_PROJECT }}
  DBT_GOOGLE_BIGQUERY_DATASET_PROD: ${{ vars.DBT_GOOGLE_BIGQUERY_DATASET_PROD }}
  DBT_GOOGLE_BIGQUERY_DATASET_DEV: ${{ vars.DBT_GOOGLE_BIGQUERY_DATASET_DEV }}
  DBT_GOOGLE_BIGQUERY_KEYFILE: ./dbt-service-account.json

jobs:
  scheduled_run:
    name: scheduled_run
    runs-on: ubuntu-latest

    steps:
      - name: Check out
        uses: actions/checkout@main

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

      - name: Authenticate using service account
        run: 'echo "$KEYFILE" > ./dbt-service-account.json'
        shell: bash
        env:
          KEYFILE: ${{secrets.DBT_GOOGLE_BIGQUERY_KEYFILE}}

      - name: Install dependencies
        run: |
          pip install dbt-bigquery
          dbt deps --target prod

      - name: Run dbt models
        run: dbt run --target prod

      - name: Delete Credentials
        run: 'rm ./dbt-service-account.json'

実際に動かしてみた

上述の内容で準備を行い、GitHub Actions上でどのようにスケジュール実行されるかを確かめてみます!

実際に対象のワークフローの画面を見てみると、下図のようにスケジュール実行されているのがわかります。(5分置きに設定していたのに、明らかに5分置きに実行されていないのは気になりますが…)

実行された履歴のうち1つをクリックすると、下図のように表示されます。

更にクリックすると詳細の履歴が表示されます。dbtの処理が行われていることがわかりますね。

GitHub Actionsで登録したワークフローではdbt run --target prodとしていたので、出力先のデータセット名は本番用のデータセットとなります。実際にBigQueryで確認してみると、本番用のデータセットにdbtで開発したModelがテーブルやビューとして作られていました!

最後に

dbt-coreで開発したModelをGitHub Actionsで定期実行する方法についてまとめてみました。

私も試してみて定期実行の頻度には課題があることが実体験としてわかりましたが、実行頻度が高くなく、少し定刻より遅くなっても問題ないケースでは十分に使えるのではないでしょうか。

dbtのPoCなどにはちょうどよいと思いますので、ぜひお試しください!