【Ruby on Rails】CloudRunでRailsアプリをデプロイしてみる【GoogleCloudSkillsBoost】

CI/CD

はじめに

今回は、GoogleCloudのSkillsBoostを用いて、CloudRunでのRailsアプリをデプロイしてみたいと思います。

手順は以下の公式リンクをご参照ください。クレジットを消費してラボ環境を使用するかご自身のGoogleCloudアカウントで検証することができます。

https://www.cloudskillsboost.google/focuses/20049?catalog_rank={“rank”%3A1%2C”num_filters”%3A0%2C”has_search”%3Atrue}&parent=catalog&search_id=40370250

構成

以下の通りの構成となっており、ラボを進めることで、専用環境で構築することができるようです。

Rails サイトは Cloud Run から提供され、Cloud Run は複数のバッキング サービスを使用してさまざまなデータタイプ(リレーショナル データベース情報、メディア アセット、構成シークレット、コンテナ イメージ)を保存します。バックエンド サービスは、ビルドと移行タスクの一部として Cloud Build によって更新されます。

検証の流れ

  1. 環境構築
  2. DB,ストレージを準備
  3. シークレットを準備
  4. RailsアプリをDBとストレージに接続
  5. CloudBuildにDBへのアクセス権を付与
  6. CloudRunへのデプロイ

1.環境構築

今回はローカル環境で進めるので、任意のエディタとターミナルを開いて進めていきます。

GoogleCloud SkillsBoostのラボ環境かご自身のGoogleCloudアカウントに接続して、CloudShellを開くことでも同様に進めることができます。

以下のコマンドでアカウントのプロジェクトの認証を済ませておきます。

gcloud auth list
gcloud config list project

指定のRailsアプリをgit cloneします。

リポジトにルートに移動してGemfileを読み込みます。

cd ruby-docs-samples/run/rails
bundle install

2.DBとストレージを準備

次にCloudSQLを作成していきます。

# set env
INSTANCE_NAME=postgres-instance
DATABASE_NAME=mydatabase
REGION=asia-northeast1
# create cloudsql instance
gcloud sql instances create $INSTANCE_NAME \\
  --database-version POSTGRES_12 \\
  --tier db-g1-small \\
  --region $REGION
# create databese
$ gcloud sql databases create $DATABASE_NAME \\
  --instance $INSTANCE_NAME
Creating Cloud SQL database...done.
Created database [mydatabase].
instance: postgres-instance
name: mydatabase
project: yourproject
# create password
cat /dev/urandom | LC_ALL=C tr -dc '[:alpha:]'| fold -w 50 | head -n1 > dbpassword
# configuration password
gcloud sql users create qwiklabs_user \\
  --instance=$INSTANCE_NAME --password=$(cat dbpassword)

続いて、CloudStorageを作成していきます。

DEVSHELL_PROJECT_ID=yourproject
BUCKET_NAME=$DEVSHELL_PROJECT_ID-ruby
gsutil mb -l asia-northeast1 gs://$BUCKET_NAME
Creating gs://yourproject-ruby/...
# chenga iam
gsutil iam ch allUsers:objectViewer gs://$BUCKET_NAME

3.シークレットを準備

Railsでは、認証情報(データベースのパスワードやAPIキーなど)を config/credentials.yml.enc に暗号化して保存します。この暗号化を解除するためのキー(config/master.key または ENV["RAILS_MASTER_KEY"])は、Google Cloudの Secret Manager に安全に保管するのがおすすめです。

config/credentials.yml.enc

config/master.keyENV[“RAILS_MASTER_KEY”]

Cloud RunやCloud Buildがこのキーにアクセスできるようにするには、それぞれのサービスアカウントにSecret Managerのアクセス権を付与します。サービスアカウントはプロジェクト番号を含むメールアドレス形式で識別されます。

以下のコマンドで、クレデンシャル用のファイルを生成します。

このコマンドは、config/master.key が未定義の場合に新しいキーを作成し、config/credentials.yml.enc が存在しない場合にはファイルを生成します。その後、設定された $EDITOR で一時ファイルが開き、復号化された認証情報を編集できます。

$ EDITOR="nano" bin/rails credentials:edit
Adding config/master.key to store the encryption key: hogehoge

Save this in a password manager your team can access.

If you lose the key, no one, including you, can access anything encrypted with it.

      create  config/master.key

File encrypted and saved.

別のターミナルを開いて、以下のコマンドで、dbpasswordが表示されるのでコピーしてnanoエディタの方に貼り付けます。

cat ~/dev/ruby-docs-samples/run/rails/dbpassword

nanoで開いた資格情報ファイルが以下の様になります。

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: hogehoge
gcp:
  db_password: fugafuga

次のコマンドでSecretManagerを作成して、config/master.keyの内容を書き込みます。

$ gcloud secrets create rails_secret --data-file config/master.key
API [secretmanager.googleapis.com] not enabled on project [yourproject].
Would you like to enable and retry (this will take a few minutes)?
(y/N)?  y

Enabling service [secretmanager.googleapis.com] on project [yourproject]...
Operation "operations/acat.p2-yourprojectid-a01e037a-baa5-4080-a73e-e240c145fa4d" finished successfully.
Created version [1] of the secret [rails_secret].
# check secret value
$ gcloud secrets describe rails_secret
createTime: '2024-12-28T13:32:15.612937Z'
etag: '"162a549b8d5009"'
name: projects/yourprojectid/secrets/rails_secret
replication:
  automatic: {}
$ gcloud secrets versions access latest --secret rails_secret
hogehoge

プロジェクト番号を取得してシークレットへのアクセス権をCloudRunサービスアカウントに付与します。

また、CloudBuildを実行するcomputeのサービスアカウントにも付与します

# get google cloud project number
$ PROJECT_NUMBER=$(gcloud projects describe $DEVSHELL_PROJECT_ID --format='value(projectNumber)')
# check got number
$ echo $PROJECT_NUMBER
# add permission 
$ gcloud secrets add-iam-policy-binding rails_secret \\
  --member <serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com> \\
  --role roles/secretmanager.secretAccessor
Updated IAM policy for secret [rails_secret].
bindings:
- members:
  - <serviceAccount:yourprojectid-compute@developer.gserviceaccount.com>
  role: roles/secretmanager.secretAccessor
etag: BwYqVLXK9zY=
version: 1
$ gcloud secrets add-iam-policy-binding rails_secret \\
   --member <serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com> \\
   --role roles/secretmanager.secretAccessor
Updated IAM policy for secret [rails_secret].
bindings:
- members:
  - <serviceAccount:yourprojectid-compute@developer.gserviceaccount.com>
  role: roles/secretmanager.secretAccessor
etag: BwYqVbjeyH0=
version: 1

4.RailsアプリをDBとストレージに接続

PostgreSQLを本番データベース、Cloud Storageをストレージとして使用します。

Railsがこれらに接続するために必要な情報は、環境変数ファイル(.env)に指定します。

.env ファイルにはアプリケーションの設定が含まれ、dotenv gem を使って読み取られます。

機密情報はSecret Managerに保存されているため、.env ファイル自体を暗号化する必要はありません。

$ cat .env
PRODUCTION_DB_NAME: [YOUR_CLOUD_SQL_DB_NAME]
PRODUCTION_DB_USERNAME: [YOUR_CLOUD_SQL_USERNAME]
CLOUD_SQL_CONNECTION_NAME: [YOUR_INSTANCE_CONNECTION_NAME]
GOOGLE_PROJECT_ID: [YOUR_GOOGLE_PROJECT_ID]
STORAGE_BUCKET_NAME: [YOUR_CLOUD_STORAGE_BUCKET_NAME]
$ cat << EOF > .env
PRODUCTION_DB_NAME: $DATABASE_NAME
PRODUCTION_DB_USERNAME: qwiklabs_user
CLOUD_SQL_CONNECTION_NAME: $DEVSHELL_PROJECT_ID:$REGION:$INSTANCE_NAME
GOOGLE_PROJECT_ID: $DEVSHELL_PROJECT_ID
STORAGE_BUCKET_NAME: $BUCKET_NAME
EOF
$ cat .env

5.CloudBuildにDBへのアクセス権を付与

CloudBuildでデータベースのマイグレートを実行するために、CloudSQLへのアクセス権限を付与します。

$ gcloud projects add-iam-policy-binding $DEVSHELL_PROJECT_ID \\
    --member <serviceAccount:$PROJECT_NUMBER@cloudbuild.gserviceaccount.com> \\
    --role roles/cloudsql.client
Updated IAM policy for project [yourproject].
bindings:
- members:
  - <serviceAccount:yourprojectid@cloudbuild.gserviceaccount.com>
  role: roles/cloudbuild.builds.builder
- members:
  - <serviceAccount:service-yourprojectid@gcp-sa-cloudbuild.iam.gserviceaccount.com>
  role: roles/cloudbuild.serviceAgent
- members:
  - <user:youremail@gmail.com>
  role: roles/cloudsql.admin
- members:
  - <serviceAccount:yourprojectid@cloudbuild.gserviceaccount.com>
  role: roles/cloudsql.client
- members:
  - <serviceAccount:service-yourprojectid@compute-system.iam.gserviceaccount.com>
  role: roles/compute.serviceAgent
- members:
  - <serviceAccount:service-yourprojectid@containerregistry.iam.gserviceaccount.com>
  role: roles/containerregistry.ServiceAgent
- members:
  - <serviceAccount:yourprojectid-compute@developer.gserviceaccount.com>
  - <serviceAccount:yourprojectid@cloudservices.gserviceaccount.com>
  role: roles/editor
- members:
  - <user:youremail@gmail.com>
  role: roles/owner
- members:
  - <serviceAccount:service-yourprojectid@gcp-sa-pubsub.iam.gserviceaccount.com>
  role: roles/pubsub.serviceAgent
- members:
  - <serviceAccount:service-yourprojectid@serverless-robot-prod.iam.gserviceaccount.com>
  role: roles/run.serviceAgent
etag: BwYqVj327mI=
version: 1

6.CloudRunへのデプロイ

いよいよアプリをCloudRunでデプロイしてみます。

まずは、Dockerfileのrubyのバージョン変更から行っていき、イメージのビルド、CloudRunへのデプロイと進めていきます。

以下がコマンドと出力結果です。

適宜、コンソールでも確認していきます。

# edit ruby version for Dockerfile
RUBY_VERSION=$(ruby -v | cut -d ' ' -f2 | cut -c1-3)
sed -i "/FROM/c\\FROM ruby:$RUBY_VERSION-buster" Dockerfile
# start build
$ APP_NAME=myrubyapp
$ gcloud builds submit --config cloudbuild.yaml \\
      --substitutions _SERVICE_NAME=$APP_NAME,_INSTANCE_NAME=$INSTANCE_NAME,_REGION=$REGION,_SECRET_NAME=rails_secret --timeout=20m
Creating temporary archive of 104 file(s) totalling 111.2 KiB before compression.
Some files were not included in the source upload.

Check the gcloud log [/Users/kosments/.config/gcloud/logs/2024.12.29/12.50.22.850507.log] to see which files and the contents of the
default gcloudignore file used (see `$ gcloud topic gcloudignore` to learn
more).

Uploading tarball of [.] to [gs://yourproject_cloudbuild/source/1735444223.235925-07a6e1f1402a44cea136c2c636dff5de.tgz]
Created [<https://cloudbuild.googleapis.com/v1/projects/yourproject/locations/global/builds/882325f3-1455-4214-aac9-4952bcef79ee>].
Logs are available at [ <https://console.cloud.google.com/cloud-build/builds/882325f3-1455-4214-aac9-4952bcef79ee?project=yourprojectid> ].
------------------------- REMOTE BUILD OUTPUT --------------------------
starting build "882325f3-1455-4214-aac9-4952bcef79ee"

FETCHSOURCE
Fetching storage object: gs://yourproject_cloudbuild/source/1735444223.235925-07a6e1f1402a44cea136c2c636dff5de.tgz#1735444224287455
Copying gs://yourproject_cloudbuild/source/1735444223.235925-07a6e1f1402a44cea136c2c636dff5de.tgz#1735444224287455...
/ [0 files][    0.0 B/ 47.7 KiB]                                        / [1 files][ 47.7 KiB/ 47.7 KiB]
Operation completed over 1 objects/47.7 KiB.
BUILD
Starting Step #0 - "build image"
Step #0 - "build image": Already have image (with digest): gcr.io/cloud-builders/docker
Step #0 - "build image": Sending build context to Docker daemon    215kB
Step #0 - "build image": Step 1/15 : FROM ruby:2.7-buster
Step #0 - "build image": 2.7-buster: Pulling from library/ruby
Step #0 - "build image": a94073ab46f8: Pulling fs layer
***省略***
f584c095e67e: Layer already exists
latest: digest: sha256:9471942d671b02a92095a541ad3aaf1f8a3848891997c06814e311dee5a612d6 size: 3264
DONE
------------------------------------------------------------------------
ID                                    CREATE_TIME                DURATION  SOURCE                                                                                    IMAGES                                 STATUS
882325f3-1455-4214-aac9-4952bcef79ee  2024-12-29T03:50:25+00:00  6M9S      gs://yourproject_cloudbuild/source/1735444223.235925-07a6e1f1402a44cea136c2c636dff5de.tgz  gcr.io/yourproject/myrubyapp (+1 more)  SUCCESS
# deploy app
$ gcloud run deploy $APP_NAME \\
     --platform managed \\
     --region $REGION \\
     --image gcr.io/$DEVSHELL_PROJECT_ID/$APP_NAME \\
     --add-cloudsql-instances $DEVSHELL_PROJECT_ID:$REGION:$INSTANCE_NAME \\
     --allow-unauthenticated \\
     --max-instances=3
Deploying container to Cloud Run service [myrubyapp] in project [yourproject] region [asia-northeast1]
✓ Deploying new service... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [myrubyapp] revision [myrubyapp-00001-2j6] has been deployed and is serving 100 percent of traffic.
Service URL: <https://myrubyapp-4uwkzqbxua-an.a.run.app>

Updates are available for some Google Cloud CLI components.  To install them,
please run:
  $ gcloud components update

gcloud buildを実行すると、ビルドイメージが作成されます。

CloudBuildコンソールでも確認できます。

最後にgcloud run deployを使用して、サンプルRubyアプリケーションをCloudRunにデプロイしました。

CloudRunコンソールでも問題なくアプリケーションがデプロイできたことがわかります。

CloudRunの対象サービスを開き、サービスURLを選択すると別タブでアプリケーションが開きます。

サンプルアプリケーションの画像投稿アプリが表示されました。

まとめ

今回は、SkillsBoostでCloudRunへのデプロイを試してみました。

概要

  • Railsアプリの作成: 標準的なRailsコマンドでcat_albumアプリを作成し、Photoモデル、コントローラー、ビューを生成。
  • データベース接続:
    • config/database.yml: 各環境(開発、テスト、本番)のデータベース接続設定。
    • 本番環境: Cloud SQL for PostgreSQLに接続。
      • Cloud SQL: Cloud SQL for PostgreSQLインスタンスを利用。
      • 接続方法はCloud Run環境かローカルマシンかによって異なる。
      • Cloud Run: Cloud Run環境が提供するソケット経由
      • ローカルマシン: Cloud SQL Authプロキシ経由
      • db/migrateディレクトリ内の既存のマイグレーションが適用される。
# run/rails/config/database.yml
production:
  <<: *default
  database: <%= ENV["PRODUCTION_DB_NAME"] %>
  username: <%= ENV["PRODUCTION_DB_USERNAME"] %>
  password: <%= Rails.application.credentials.gcp[:db_password] %>
  host: "<%= ENV.fetch("DB_SOCKET_DIR") { '/cloudsql' } %>/<%= ENV["CLOUD_SQL_CONNECTION_NAME"] %>"
  • ファイルのアップロード:
    • Google Cloud Storage: Active Storageを使ってGoogle Cloud Storageにファイルを保存。
      • config/storage.ymlconfig/environments/production.rbrun/rails/config/storage.yml でCloudStorageを使用したファイルアップロード機能を実現する。
# run/rails/config/storage.yml
config.active_storage.service = :google
# config/storage.ymlconfig/environments/production.rb
google:
  service: GCS
  project: <%= ENV["GOOGLE_PROJECT_ID"] %>
  bucket: <%= ENV["STORAGE_BUCKET_NAME"] %>
  • 自動化:
    • Cloud Build: イメージのビルド、プッシュ、移行を自動化。cloudbuild.yamlで詳細設定。
      • イメージのビルド、コンテナレジストリへのプッシュ、Railsデータベースの移行を実行。
      • cloudbuild.yamlで置換変数を利用し、柔軟な設定が可能。
      • Cloud SQL Authプロキシのヘルパーapp-engine-exec-wrapperを使用してデータベースにアクセス。
    • Secret Manager: Secret ManagerからRAILS_MASTER_KEYシークレットを取得して、データベースの移行時に使用する。
# cloudbuild.yaml
steps:
  - id: "build image"
    name: "gcr.io/cloud-builders/docker"
    entrypoint: 'bash'
    args: ["-c", "docker build --build-arg MASTER_KEY=$$RAILS_KEY -t gcr.io/${PROJECT_ID}/${_SERVICE_NAME} . "]
    secretEnv: ["RAILS_KEY"]

  - id: "push image"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"]

  - 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 RAILS_MASTER_KEY=$$RAILS_KEY -- bundle exec rails db:migrate"
      ]
    secretEnv: ["RAILS_KEY"]

substitutions:
  _REGION: Region
  _SERVICE_NAME: rails-cat-album
  _INSTANCE_NAME: cat-album
  _SECRET_NAME: rails-master-key

availableSecrets:
  secretManager:
  - versionName: projects/${PROJECT_ID}/secrets/${_SECRET_NAME}/versions/latest
    env: RAILS_KEY

images:
  - "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"
  • Dockerfile:
    • run/rails/DockerfileにてRAILS_MASTER_KEY環境変数を設定。
  • 継続的デプロイ:
    • Cloud Buildを利用して、gitからの継続的デプロイが可能。

公式でも解説されていますが、上記の点が実際にRailsアプリケーションをCloudRunで実行する際のポイントとなると思いますので確認してみました。

引き続きSkillsBoostで学習を進めていきます!

事後処理

最後にリソースの削除をお忘れなく!

# delete run servise
$ gcloud run services delete myrubyapp
# check deleted
$ gcloud run services list
Listed 0 items.
# delete sql instance
$ gcloud sql instances delete postgres-instance
# check deleted
$ gcloud sql instances list
Listed 0 items.
# delete secrets
$ gcloud secrets delete projects/<プロジェクトID>/secrets/rails_secret
# check deleted
$ gcloud secrets list
Listed 0 items.
# delete buckets
gcloud storage rm --recursive gs://yourproject-ruby
gcloud storage rm --recursive gs://yourproject_cloudbuild
# chek deleted
gsutil ls -b

コメント

タイトルとURLをコピーしました