ぽくつなです

Google Cloud 版 Dataform と周辺リソースの図

GCP 版 Dataform がついに GA になりましたね。同時に定期実行の仕組みも出て、一通りの機能が揃った感がある。いまこそ買収以前の SaaS 版(Legacy 版)から GCP 版に移行する時!!

しかし GitHub リポジトリと連携する場合、登場人物が多くて難しくなっていると思う。 特に GCP に馴染みがなかったりデータ分析がメインの人は困りそう。公式ドキュメントには step by step で書いてあるものの、なぜ必要なのか分からないまま設定することになる。

なので全体像を図にしたり補足するという趣旨のエントリです。

Dataform とは

Dataform とは...という話はしません。公式ドキュメントや世間のブログ記事を読もう。

Dataform を使うと、テーブル同士の依存に基づいて順番に SQL を実行してデータパイプラインを作ったり、依存関係を可視化したり、データセット名に suffix を付与してステージング環境を作ったりできる。

現状の Dataform でカバーしきれない複雑な要件、具体的には更新や変換にリアルタイム性が要求されたり、外部の依存やトリガーに基づくパイプラインなどでは Cloud Dataflow や Clodu Composer が必要になるけど、データウェアハウス実装のかなりの範囲をカバーするサービスです。

cloud.google.com

Google Cloud のリソースとの関係

このあたりのドキュメントで登場する登場人物をまとめた。


Dataform と周辺リソースの関係

① まず Dataform API を有効にして、コンソールから Dataform リポジトリを作る。

すると ② Dataform サービスアカウントが Cloud IAM に生えてくる。
service-{PROJECT_NUMBER}@gcp-sa-dataform.iam.gserviceaccount.com の形式。

IAM 一覧では、ユーザが作ったもの以外はデフォルトでは非表示なので、"Google 提供のロール付与を含める" をチェックするとよい。

ふくめる

重要なのは、Dataform はこのサービスアカウントの権限で動作するということ。
なのでこいつに一通りの権限を付与してあげる必要がある。

GitHub に行き、Dataform のコードを保存するリポジトリを作る

④このリポジトリGCP 側から操作するため Personal access token (PAT) を払い出す

GitHub には 2 種類の PAT がある。

classic は以前からあるが、リポジトリ単位でのアクセス制御ができない。新しい fine-grained を使い ③ で作ったリポジトリのみ許可するのが良い。classic では、scope に repo を、fine-grained では Repository permissions で Contents: read and write を与えればよい。

⑤ 再び GCP 側に戻り、Secret Manager でシークレットを作成し、④ で作成した PAT をバージョンとして保存する。

⑥ シークレットの "権限" タブで、② の Dataform サービスアカウントに roles/secretmanager.secretAccessor ロールを付与して Dataform がこのトークンを利用できるようにする。

補足すると、ここでは個別のシークレットへのアクセス権を付与している。IAM コンソールの目立つボタンからロールを付与すると、プロジェクトレベルのアクセス権となり、一通りのシークレットにアクセスできる権限を付与することになるので注意。

これで Dataform コンソールでコードを書いて GitHub へ push できるようになった。

⑦ Dataform を使う目的は BigQuery のテーブルを作成したり更新したりすることなので、IAM で Dataform サービスアカウントに BigQuery を操作する以下の権限を付与する。

  • BigQuery ジョブユーザー (roles/bigquery.jobUser)
  • BigQuery データ編集者 (roles/bigquery.dataEditor)

その他、別のプロジェクトのデータを参照したい場合は、そちらでも BigQuery データ閲覧者(roles/bigquery.dataViewer) を付与する。更新したいならデータ編集者ね。Dataform 実行プロジェクトと BigQuery リソースのプロジェクトは異なっていても構わない。

Google Spreadsheet など Google ドライブのデータを外部テーブルとして扱いたい場合は、該当のファイルを Dataform サービスアカウントに共有すればよい。

Spreadsheet をサービスアカウントに共有する

(オプション) CI やその他のワークフローから実行する可能性があるなら、それ用のサービスアカウントを作っておくとよい。

② のサービスアカウントは、GCP が管理するもので Service Agentとも呼ばれる。追加のロールを付与することはできるけど、ユーザーが成り代わったり認証情報を入手したりはできないので、Dataform コンソールの外から利用できない。

IAM での権限は Google グループに対しても付与できるので、Dataform サービスエージェントと、CI 用のサービスアカウントを所属させておいて、そのグループに対して BigQuery の権限を与えるようにしておくと、2 つのサービスアカウントそれぞれ与えなくて済むし権限が食い違うこともなくなる。

サービスアカウントを Google グループに所属させる

cloud.google.com

GitHub リポジトリと連携しなくても使える

ここまで GitHub リポジトリと連携するつもりで書いていたけど、連携しなくても Dataform を使うことはできる。
ただし、差分を見たり特定の revision に戻したりする UI はないので、git リポジトリとしての便利さはほぼ得られない。2023-05-14 現在、HEAD にコミットするか戻すかどうか、Workspace の変更を main に push する操作だけできる。

ちょっと触ってみたいだけなら、連携部分は気にしないでいいと思う。必要になったら GitHub リポジトリと接続すればいい。Legacy 版と違って既にファイルのある GitHub リポジトリと連携することもできるし。

CI

GitHub 連携をしたら GitHub Actions で CI を整備したいですね。
上のオプション部分で作ったサービスアカウントを OIDC 連携で利用して、

  • push したら compile に成功するかチェック & dry-run する
  • main にマージしたら Dataform を実行する

などするようにしています。

cloud.google.com

CI で色々やる際の注意点として、ルートの package.json の依存は @dataform/core のみか、最小限にする必要がある。Dataform コンソールで SQLX をコンパイルする際に、バックグラウンドで npm install が走っているようだけど、依存が増えるとタイムアウトしてまともに使えなくなる。@dataform/cli が増えただけでダメ。

CI の中でだけインストールしたり $ npx -p @dataform/cli@latest dataform compile などしています。

Can't install certain devDependencies [264598563] - Visible to Public - Issue Tracker

おまけ

Dataform 関連だけ抜き出した Terraform、google-beta が要ります。

resources.tf

variable "project" {
  type = string
}

variable "region" {
  type    = string
  default = "asia-northeast1"
}

variable "dataform_repository_url" {
  type = string
}

variable "dataform_default_branch" {
  type    = string
  default = "main"
}


data "google_project" "this" {
  project_id = var.project
}


resource "google_secret_manager_secret" "dataform_repository_token" {
  secret_id = "dataform_thirdparty_repository_token"

  replication {
    automatic = true
  }
}

resource "google_secret_manager_secret_version" "dataform_repository_token" {
  secret = google_secret_manager_secret.dataform_repository_token.id

  secret_data = "******"
}
# secret が state に書かれてしまうので、普段は手でコンソールから secret と version を作って
# secret は terraform import、 version は data リソースで参照、みたいにしている
#
# data "google_secret_manager_secret" "dataform_repository_token" {
#   secret = google_secret_manager_secret.dataform_repository_token.id
# }


resource "google_dataform_repository" "dataform" {
  provider = google-beta
  name     = "my-dataform-repository"
  region   = var.region

  git_remote_settings {
    url            = var.dataform_repository_url
    default_branch = var.dataform_default_branch

    authentication_token_secret_version = google_secret_manager_secret_version.dataform_repository_token.id
  }
}


# Dataform Service Agent へのロール付与
locals {
  dataform_agent_member = "serviceAccount:service-${data.google_project.this.number}@gcp-sa-dataform.iam.gserviceaccount.com"
}

resource "google_project_iam_member" "dataform_agent_roles" {
  for_each = toset([
    "roles/bigquery.dataEditor",
    "roles/bigquery.jobUser",
    "roles/dataform.editor",
    "roles/dataform.serviceAgent",
  ])
  project = var.project
  role    = each.value
  member  = local.dataform_agent_member

  depends_on = [
    # Dataform Service Agent は最初の repository が作成された後に作られる
    google_dataform_repository.dataform
  ]
}

resource "google_secret_manager_secret_iam_member" "dataform_repository_token" {
  secret_id = google_secret_manager_secret.dataform_repository_token.secret_id
  role      = "roles/secretmanager.secretAccessor"
  member    = local.dataform_agent_member

  depends_on = [
    google_dataform_repository.dataform
  ]
}


# CI などから Dataform を実行するためのサービスアカウント
resource "google_service_account" "dataform" {
  account_id   = "dataform"
  display_name = "dataform"
  description  = "Dataform 実行用サービスアカウント"
}

resource "google_project_iam_member" "dataform_sa_roles" {
  for_each = toset([
    "roles/bigquery.dataEditor",
    "roles/bigquery.jobUser",
    "roles/dataform.editor",
  ])
  project = var.project
  role    = each.value
  member  = google_service_account.dataform.member
}

おわりに

Dataform が買収されて進化が止まっている間にライバルの dbt が普及してしまった感がありますが、巻き返してほしいですね。

Google Cloud 組み込みだし、BigQuery を DWH として使っていたらすぐ使い始められるし、無料だし dbt Cloud のようにユーザー数課金でもない、第一の選択肢になると思います。

気が向いたら、定期実行とその通知について書こうと思います。