4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform で BigQuery の Scheduled Query を IaC 化する方法【テンプレート SQL / 複数クエリ対応】

Last updated at Posted at 2025-12-11

目次

あいさつ

こんにちは、株式会社unerryでフロントエンドエンジニアをしている ほそだ です。

普段はReactだのNext.js等を触っているのですが、ひょんなことからTerraformでGCPまわりを書くことになりました。

せっかくなので、業務で組んだ BigQuery Scheduled Query をTerraformで管理する構成 をまとめてみました。

同じように「気づいたらインフラ寄りのタスクを抱えているエンジニア」の方の助けになれば嬉しいです。

導入:背景とこの記事で実現すること

BigQuery で定期的に SQL を実行する際、多くのプロジェクトではコンソールから Scheduled Query(定期クエリ)を設定しています。しかし以下のような課題に直面しやすく、運用規模が大きくなるほど手作業による管理には限界があります。

  • クエリ内容の変更が履歴として残らない
  • どの環境にどの定期クエリがあるか管理しづらい
  • External Query(Cloud SQL 参照)を使うと IAM や構成が特に複雑
  • クエリ追加・修正のたびに手動オペレーションが必要

これらを根本から解決する方法が Terraform による IaC(Infrastructure as Code)化 です。

本記事では、Terraform を使って BigQuery の Scheduled Query を SQL ファイル込みで完全コード管理 するための構成を紹介します。主に以下のポイントを実現できます。

  • Scheduled Query を module 化 + for_each で量産可能
  • SQL ファイルを templatefile() で読み込み、変数展開が可能
  • start_time が未指定でも Terraform 側で 自動で適切に補完
  • External Query を含む実務向けの構成(Cloud SQL 参照)にも対応
  • SQL を追加するだけで新しい定期クエリをデプロイできる

実際に動く Terraform 構成をそのまま利用できるため、
「BigQuery の定期クエリをコード管理したい」「運用の属人化をなくしたい」という方に最適な内容です。

事前準備

Terraform で BigQuery の Scheduled Query を IaC 化するにあたり、最低限以下の準備が必要です。

  • terraformのインストール
  • GCP のサービス有効化
  • Terraform State 用の GCS バケットの作成
  • Scheduled Query 実行用のサービスアカウントの作成

(いつか別の記事でまとめようと思っています)

ディレクトリ構成

本記事で採用する Terraform プロジェクトの構成は、Scheduled Query を module 化し、
SQL ファイルをテンプレートとして読み込めるようにしたシンプルな構成です。

.
├── main.tf
├── variables.tf
└── modules
    └── bigquery_scheduled_query
        ├── main.tf
        ├── queries
        │   ├── append_visitors.sql
        │   └── tenant_relationships.sql
        └── variables.tf

ポイント

  • root に main.tf を集約し、複数クエリを for_each で一括管理
  • modules/bigquery_scheduled_query/ が再利用可能な Scheduled Query Module
  • SQL は queries/ 以下でファイル管理し、templatefile() で読み込み
  • クエリ追加は「queries に SQL ファイル追加 → locals に定義を追加」するだけ
  • module 化により他プロジェクトへの転用・別のモジュールの追加も容易

この構造により、SQL と Terraform を分離しながら IaC として統合的に管理できます。

メインの構成とポイント

main.tfでは、以下の 3 つを中心に構成しています。

  1. Terraform / Provider の定義
  2. Scheduled Query 一覧(locals)をまとめて管理
  3. for_each を使ったモジュール呼び出し

実際の構成は次のようにシンプルで、拡張しやすい形を意識しています。

1. Terraform・Provider 設定

main.tf
terraform {

  backend "gcs" {
    bucket = "test-project-terraform-state"
  }

  required_version = "~> 1.13"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "7.5.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "7.5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

provider "google-beta" {
  project = var.project_id
  region  = var.region
}

#########################################
# BigQuery Scheduled Query
#########################################

locals {
  scheduled_queries = {
    tenant_relationships = {
      display_name = "replace_tenant_relationships_tf"
      query_file   = "tenant_relationships.sql"
      schedule     = "every day 15:00"
    }
    append_visitors = {
      display_name = "append_visitors_tf"
      query_file   = "append_visitors.sql"
      schedule     = "every day 15:10"
    }
  }
}

module "bigquery_scheduled_queries" {
  source   = "./modules/bigquery_scheduled_query"
  for_each = local.scheduled_queries

  project_id   = var.project_id
  region       = var.region
  display_name = each.value.display_name
  schedule     = each.value.schedule
  query_file   = "${path.module}/modules/bigquery_scheduled_query/queries/${each.value.query_file}"

  query_params = {
    project_id  = var.project_id
    dataset_id  = "app_db"
    cloudsql_id = "0000000000000.asia-northeast1.test-sql-instance"
  }
}

Root(main.tf) でのポイントは次の通りです。

  • for_each で複数の Scheduled Query を一括管理
  • SQL ファイルパスを root で解決することで、モジュール側がシンプルになる
  • query_params を map にまとめることで、SQL テンプレートで変数展開が可能
  • project_id や dataset_id はすべてここで制御でき、環境ごとの差分管理がしやすい

現場の運用では、クエリを追加する際に module 側を触る必要がないため、
運用コストが大幅に下がる実践的な構造になっています。

Scheduled Query モジュールの実装

このモジュールでは、BigQuery の Scheduled Query(Data Transfer Config)を作成します。
templatefile() で SQL を読み込み、start_time が未指定の場合は Terraform 実行時刻の +1 時間を自動設定します。

modules/bigquery_scheduled_query/main.tf
locals {
  # start_time が未指定の場合は現在時刻の 1 時間後を設定
  computed_start_time = var.start_time != null ? var.start_time : timeadd(timestamp(), "1h")
}

resource "google_bigquery_data_transfer_config" "scheduled_query" {
  project        = var.project_id
  location       = var.region
  display_name   = var.display_name != "" ? var.display_name : "scheduled_query_tf"
  data_source_id = "scheduled_query"
  schedule       = var.schedule

  service_account_name = var.service_account_name

  params = {
    query = templatefile(var.query_file, var.query_params)
  }

  schedule_options {
    start_time = local.computed_start_time
  }
}

SQL テンプレートの組み込み方法

Scheduled Query で実行する SQL は、Terraform の templatefile() 関数を使うことで外部ファイル化できます。
これにより SQL の変更を Git 管理しやすくなり、複数クエリの再利用も簡単になります。

1. SQL テンプレートファイルを配置

EXTERNAL_QUERYを使用して、cloudSQLからBigQueryにデータを持ってくるクエリをサンプルで書いています。

modules/bigquery_scheduled_query/queries/tenant_relationships.sql
INSERT INTO `${project_id}.${dataset_id}.visitors`
SELECT DATE(visited_at) AS visited_at_date,
  created_at,
  updated_at,
  uid,
  id,
  hwid,
  dm,
  visited_at
FROM EXTERNAL_QUERY(
    "${cloudsql_id}",
    """SELECT *
    FROM public.visitors
    WHERE DATE(created_at) = DATE(CURRENT_DATE - INTERVAL '1 day');"""
  );
modules/bigquery_scheduled_query/queries/append_visitors.sql
INSERT INTO `${project_id}.${dataset_id}.visitors`
SELECT * FROM EXTERNAL_QUERY(
  "${cloudsql_id}",
  "SELECT * FROM public.visitors;"
);

${project_id} や ${dataset_id} は templatefile() の変数として展開されます。

2. module で templatefile を使用

google_bigquery_data_transfer_config の params.query に直接組み込みます。

modules/bigquery_scheduled_query/main.tf
params = {
  query = templatefile(var.query_file, var.query_params)
}
  • var.query_file:SQL ファイルのパス
  • var.query_params:SQL 内で展開する変数マップ(例:project_id, dataset_id など)

3. root module で SQL パスとパラメータを渡す

main.tf
module "bigquery_scheduled_queries" {
  source   = "./modules/bigquery_scheduled_query"
  for_each = local.scheduled_queries

  project_id   = var.project_id
  region       = var.region
  display_name = each.value.display_name
  schedule     = each.value.schedule
  query_file   = "${path.module}/modules/bigquery_scheduled_query/queries/${each.value.query_file}"

  query_params = {
    project_id  = var.project_id
    dataset_id  = "app_db"
    cloudsql_id = "0000000000000.asia-northeast1.test-sql-instance"
  }
}

Terraform Apply と動作確認

最後に、Terraform で Scheduled Query をデプロイし、BigQuery 上で動作を確認します。

1. 初期化

まずは provider と backend を初期化します。

terraform init

2. プラン確認(差分チェック)

Terraform がどの Scheduled Query を作成/更新するか確認します。

terraform plan

問題がなければ apply に進みます。

3. デプロイ(Scheduled Query の作成)

terraform apply

すべて yes で確定すれば、Scheduled Query が GCP 上に作成されます。

4. BigQuery コンソールで確認

GCP コンソールから以下を確認します:

  1. BigQuery → Transfers → Scheduled Queries
  2. display_name で指定した名前が作成されていること
  3. クエリ内容が SQL テンプレート通りに展開されていること
  4. 「次回実行時刻(start_time)」が正しく設定されていること

拡張アイデア

  • Scheduled Query の追加をファイル追加だけで自動化
    queries ディレクトリに SQL を増やし、locals に定義を追加するだけで拡張可能
  • 環境(dev / stg / prod)ごとにスケジュールやパラメータを切り替え
    tfvars と workspace を使うと柔軟に環境差分を管理できる
  • Cloud Composer や Cloud Scheduler との比較・統合
    より複雑なワークフローにも発展可能
  • module を社内標準としてカタログ化
    新しい定期クエリが必要になった際のセットアップ工数がゼロになる
  • moduleを追加
    その他GCPのサービス(Cloud SQL, Cloud Run など)を追加してインフラ全体をterraformで管理する

まとめ

  • BigQuery Scheduled Query は Terraform で安全かつ再現性のある管理が可能
  • templatefile と module 化により SQL 管理・スケジュール設定・外部クエリ を柔軟に扱える
  • for_each で複数クエリを効率的にデプロイできる
  • 本記事の構成を使えば、新しい SQL 追加だけで定期クエリを量産可能

実務環境で Scheduled Query を IaC 化したい方にとって、再利用性と保守性の高いテンプレートとして活用できる内容になっています。

GitHubのリポジトリも載せておくので一緒に見てみてください

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?