Cloud RunのDBマイグレーションどうする問題にCloud Runジョブで対処する

RailsアプリをCloud Runで動かすときにいくつか悩むことがありますが、そのひとつにDBマイグレーションをどうやって実行するかがあると思います。
この問題の解決策としてCloud Runジョブを使ったらうまくはまったのでご紹介します。

# おことわり

Cloud Run自体の説明やCloud Runへのデプロイ方法については省略しています。別途公式ドキュメントや解説記事を参照してください。

また、Cloud Runジョブは現在プレビューとして提供されているので、事前に利用規約などをご確認ください。

https://cloud.google.com/run/docs/create-jobs?hl=ja

# Cloud RunのDBマイグレーションどうする問題とは

RailsアプリのDBマイグレーションは`rails db:migrate`コマンドの実行によって行います。

Cloud RunにデプロイしたRailsアプリで`rails db:migrate`を実行したいと思っても、Cloud Runサービスはリクエストを受けて処理を実行するものなので、リクエストを伴わずに特定の処理を実行させることはできません。

コンテナ起動処理に混ぜ込んだりCloud Runの外で実行したりいくつか方法はありますが、今回はCloud Runジョブを使う方法を順を追って設定していきます。

# 事前準備: Cloud RunにRailsアプリをデプロイ

サンプルとして単純なRailsアプリを用意しました。

https://github.com/okonomi/cloud-run-jobs-db-migrate-example

Cloud SQLのMySQLインスタンスに接続するので、Cloud SQLのインスタンスとデータベースを作成しておきます。

まずは普通にRailsアプリをデプロイしてブラウザでアクセスできることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461/6c7aab7c-7adc-a07d-743b-0243561e38dd.png)

# Cloud Runジョブでrailsコマンドを実行してみる

Cloud Runジョブの動作確認として、`rails about`コマンドをCloud Runジョブで実行してみます。

Cloud Runジョブは現在プレビューでbetaコンポーネントのインストールが必要なので、まだインストールしていなければインストールしておきます。

```
gcloud components install beta
```

ジョブを作成します。

```
gcloud beta run jobs create rails-about \
    --image [イメージURL] \
    --command bin/rails \
    --args about \
    --parallelism 1 \
    --max-retries 0 \
    --execute-now　 \
    --wait
```

オプションの意味は次のとおりです。

- `--command`, `--args`
    - 実行するコマンドとコマンドの引数
- `--parallelism 1`
    - 並列実行してほしくないので1を指定
- `--max-retries 0`
    - コマンドがエラーになったらリトライせずにそのまま終了
- `--execute-now`
    - ジョブ作成後すぐ実行
- `--wait`
    - ジョブの完了を待つ

ジョブが完了したら、GCPコンソールでジョブの実行結果を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461/e858c709-278a-981a-dc9c-dd059e3f6675.png)

`rails about`のコマンド結果が確認できました。

# Cloud Runジョブで`rails db:migrate`を実行する

Cloud Runジョブで`rails`コマンドを実行できることが確認できたので、次は`rails db:migrate`を実行してみます。
    
ジョブを実行する前に、DBマイグレーションされていないのを確認してみます。
`/posts`にアクセスするとエラーになります。ログを見ると`posts`テーブルが存在していないことがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461/0f197c08-4e03-5e7b-563d-2f09bd2327c9.png)

ではジョブを作成します。

```
gcloud beta run jobs create rails-db-migrate \
    --image [イメージURL] \
    --command bin/rails \
    --args db:migrate \
    --set-cloudsql-instances [インスタンス接続名] \
    --set-env-vars DATABASE_USERNAME [DBユーザー名] \
    --set-env-vars DATABASE_PASSWORD [DBパスワード] \
    --set-env-vars DATABASE_SOCKET /cloudsql/[インスタンス接続名] \
    --parallelism 1 \
    --max-retries 0 \
    --execute-now　 \
    --wait
```

`--args`に`db:migrate`を指定します。
`--set-cloudsql-instances`や`--set-env-vars`はCloud Runサービスと同じ情報を指定すればOKです。

先ほどと同じようにGCPコンソールでジョブの実行結果を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461/eb7d39b8-4808-cd0e-4c3e-8e1eeea1a5dd.png)

再度`/posts`にアクセスすると今度は正常にページが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461/a6f0b53d-1004-79de-786f-fc066c2829fb.png)

# デプロイフローに組み込む

最後に、CI/CDのデプロイフローに組み込むことを考えます。

既存のデプロイフローにコンテナイメージのビルドとCloud Runサービスのデプロイがあると思うので、その間にジョブの実行を挟みます。

```
gcloud beta run jobs update rails-db-migrate \
    --service-account [サービスアカウントID] \
    --image [イメージURL] \
    --command bin/rails \
    --args db:migrate \
    --set-cloudsql-instances [インスタンス接続名] \
    --set-env-vars DATABASE_USERNAME=[DBユーザー名] \
    --set-env-vars DATABASE_PASSWORD=[DBパスワード] \
    --set-env-vars DATABASE_SOCKET=/cloudsql/[インスタンス接続名] \
    --parallelism　1 \
    --max-retries　0 \
    --execute-now \
    --wait
```

ビルドした最新のコンテナイメージを使うように`gcloud beta run jobs update`でジョブ定義を更新します。
`--service-account`でデプロイフローで使用してるサービスアカウントの権限でジョブ実行するように指定します。

# まとめ

Cloud Runに載せたRailsアプリのDBマイグレーションをCloud Runジョブで実行してみました。
業務で運用しているRailsアプリでこの方法を導入していまのところ安定してデプロイできています。

いままでCloud Runでは`rails`コマンドやRakeタスクを実行するのが面倒でしたが、Cloud Runジョブの登場で弱点がひとつ消えてまた便利になりました。