こんにちは、CTO室の丸山です。相変わらず某CTOからの無茶振りを捌いております。
今回は、今開発しているSaaSサービスのER図を、なるべく手をかけずに最新状態に保ちながら、いつでも閲覧できるようにするために実践したことをご紹介できればと思います。
前提環境
今回ご紹介する方法は以下の環境下で実現しています。
- インフラ: GCP
- データベース: PostgreSQL
- データベースのスキーマ管理: sql-migrate
- CI/CD: Github Actions
モチベーション
開発・データ分析を行う上で、データベースのスキーマ構造を俯瞰的に把握するための手段としてER図を書いているのですが、これを最新の状態に保ち続けるのは割と面倒であると常々感じていました。
そこで、Schemaspyを使用してデータベースのスキーマ構造からER図を生成するようにしたものの、以下のような手順をスキーマ構造の更新のたびに手動で行うのは地味に面倒であり、手動だとER図の更新を忘れがちでした。
- ① PostgreSQLのDockerコンテナを起動する
- ② データベースに対してマイグレーションを実行してテーブルを作成/更新する
- ③ Schemaspyを使用してマイグレーションに基づく最新のスキーマ構造からER図を書き起こす
- ④ 書き起こしたER図(HTML形式)をWebサーバにホスティングする
上記の課題感を解消するためにER図の更新が自動的に行われる状態を目指したわけですが、とはいえ頻繁に見るものでもないので、運用コストはなるべく安く済ませたいと考えました。
ということで、以下の要求をなるべくお手軽に叶える方法を考えてみることにしました。
- 最新のDBスキーマ構造を元に作成されたER図を常時閲覧できる環境がほしい
- プロジェクト関係者全員がER図を閲覧できるようにしたい
- プロジェクト関係者だけが閲覧できれば十分
- 運用コストは極力かけたくない
実現方法の概要
私たちが開発に利用している前提環境を踏まえて、Github Actions と AppEngine Standard環境 と Identity-Aware Proxy を組み合わせて実現しました。
- マイグレーションツールを管理するgitリポジトリに対し、特定のブランチに対するPUSHの発生をトリガとして、ER図の生成とAppEngineへのデプロイを行うGithub Actionsジョブを定義
- WebサイトとしてホスティングされたER図は、予め指定されたユーザーのみが閲覧できるようにIdentity-Aware Proxyによりアクセス制御を設定
- 一定時間アクセスがなければAppEngineのインスタンスを自動的に停止することで運用コストを抑制(無料割当の範囲内で運用)
以下では簡単な実装例を紹介したいと思います。
実装例
ディレクトリ構成
マイグレーション用のDDLと設定情報、AppEngineの設定、Github Actionsのジョブ定義を以下のように構成します。
. ├── .github │ └── workflows │ └── release-er-diagram.yml # ER図の生成とホスティングを実行するジョブ ├── appengine │ └── app.yaml # AppEngineにホスティングする際の設定ファイル ├── dbconfig.yml # sql-migrateを実行する際の設定情報 ├── migrations # sql-migrateで実行するDDL群の格納先 │ ├── create-tables.sql │ ├── add-tables.sql │ └── ... └── output # schemaspyで生成したER図の一時的な出力先
release-er-diagram.yml
develop
ブランチへのコミットをトリガにして、ER 図の作成及び AppEngine へのホスティングを行うための Github Actions の CI ジョブです。
name: Create & Upload ER Diagram on: # developブランチにPUSHし、且つmigrationsディレクトリ配下が更新されていたら実行 push: branches: - develop paths: - "migrations/**" jobs: create-er-diagram: runs-on: ubuntu-latest timeout-minutes: 10 # PostgreSQLのサービスコンテナを起動する # https://docs.github.com/ja/enterprise-cloud@latest/actions/using-containerized-services/creating-postgresql-service-containers services: postgres: image: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: eredit TZ: Asia/Tokyo ports: - 5432:5432 steps: - name: Checkout uses: actions/checkout@v4 # sql-migrateを使用するためにgolang、及びsql-migrateをセットアップ - name: Setup Golang uses: actions/setup-go@v5 with: go-version: '1.22.1' # @see https://github.com/rubenv/sql-migrate?tab=readme-ov-file#installation - name: Install sql-migrate run: go install github.com/rubenv/sql-migrate/...@latest # サービスコンテナに対してマイグレーションを実行し、適用状況を確認 # @see https://github.com/rubenv/sql-migrate?tab=readme-ov-file#usage - name: Setup database run: | sql-migrate up -config dbconfig.yml -env local sql-migrate status -config dbconfig.yml -env local # ER図を作成するSchemaspyの実行環境としてJavaをセットアップ - name: Setup JDK uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: '21' # SchemaspyとPostgreSQLのJDBCドライバを手動で取得した上で、Schemaspyを実行してER図を作成(執筆時点の最新版) # @see https://schemaspy.org/ # 最新版に拘らないのであれば、 Market place で公開されている Github Action の利用を検討してみてもよいかもしれない # @see https://github.com/marketplace/actions/run-schemaspy - name: Run SchemaSpy run: | wget -O schemaspy.jar https://github.com/schemaspy/schemaspy/releases/download/v6.2.4/schemaspy-6.2.4.jar wget -O postgresql.jar https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.3/postgresql-42.7.3.jar java -jar schemaspy.jar -dp postgresql.jar -t pgsql -host localhost -port 5432 -db eredit -u app -p secret -s public -o output -vizjs # 生成したER図は Artifacts に一時的に保管 - name: Archive ER Diagram uses: actions/upload-artifact@v4 with: name: er-diagram path: | output overwrite: true retention-days: 1 # AppEngineにデプロイするまでの一時的な置き場として利用するので保存期間は設定可能な最小とする deploy-to-appengine: runs-on: ubuntu-latest timeout-minutes: 10 needs: create-er-diagram environment: dev_appengine steps: - name: Checkout uses: actions/checkout@v4 # Artifacts からER図を取得して、appengine/public ディレクトリ配下に展開 - name: Download ER Diagram uses: actions/download-artifact@v4 with: name: er-diagram path: appengine/public - name: Authenticate with Google Cloud id: auth uses: google-github-actions/auth@v2 with: # Github リポジトリのEnvironments配下でサービスアカウントキーを保持 # TODO: Workload Identity federation を使用すれば、サービスアカウントキーを保持する必要はなくなる credentials_json: "${{ secrets.GCP_CREDENTIALS }}" # appengine配下のリソースを静的サイトとしてAppEngineにデプロイ - name: Deploy ER Diagram to App Engine uses: 'google-github-actions/deploy-appengine@v2' with: working_directory: appengine - name: Setup Cloud SDK uses: google-github-actions/setup-gcloud@v2 # 過去にデプロイしたものは直近3世代分を残して破棄 # 直近稼働分のメトリクスを閲覧できるように残しているが不要なら残さなくてもよい - name: Delete old apps run: | result=$(gcloud app versions list --service=er-diagram --sort-by=~version.createTime --format='value(version.id)' --filter="version.servingStatus='STOPPED'" | tail -n +3); [[ -z "$result" ]] && echo "No old versions to delete." || echo "$result" | xargs -r -I {} gcloud app versions delete {} --quiet
dbconfig.yml
sql-migrateを実行するための各種接続情報、具体的にはCIの過程で起動した PostgreSQL のサービスコンテナに対する接続情報を管理します。
# sql-migrateを実行するための各種接続情報 local: dialect: postgres datasource: host=localhost port=5432 dbname=eredit user=app password=secret sslmode=disable dir: migrations
app.yaml
AppEngineの設定ファイルです。 Schemaspyが生成したER図を静的サイトとして運用するためのルーティング設定を書きます。
runtime: php82 # AppEngine上でのサービス名称 service: er-diagram # 一番低スペックのインスタンスを選択 #(静的サイトかつアクセス数は少ないので、低スペックインスタンスで十分と判断) instance_class: B1 handlers: # /へのアクセスは public/index.html にルーティング - url: / static_files: public/index.html upload: public/index.html secure: always # 全てのアクセスを public 配下のコンテンツにルーティング - url: /(.*) static_files: public/\1 upload: public/(.*) secure: always # 最大インスタンス数は1として余計なスケールを抑制しつつ、 # 一定時間アクセスがなかったらインスタンスを停止してコストを抑える basic_scaling: max_instances: 1 idle_timeout: 10m
GCP側の各種設定
AppEngineへのデプロイに必要な権限
AppEngineへのデプロイを行うサービスアカウントには以下のロールを付与します。
- AppEngine サービス管理者
- AppEngine デプロイ担当者
- Cloud Build 編集者
- サービスアカウント ユーザー
- ストレージ フォルダ管理者
Identity Aware Proxy によるアクセス制御
AppEngineへのデプロイ後、Identity−Aware Proxy によるアクセス制御を有効にします。
開発者のIAMロール
ER図をホストするAppEngineアプリケーションにアクセスするユーザーに、以下のロールを付与します。
- IAPで保護されたウェブアプリ ユーザー
結果
アクセス権限のあるユーザーでアクセスすると、以下のようにER図が閲覧できます。
今回は sql-migrate と PostgreSQL の事例でご紹介しましたが、他のツールやRDBMSに置き換えてもおおよそ同じような方法で実現できると思います。
同じようなお悩みを抱えている方は、ぜひ参考にしてください!