30
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Cloud BuildでCloud SQLのマイグレーションをしたい

Posted at

概要

CIでデプロイの自動化はよく行うかと思いますが、データベースのマイグレーション作業も自動化したかったりします。

GCPを使っているとCIはCloud BuildでDBはCloud SQLを使うケースがあるかと思いますが、同じGCPサービスながらCloud BuildからCloud SQLに接続する過程で色々苦労があったので、備忘録を残します。

ちなみにマイグレーションはgooseというライブラリを用いています。

構成

コードの構成は以下のようになっています。

database/ # データベース関連のファイル置き場。
  db/
    dbconf.yml
    migrations/
      2019xxxxxxxxxx_hogehoge.sql
      2019yyyyyyyyyy_fugafuga.sql
docker/
  Dockerfile.migrate # マイグレーション用のコマンドを記載したDockerfile

Dockerfileの中身は以下の通りです。

Dockerfile.migrate

FROM golang:1.12-stretch as builder

RUN apt-get update && apt-get install -y \
  git \
  wait-for-it

ENV GO111MODULE on
RUN go get bitbucket.org/liamstask/goose/cmd/goose

# WORKDIRは自分のプロジェクトパスに設定
WORKDIR /go/src/github.com/kshibata101/sample-project/database
COPY . .

# envはdb/dbconf.ymlに書いているものを指定。今回はcloud_sqlへ接続する設定を用いる。
CMD goose -env cloud_sql up

db/dbconf.ymlに書いた内容を元にDBへの接続を試みます。

db/dbconf.yml

cloud_sql:
    driver: mysql
    open: $CLOUD_SQL_DSN

gooseはgo-sql-driver/mysqlを使っているようなので、openにはdsnの形式で記載します。
dsnにはパスワードなどの設定情報が含まれるため、環境変数としてCloud Buildから入れるようにします。

事前準備

  • IAM
    • Cloud BuildからCloud SQLへ接続するためにIAMでCloud Buildのサービスアカウントに対して、「Cloud SQL 管理者」を設定しておきます。
  • Cloud SQL
    • マイグレーション対象となるDBを作成します。
    • このとき作成したインスタンスやDBの情報を後ほど使用します。

マイグレーション手順

今回の手順は似たようなことをしている記事があったため、そちらを参考にさせていただきました。

# https://stackoverflow.com/questions/52352103/run-node-js-database-migrations-on-google-cloud-sql-during-google-cloud-build/52366671#
steps:
  - id: 'build'
    name: 'gcr.io/cloud-builders/docker'
    args: ['build', '--no-cache', '-f', 'docker/Dockerfile.migrate',
           '-t', 'gcr.io/$PROJECT_ID/migration:$_TAG', 'database/']
  - id: 'cloudsql-proxy'
    name: 'gcr.io/cloudsql-docker/gce-proxy'
    entrypoint: 'sh'
    args: ['-c', '/cloud_sql_proxy -dir=/cloudsql -instances=$_CLOUD_SQL_CONNECTION_NAME & while [ ! -f /cloudsql/stop ]; do sleep 2; done']
    volumes:
      - name: cloudsql
        path: /cloudsql
  - id: 'migration'
    name: 'gcr.io/cloud-builders/docker'
    args: ['run', '-e', 'CLOUD_SQL_DSN=$_CLOUD_SQL_DSN',
           '-v', 'cloudsql:/cloudsql', # volumeはhost側はpathではなくnameを指定しろとのこと
           'gcr.io/$PROJECT_ID/migration:$_TAG']
    volumes:
      - name: cloudsql
        path: /cloudsql
    waitFor:
      - 'build'
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'sh'
    args: ['-c', 'touch /cloudsql/stop']
    volumes:
      - name: cloudsql
        path: /cloudsql
    waitFor:
      - 'migration'
substitutions:
  _TAG: 'latest'
  _CLOUD_SQL_CONNECTION_NAME: '' # required
  _CLOUD_SQL_DSN: '' # required
images:
  - 'gcr.io/$PROJECT_ID/migration:$_TAG'

要点としては、Cloud SQL Proxyをwhile sleepで立ち上げておき、それと並列でマイグレーションの処理を実行するところ位ですが、順に見ていこうと思います。

ステップ1: docker build

  - id: 'build'
    name: 'gcr.io/cloud-builders/docker'
    args: ['build', '--no-cache', '-f', 'docker/Dockerfile.migrate',
           '-t', 'gcr.io/$PROJECT_ID/migration:$_TAG', 'database/']

初めにbuildを行います。
-tで指定したタグ名については、gcr.io/$PROJECT_ID/以下は好きな名称を指定して問題ありません。

ステップ2: Cloud SQL Proxy

  - id: 'cloudsql-proxy'
    name: 'gcr.io/cloudsql-docker/gce-proxy'
    entrypoint: 'sh'
    args: ['-c', '/cloud_sql_proxy -dir=/cloudsql -instances=$_CLOUD_SQL_CONNECTION_NAME & while [ ! -f /cloudsql/stop ]; do sleep 2; done']
    volumes:
      - name: cloudsql
        path: /cloudsql
    waitFor:
      - '-'

gce-proxyのクラウドビルダーを利用してCloud SQL Proxyを立ち上げます。

$_CLOUD_SQL_CONNECTION_NAME にはCloud SQLのインスタンス接続名を使います。
Cloud Buildのトリガー設定で代入変数に埋め込んでおきます。

立ち上げるコマンドがややトリッキーですが、Cloud Buildではステップが移ると立ち上げたプロセスが落ちてしまうので、単純なコマンドでは次のステップでproxy経由での接続ができません。

そこで、whileを使ってcloud_sql_proxyコマンドを立ち上げたままの状態を維持します。

ただし、whileをずっと残しておくと逆にいつまでもビルドが終わらないため、マイグレーションが終わったタイミングで /cloudsql/stop にファイルを作成(ステップ4)しそれを検知して処理が終わるように仕向けます。

ファイルやディレクトリもステップ間で共有されないのですが、こちらはvolumesのオプションを使うことで共有できるようになります。
/cloudsql/stop ファイルがあるか判定するため /cloudsql をvolumesに指定しておきます。

waitForオプションはCloud Buildにおいてビルドの順列化・並列化をできるようにするものです。
通常は書いた順番に処理されていきますが、waitForを指定することで特定のビルドが終わったらこれを実行する、といったことができるようになります。

次のステップ3が始まる前にはproxyを立ち上げておきたいため、waitForに-を指定します。
-のみ指定するとビルド開始直後に実行されるようになります。

ステップ3: migration

  - id: 'migration'
    name: 'gcr.io/cloud-builders/docker'
    args: ['run', '-e', 'CLOUD_SQL_DSN=$_CLOUD_SQL_DSN',
           '-v', 'cloudsql:/cloudsql', # volumeはhost側はpathではなくnameを指定しろとのこと
           'gcr.io/$PROJECT_ID/migration:$_TAG']
    volumes:
      - name: cloudsql
        path: /cloudsql
    waitFor:
      - 'build'

先程ビルドしたdockerを起動することでマイグレーションを行います。

runの-eオプションで環境変数を渡せるため、DB接続に用いるDSN情報はCloud Buildの代入変数(ここでは $_CLOUD_SQL_DSN )を利用して埋め込みます。

dsn形式
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

Cloud SQLへの接続は公式でCloud SQL Proxy経由で行うことが推奨されているため、dsnもunix domain socket形式で記述することになります。

dsn上ではprotocolの部分を unix として記載します。
addressはproxyの立ち上げ場所にもよりますが、 今回はステップ2で/cloudsqlをdirと指定したため、 /cloudsql/{Cloud SQL インスタンス接続名} が入ります。
最終的には以下のような値を$_CLOUD_SQL_DSNに設定します。

username:password@unix(/cloudsql/project-id:asia-northeast1:cloud-sql-instance-id)/database?charset=utf8mb4&parseTime=True&loc=UTC

また気をつけなければならないのが-vオプションです。
Cloud SQLに接続するproxyはホスト側の /cloudsql 以下に作られていますが、コンテナ側に渡す際の -v オプションでホスト側はpathではなくnameを指定する必要があります。
(Issueに言及あり)

つまりホスト側を/cloudsqlなどのパス形式にしていると、うまく接続ができないので注意してください。

waitForのオプションについては、'build'を指定しているためステップ1が終わり次第実行されます。
前の項目で説明した通りステップ2はずっと実行され続けているため、ステップ2に依存する形にするといつまで経っても実行されません。ステップ1の後に実行されるようにします。

ステップ4: touch /cloudsql/stop

  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'sh'
    args: ['-c', 'touch /cloudsql/stop']
    volumes:
      - name: cloudsql
        path: /cloudsql
    waitFor:
      - 'migration'

migrationが終わった後に/cloudsql/stopにファイルを作成します。
これをするとステップ2がファイルの存在を検知して終了し、ビルド全体が完了されます。

まとめ

ということでCloud BuildからCloud SQLのマイグレーションをする手順の紹介でした。

他にも方法はあると思いますが、多少強引でもCloud SQL Proxyを立ち上げてやる方法もあるよ、くらいに思っていただればいいかと思います。

30
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?