【実践版】Rails アプリを Cloud Run で動かしてみよう

[【超シンプル版】Rails アプリを Cloud Run で動かしてみよう](https://qiita.com/kato-hiroaki/items/b8abe200b4b653f0e655)で Cloud Run を使って Rails アプリを動かしてみました。
今回はもう少し実践で使えるようにデータベース、環境変数の設定、ログの収集、ビルドの自動化を構築していきたいと思います。

## 前提

以下の構成で作っていきます！
![Untitled-2022-11-29-2139.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322836/c701f2fa-0e28-f558-833c-3212ce6a2d96.png)


## 追加で利用するプロダクト

- Cloud SQL
- Cloud Logging
- Secret Manager

## 構築の流れ

1. Rails アプリ作成
1. Cloud SQL と接続する
1. Secret Manager を利用する
1. Cloud Logging を利用する
1. 手動でビルド & デプロイ
1. Cloud Build と GitHub を連携する

## Rails アプリ作成

ここは前回とほぼ同じなので、説明は割愛します。

```
# Rails アプリ作成. データベースは MySQL にする
$ rails new sample-app -d mysql
$ cd sample-app

# scaffold でページ作成
$ bin/rails g scaffold Post title:string content:text
```

Dockerfileを作ります。
master.keyを外部から注入できるようにしておきます。

```
$ touch Dockerfile
```

```dockerfile:Dockerfile
FROM ruby:3.1.2
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp
ARG MASTER_KEY
ENV RAILS_MASTER_KEY=${MASTER_KEY}
# Cloud Run はデフォルトポート8080で起動
EXPOSE 8080
CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "8080"]
```

Cloud Build の設定ファイル cloudbuild.yaml を作成します。
今回はBuildのタイミングで`db:migrate`も行います。
また Cloud Run へのデプロイも設定します。

```
$ touch cloudbuild.yaml
```

```yaml:cloudbuild.yaml
steps:
# Cloud Build でビルド
- id: "build image"
  name: "gcr.io/cloud-builders/docker"
  entrypoint: 'bash'
  args:
  - -c
  - |
    docker build \
      -t gcr.io/${PROJECT_ID}/${_SERVICE_NAME} \
      . \
      --build-arg MASTER_KEY=$$MASTER_KEY
  secretEnv:
  - MASTER_KEY
# Container Registry にアップロード
- id: "push image"
  name: "gcr.io/cloud-builders/docker"
  args:
  - push
  - gcr.io/${PROJECT_ID}/${_SERVICE_NAME}
# db:migrate
- id: "apply migrations"
  name: "gcr.io/google-appengine/exec-wrapper"
  entrypoint: "bash"
  args:
  - -c
  - |
    /buildstep/execute.sh \
      -i gcr.io/${PROJECT_ID}/${_SERVICE_NAME}\
      -s ${PROJECT_ID}:${_REGION}:${_INSTANCE_NAME} \
      -e MASTER_KEY=$$MASTER_KEY \
      -- bundle exec rails db:migrate
# Cloud Run デプロイ
- id: 'deploy'
  name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: 'bash'
  args:
  - -c
  - |
    gcloud run deploy $_SERVICE_NAME \
      --image gcr.io/${PROJECT_ID}/${_SERVICE_NAME} \
      --region asia-northeast1 \
      --add-cloudsql-instances ${PROJECT_ID}:${_REGION}:${_INSTANCE_NAME} \
      --allow-unauthenticated
substitutions:
  _SERVICE_NAME: sample-app-service
  _REGION: asia-northeast1
  _INSTANCE_NAME: sample-app-db-instance
availableSecrets:
  secretManager:
  - versionName: projects/$PROJECT_ID/secrets/MASTER_KEY/versions/latest
    env: MASTER_KEY
images:
  - "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"
timeout: 600s
```

## Cloud SQL と接続する

Cloud SQL を構築していきます。
まずインスタンス、データベース、ユーザーの作成をします。

```
# DBのインスタンス作成
$ gcloud sql instances create sample-app-db-instance \
    --database-version MYSQL_5_7 \
    --tier db-f1-micro \
    --region asia-northeast1

# データベース作成
$ gcloud sql databases create sample-app-db \
  --instance sample-app-db-instance

# ユーザー作成
$ gcloud sql users create app_user \
  --instance=sample-app-db-instance --password=password
```

ビルド時に `db:migrate` を実行するため、Cloud Build のサービスアカウントに Cloud SQL に接続するためのロールを設定します。
Cloud Build はデフォルトでサービスアカウントが作られていますので、Webコンソールの`IAMと管理`ページを見てサービスアカウント名（`[XXXXXXXXXX]@cloudbuild.gserviceaccount.com`）を確認してください。

```
# サービスアカウントにロール追加
$ gcloud projects add-iam-policy-binding [PROJECT_ID] \
  --member serviceAccount:[XXXXXXXXXX]@cloudbuild.gserviceaccount.com \
  --role roles/cloudsql.client
```

Cloud Run のデプロイもできるようにロール設定します。
Cloud Run もデフォルトのサービスアカウントがあるため、Webコンソールの`IAMと管理`ページを見てサービスアカウント名（`[XXXXXXXXXX]-compute@developer.gserviceaccount.com`）を確認してください。

```
# cloud build -> cloud run
gcloud projects add-iam-policy-binding [PROJECT_ID] \
  --member serviceAccount:[XXXXXXXXXX]@cloudbuild.gserviceaccount.com \
  --role roles/run.developer

# cloud build -> cloud run
gcloud projects add-iam-policy-binding [PROJECT_ID] \
  --member serviceAccount:[XXXXXXXXXX]-compute@developer.gserviceaccount.com \
  --role roles/iam.serviceAccountUser
```

最後にアプリ側のデータベース設定をします。
先ほど作成したユーザー、パスワード、データベースを設定します。
socket名もデフォルトでは決まっています（　`/cloudsql/[PROJECT_ID]/[REGION]/[INSTANCE_NAME]`）

```yaml:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: app_user
  password: password
  host: localhost
  socket: /cloudsql/[PROJECT_ID]:asia-northeast1:sample-app-db-instance

development:
  <<: *default
  database: sample-app-db
...
```

## Secret Manager を利用する

環境変数に設定するような秘匿情報を Secret Manager で管理します。
Cloud Build, Cloud Run どちらからもアクセスが可能です。
特に Cloud Run の Railsアプリからアクセスするときは、コードを変更することなく `ENV('xxx')`で参照することができます。

`master.key`のシークレットを作成します。
```
# シークレットを作成
$ gcloud secrets create MASTER_KEY \
  --data-file config/master.key
```

Cloud Buildからアクセスできるようにロールを設定します。
```
# サービスアカウントにロール追加
$ gcloud projects add-iam-policy-binding [PROJECT_ID] \
  --member serviceAccount:[XXXXXXXXXX]-compute@developer.gserviceaccount.com \
  --role roles/secretmanager.secretAccessor
```

Cloud Run からアクセスできるようにロールを設定します。
```
# サービスアカウントにロール追加
$ gcloud projects add-iam-policy-binding [PROJECT_ID] \
  --member serviceAccount:[XXXXXXXXXX]@cloudbuild.gserviceaccount.com \
  --role roles/secretmanager.secretAccessor
```

## Cloud Logging を利用する

Cloud Run でアプリを動かすとデフォルトで Cloud Logging にログを出力するようになっています。そのため、特に設定することはないです（便利）
Google Cloud のWebコンソールから Cloud Logging のページを見て、ログが出力されていることを確認してください。

## ビルド & デプロイ

ここまでを手動でビルド & デプロイして実行を動作を確認します。

```
# ビルド、アップロード、デプロイ
$ gcloud builds submit .
```

## Cloud Build と GitHub を連携する

最後に GitHub に pushした タイミングでビルド & デプロイを実行するようにします。
Cloud Build のトリガー機能を使えば簡単に実現できます。

Cloud Build GitHub アプリを選択して、GitHub アカウントを選べばリポジトリが連携されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322836/f714708f-e482-0337-52a4-db7920fcb63d.png)

あとはイベントを設定して、ブランチに push したときに、Cloud Build が実行されれば設定完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322836/61c3760d-f9fe-ec7d-f733-6d21e0d8c522.png)

## まとめ

今回は以下の構成で構築しました。
![Untitled-2022-11-29-2139.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322836/c701f2fa-0e28-f558-833c-3212ce6a2d96.png)
小さなアプリであれば、十分運用できると思います。
他にも画像ファイルの保存先に Cloud Storage を使ったり、複数のアプリサーバーを使うために Cloud Load Balancing を使ったりできるので、他の構成も試していきたいと思います。