🔧

LightdashのCI/CDをGitHub Actions+AWS CodeBuildで構築した話

に公開

はじめに

こんにちは、ナウキャストでデータエンジニアをしているh-miyazawaです。

主に金融機関向けに、Snowflakeを活用したデータ基盤構築やデータの可視化を支援しており、とあるプロジェクトでBIツールとしてLightdashを利用しています。
前回はLightdashのテーブルチャートのTipsを紹介しました。

https://zenn.dev/finatext/articles/lightdash-table-chart-tips

その際、Lightdashの導入に合わせてCI/CDパイプラインも構築したのですが、後述の問題がありいくつか工夫が必要だったので、今回はその構成を紹介します。

前提条件

本記事の内容はOSS版 Lightdash(バージョン 0.2330.0)に基づいています。

Lightdash CLIを利用したデプロイの流れ

Lightdashのリソースをデプロイするには、Deploy changes to productionに記載のあるようにLightdash CLIを使います。流れは以下の通りです。

# 1. 認証
lightdash login "$LIGHTDASH_URL" --token "$LIGHTDASH_API_KEY"

# 2. Lightdashプロジェクトのデプロイ
lightdash deploy --project-dir <dbtのディレクトリパ> --profiles-dir <profiles.ymlのディレクトリパ> --target <profiles.ymlのターゲット>

# 3. チャート/ダッシュボードのアップロード
lightdash upload --path <チャート/ダッシュボードYAMLのディレクトリパ> --force

CI/CDでは--tokenオプションでPAT(Personal Access Token)を渡して認証しています。
クラウド版ではサービスアカウントを用いた認証も利用できますが、OSS版では環境変数SERVICE_ACCOUNT_ENABLEDがデフォルトfalseであり、trueに設定したとしてもライセンスキーがなければサービスアカウントは使えません。

https://github.com/lightdash/lightdash/blob/0.2330.0/packages/backend/src/ee/index.ts#L48-L50

認証、デプロイ、アップロードのCLIコマンドはいずれもLightdashのHTTP APIを呼ぶため、CLIの実行元からホスティングしたLightdashへのネットワーク到達が必要です。

Automate with CI/CDでもGitHub ActionsでCLIコマンドを実行する手法が紹介されています。

GitHub Actionsから直接実行できない問題

ナウキャストでは金融機関向けのプロジェクトを手がけており、セキュリティ要件でアプリケーションへの接続元IPを制限しているケースがあります。
今回のプロジェクトでもお客様要件で、Lightdashへの接続元IPを制限しているため、IPが不定なGitHub Actionsランナーから直接Lightdash CLIを実行できないという問題がありました。

GitHub Actions + CodeBuildによる構成

GitHub Actionsランナーの送信元IPはGitHub meta APIで公開されていますが、数千のCIDRブロックが含まれており"We make changes to our IP addresses from time to time."とあるように変更もあるうえ、許可リストに追加することは現実的ではありません。
そこで、GitHub ActionsからVPC内のAWS CodeBuildを起動し、CodeBuildからLightdash CLIを実行する構成にしました。

Lightdash CI/CDアーキテクチャ

GitHub ActionsからのCodeBuild実行の流れ

GitHub ActionsからAWS CodeBuildを経由してLightdash CLIを実行する流れです。

CI/CDパイプラインの処理ステップ

今回構築したCI/CDパイプラインではAWS CodeBuildの buildspec.yml で以下の4ステップを実行します。

  1. Lightdashプロジェクトのデプロイ
  2. チャート/ダッシュボードのアップロード
  3. 不要なチャート/ダッシュボードの削除
  4. ダッシュボードのピン留め

Lightdashプロジェクトのデプロイ

dbt parse はSnowflakeに接続せずに manifest.json を生成できます。

lightdash deploy が必要とするのもこの manifest.json だけなので、dbt parse + --skip-dbt-compile の組み合わせにより、Snowflakeへの接続は行わずにLightdashデプロイを行っています。

dbt clean                                    # キャッシュクリア
dbt deps                                     # 依存パッケージインストール
dbt parse --target <profiles.ymlのターゲット> # manifest.jsonの生成
lightdash deploy \
  --project-dir <dbtのディレクトリパ> \
  --profiles-dir <profiles.ymlのディレクトリパ> \
  --target <profiles.ymlのターゲット> \
  --skip-dbt-compile                         # parse済みなのでコンパイルをスキップ

チャート/ダッシュボードのアップロード

lightdash upload はYAMLファイルで定義したチャートとダッシュボードをLightdashに反映します。

今回のCI/CDパイプラインでは、Gitリポジトリの定義を正として常に同期させたいため、--force で変更検知をスキップし全量アップロードしています。

lightdash config set-project \
  --name <Lightdashのプロジェクト>   # 対象プロジェクトに切り替え
lightdash upload \
  --path <チャート/ダッシュボードYAMLのディレクトリパ> \
  --force                              # 変更検知をスキップして全リソースをアップロード

ちなみに、--force を指定しない場合、CLIはローカルファイルの変更有無をチェックし、変更がなければスキップします。

https://github.com/lightdash/lightdash/blob/0.2330.0/packages/cli/src/handlers/download.ts#L730-L745

不要なチャート/ダッシュボードの削除

lightdash upload はリソースの削除を行いません。

ダッシュボード名を変更するなどで、YAMLから削除したリソースがLightdash上に残り続けるため、Lightdash APIを使った独自のスクリプトでクリーンアップしています。

# 1. GitリポジトリのYAMLファイルからslug一覧を取得
local_slugs=$(find <YAMLディレクトリパ> -name "*.yml" -exec yq '.slug' {} \;)

# 2. Lightdash APIでプロジェクト内の全リソースを取得
remote_charts=$(curl -s -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
  "$LIGHTDASH_URL/api/v1/projects/$PROJECT_UUID/charts" | jq -r '.results[]')
remote_dashboards=$(curl -s -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
  "$LIGHTDASH_URL/api/v1/projects/$PROJECT_UUID/dashboards" | jq -r '.results[]')

# 3. Gitリポジトリに存在しないリソースをAPI経由で削除
for uuid in $orphan_dashboard_uuids; do
  curl -s -X DELETE -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
    "$LIGHTDASH_URL/api/v1/dashboards/$uuid"
done
for uuid in $orphan_chart_uuids; do
  curl -s -X DELETE -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
    "$LIGHTDASH_URL/api/v1/saved/$uuid"
done

ダッシュボードのピン留め

lightdash upload ではピン留め状態は制御できません。

ピン留めしないとチャートやダッシュボードが混在して埋もれてしまうため、デプロイのたびにAPIで自動的にピン留めしています。

Lightdashホームページの「Pinned items」タブ
※ 上記の画面イメージはデモサイトから取得しました。

ピン留めAPIはトグル動作のため、ピン留め済みに実行すると解除されてしまいます。ダッシュボード一覧の pinnedListUuidnull(未ピン留め)のものだけを対象にしています。

# 1. プロジェクト内の全ダッシュボードを取得
dashboards=$(curl -s -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
  "$LIGHTDASH_URL/api/v1/projects/$PROJECT_UUID/dashboards" | jq -r '.results[]')

# 2. 未ピン留めのダッシュボードのみピン留め
for uuid in $unpinned_dashboard_uuids; do
  curl -s -X PATCH -H "Authorization: ApiKey $LIGHTDASH_API_KEY" \
    "$LIGHTDASH_URL/api/v1/dashboards/$uuid/pinning"
done

おわりに

Automate with CI/CDで紹介されているGitHub ActionsからLightdash CLIを直接実行する手法では、IP制限の制約をクリアできなかったため、今回はAWS CodeBuildを中継する構成にしました。
本記事の内容が同じようにLightdashを使っている方の参考になれば幸いです。

Finatext Tech Blog

Discussion