GitHub ActionsでCloud SQLのマイグレーションを自動化する

2021-07-14

GCPのCloud SQLを利用したプロジェクトで,GitHub Actionsとgolang-migrateを使ってDBのマイグレーションを自動化してみました.

構成

使用したツール・サービス

  • GitHub Actions
  • golang-migrate
  • Cloud SQL Auth Proxy (Dockerイメージ)
  • Cloud SQL (MySQL5.7, db-f1-micro)

サービス構成,フロー

flow

流れは簡単で,

  1. 開発者(Developer)がGitHubにpush
  2. GitHub Actionsでプロキシを使用してCloud SQLに接続し,マイグレーション

これだけです.

ディレクトリ構成

.
├── .github
│   └── workflows
│       └── migration.yml
└── db
    └── migrations
        ├── 000001_create_users_table.down.sql
        ├── 000001_create_users_table.up.sql
        ├── 000002_create_posts_table.down.sql
        └── 000002_create_posts_table.up.sql

db/migrations以下に,golang-migrateの仕様に合わせたマイグレーションファイルを配置しています. 上では例として,usersテーブルの作成,postsテーブルの作成をするつもりのファイルを配置しました.

GitHub Actionsでは.github/workflows/migration.ymlに書いたワークフローを走らせます. migrate upコマンドでdb/migrations/XXX.up.sqlが実行され,migrate downコマンドでdb/migrations/XXX.down.sqlが実行される仕様です.

GCP

準備として,GCPのサービスアカウントを作成します.

APIの有効化とサービスアカウントへのrole付与などが必要ですが,ここでは割愛します. Cloud RunとCloud SQLを使っており,GitHub Actionsからマイグレーションを実行するようなプロジェクトにおいてサービスアカウントを作成する方法をドキュメントとして書いています. こちら参考にしてみてください.

サービスアカウントが作成できたらキー(jsonファイル)を手元に保存しておきます. キーは外部に公開しないでください.

Cloud SQLインスタンスも立てておく必要があります. これもドキュメントに手順を書いています.

Secretsの設定

GitHubのリポジトリのページから,Settings > Secretsの画面でRepository secretsの設定を行います.

  • CLOUDSQL_INSTANCE: Cloud SQLのインスタンス名
  • GCP_PROJECT: GCPのプロジェクトID
  • GCP_REGION: Cloud SQLのリージョン
  • GCP_SA_KEY: GCPのサービスアカウントキー
  • MYSQL_DATABASE: データベース名
  • MYSQL_PASSWORD: データベースのパスワード
  • MYSQL_USER: データベースのユーザ

これらを設定してください. ワークフローから${{ secrets.GCP_PROJECT }}のように参照でき,なおかつログにはアスタリスクで隠された文字列として表示されるため安全です.

マイグレーションのワークフロー

ようやく本題のマイグレーションが実行できます. 以下は.github/workflows/migration.ymlの中身です.

name: Migration

on:
  push:
    branches:
      - main

env:
  PROXY_IMAGE: gcr.io/cloudsql-docker/gce-proxy
  CLOUDSQL_INSTANCE_CONNECTION_NAME: ${{ secrets.GCP_PROJECT }}:${{ secrets.GCP_REGION }}:${{ secrets.CLOUDSQL_INSTANCE }}
  MYSQL_DSN: mysql://${{ secrets.MYSQL_USER }}:${{ secrets.MYSQL_PASSWORD }}@tcp(127.0.0.1:3306)/${{ secrets.MYSQL_DATABASE }}

jobs:
  migrate-db:
    runs-on: ubuntu-18.04
    defaults:
      run:
        working-directory: db

    steps:
      - uses: actions/checkout@v1

      - name: Start Cloud SQL Proxy
        run: |
          echo '${{ secrets.GCP_SA_KEY }}' > sa_key
          docker pull $PROXY_IMAGE
          docker run -d \
            -v $PWD/sa_key:/config \
            -p 127.0.0.1:3306:3306 \
            $PROXY_IMAGE /cloud_sql_proxy \
            -instances=$CLOUDSQL_INSTANCE_CONNECTION_NAME=tcp:0.0.0.0:3306 \
            -credential_file=/config

      - name: Install migrate
        run: |
          curl -L https://packagecloud.io/golang-migrate/migrate/gpgkey | sudo apt-key add -
          echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/migrate.list
          sudo apt-get update
          sudo apt-get install -y migrate

      - name: Migrate DB (up)
        run: migrate -path "./migrations/" -database "$MYSQL_DSN" up

mainブランチへのpushのみトリガーとするようにしています. これにより,直接mainブランチへpushしたとき,もしくはプルリクなどでmainブランチにマージされたときにワークフローが開始されます.

以下,ステップごとの説明です.

Step1. Cloud SQLのプロキシを起動

- name: Start Cloud SQL Proxy
  run: |
    echo '${{ secrets.GCP_SA_KEY }}' > sa_key
    docker pull $PROXY_IMAGE
    docker run -d \
      -v $PWD/sa_key:/config \
      -p 127.0.0.1:3306:3306 \
      $PROXY_IMAGE /cloud_sql_proxy \
      -instances=$CLOUDSQL_INSTANCE_CONNECTION_NAME=tcp:0.0.0.0:3306 \
      -credential_file=/config

権限付与したGCPのサービスアカウントのキーを,sa_keyという名前のファイルに保存しておきます. これはプロキシの認証に必要です.

次にCloud SQL Proxyの公式のDockerイメージをpullし,docker runで起動します. このコマンドはGCPのドキュメントにほとんどそのまま書いてあります.

書き方がわかればこんなもんですが,サービスアカウントキーをシングルクオートで囲っておかないと,echoコマンドでリダイレクトする際に失敗してしまうという罠がありました.

Step2. golang-migrateをインストール

- name: Install migrate
  run: |
    curl -L https://packagecloud.io/golang-migrate/migrate/gpgkey | sudo apt-key add -
    echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/migrate.list
    sudo apt-get update
    sudo apt-get install -y migrate

ジョブはUbuntuで動かしているのでLinux向けのインストール方法をもとに記述しています.

これも,パイプラインやリダイレクトの権限周りでエラーが起き,sudoを付けたりteeコマンドに置き換えたりする必要がありました.

Step3. マイグレーションを実行

- name: Migrate DB (up)
  run: |
    migrate -path "./migrations/" -database "$MYSQL_DSN" up

golang-migrateは,upやdownのコマンドの引数として1や2といった数字を与えると,現在のDBのバージョンを1段階,2段階上げたり下げたりすることが可能です. 数字を与えない場合,upもしくはdownのマイグレーションが全て実行されます.

今回はup(db/migrations/XXX.up.sql)を全て実行してDBを最新バージョンにします.

基本的に,コードにコミットされているスクリプトは全て実行して,バージョンを下げたり細かな調整をしたりするのは手動でやるという想定です.

最後に

Cloud SQLのマイグレーションを自動化する方法を紹介しました.

Cloud Buildを使った自動化についての記事はいくつもありますが,GitHub Actionsでやる方法はあまり見かけなかったのでブログ記事に書いてみました. ちなみに個人で利用する分には無料枠のあるGitHub Actionsの方がお手軽感もあって好きです.

できてしまえばこんなものかという感じがしますが,成功するまでのドキュメントを読みこんだりする過程で,クラウドサービスの扱いに慣れるきっかけにもなると思います. 何よりも,自動化すれば開発自体に集中できるという点が一番良いですね.

参考

developmentGitHubActionsGCPDB

2021年の振り返りと来年の目標

技育祭(2021)に参加して得た,エンジニア志望の学生に送るメッセージ