Dataform cli を Cloud Run で動かす
動機
- Dataform をイベント駆動で実行したい
- Dataform には REST API が用意されておりリクエストとすると実行が可能
- しかし REST API は Beta 版である(2022/07 時点)
- また REST API は不安定でレスポンスが返ってこないことがときたまある
- Dataform cli を実行するためのインフラとして Cloud Run (Jobs) を使ってみる
Dataform プロジェクトの用意
こちらを参考にやっていく
Dockerfile
上の手順で作った example.sql
を実行するための Dockerfile を書いてみる。
ただ、dataform init-creds bigquery
が実行ができなかった。
コマンド途中に location や service account の json へのパスを入力する手順を Dockerfile 内で突破できなかったためである。
(コマンド途中の標準入力はパイプ使えば突破できるかなと思ったができなかった。誰か突破の仕方教えてほしい…)
FROM node:slim
MAINTAINER hiracky16
RUN npm i -g @dataform/cli
ENV DATAFORM_PATH /dataform
WORKDIR $DATAFORM_PATH
RUN dataform init bigquery . --default-database
# TODO: 本当は実行したかったがコマンド実行時の入力がクリアできず断念
# 代わりにローカルで作った .df-credentials.json を COPY
# RUN dataform init-creds bigquery
COPY dataform.json .
COPY definitions/* definitions/
COPY includes/* includes/
COPY .df-credentials.json .
RUN dataform compile
ENTRYPOINT ["dataform", "run"]
試しに実行
> docker build -t dataform .
...
> docker run dataform
Compiling...
Compiled successfully.
Running...
Dataset created: dataform.example [view]
できてた。
Artifact Registry へ push
- リポジトリを作る(dataform って名前にした)
- 以下を実行
> docker build -t asia-northeast1-docker.pkg.dev/$PROJECT/dataform/dataform:latest .
...
> docker push asia-northeast1-docker.pkg.dev/$PROJECT/dataform/dataform:latest
...
Artifact Registry を見たらアップされてた。
(寄り道)Cloud Run jobs を使ってみる
- 先日プレビューになった Cloud Run jobs を使ってみる
- 普通の Cloud Run は HTTP リクエストをトリガーに実行されるが jobs 任意のタイミングでジョブを作成し実行することができる
- 上記で作った Docker イメージをそのまま jobs に登録して実行してみる
さっき push したイメージを指定してジョブを登録する。
登録したジョブを実行してみると BigQuery に example という view ができる。
Cloud Run で実行する
これを参考にシェルを実行できる Shell を実行できるアプリを作成する。
Cloud Run で実行(node バージョン)
- dataform cli は npm パッケージなのでアプリも node で書いて Shell を実行すると Go のアプリから node を実行するみたいなテクニカルなことをせずとも済む
FROM node:slim
MAINTAINER hiracky16
RUN npm i -g @dataform/cli
ENV DATAFORM_PATH /app
WORKDIR $DATAFORM_PATH
RUN dataform init bigquery . --default-database
COPY dataform.json .
COPY definitions/* definitions/
COPY .df-credentials.json .
COPY ./package.json .
RUN npm i express
COPY invoke.js .
CMD ["node", "invoke.js"]
- node で shell を実行するアプリを作ってみる
- child_process を使うと shell を実行することができた
参考
const express = require('express');
const childProcess = require('child_process');
const util = require('util');
const exec = util.promisify(childProcess.exec);
const app = express();
const Response = {
success: "Success",
error: "Error"
}
const execShell = async () => {
const res = await exec('dataform run')
return res.stderr ? Response.error : Response.success
}
app.get('/', async (req, res) => {
const response = await execShell();
res.send(response);
});
const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
console.log(`helloworld: listening on port ${port}`);
});
これをデプロイして発行された URL にリクエストしてみると、レスポンスが返ってきて、BigQuery にも view が作られていた。
tags を指定して実行する
クエリパラメータに ?tag=hogehoge
みたいに指定したら dataform の tags を指定できるようにしたい。
tags とは SQLX という記法の config に設定する項目の一つで、dataform のテーブルをとある文脈でまとめることができる。
例えば datawarehoues のクエリだけ実行するみたいな限定ができる。
example.sql
をベースに example1.sqlx
と example2.sqlx
を作る
config { type: "view", tags: ["tag1"] }
select 1 as test
config { type: "view", tags: ["tag2"] }
select 2 as test
次に node のアプリの方を修正
const express = require('express');
const childProcess = require('child_process');
const util = require('util');
const exec = util.promisify(childProcess.exec);
const app = express();
const Response = {
success: "Success",
error: "Error"
}
const execShell = async (tag) => {
let command = 'dataform run'
if (!!tag) {
command = `${command} --tags ${tag}`
}
const res = await exec(command)
if (res.stderr) {
console.error(res.stderr)
return Response.error
}
console.info(res.stdout)
return Response.success
}
app.get('/', async (req, res) => {
const tag = req.query.tag
const response = await execShell(tag);
res.send(response);
});
const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
console.log(`helloworld: listening on port ${port}`);
});
こうすることによって https://dataform-xxxxx?tag=tag1
で example1 が、https://dataform-xxxxx?tag=tag2
で example2 が BQ 上に作成される。
tags によって実行するクエリを限定できたことが確認できる。
目的だった Cloud Run サービスへの HTTP リクエストをトリガーに dataform run
を実行することができた。
全然関係ないが Cloud Run で Shell を動かすアプリ汎用性あるので他でも使えるかもと思った。
.df-credentials.json の扱いについて
-
https://zenn.dev/link/comments/56fd475589f01c にて
.df-credentials.json
をコマンドベースで生成できずでローカルで生成したものをコピーすることで対応した -
このファイルの中身はサービスアカウントキーの内容がそのまま記述されておりセキュリティ的にコンテナに含むのは気が引ける
-
Cloud Run では Sercret Manager のシークレットを環境変数やファイルとして扱うことができる
-
コンソール上から設定するとこんな感じ。
-
ポイントが
/app/credentials/.df-credentials.json
にファイルをマウントするところ -
dataform のプロジェクト直下
/app
にないと動かないため Dockerfile 側でシンボリックリンクを予め作成しておく-
/app
配下にマウントすると SQL ファイルなども上書きされちゃうので別ディレクトリを指定する必要がある
-
FROM node:slim
MAINTAINER hiracky16
RUN npm i -g @dataform/cli@1.22.1
ENV DATAFORM_PATH /app
WORKDIR $DATAFORM_PATH
RUN dataform init bigquery . --default-database
RUN mkdir credentials
COPY dataform.json .
COPY definitions/* definitions/
# COPY .df-credentials.json /tmp/.df-credentials.json
RUN ln -s credentials/.df-credentials.json .df-credentials.json
COPY ./package.json .
RUN npm i express
COPY invoke.js .
CMD ["node", "invoke.js"]```