前回M1 MacでGCP Cloud RunのQuickstart(ruby版)試したので詰まったポイントをメモで、sinatraアプリをCloud Runにデプロイした。
次はRailsを試してみる。
- GCP Cloud Runに
- Ruby v3.0.3で Rails 6のアプリを
- M1 Macの環境から
デプロイしてみる。
ちょうどRails6を利用したチュートリアルが用意されているので、それをやっていく。
準備
今回実験に使うプロジェクトをgcloudのデフォルトプロジェクトとして設定したい。
プロジェクトIDを探しにブラウザを動かすのがめんどくさい場合、CLIでも探すことができる。
$ gcloud projects list
PROJECT_ID NAME PROJECT_NUMBER
test-project-41231412 Test Project 412314......
その上で、見つけたプロジェクトIDをデフォルトプロジェクトに設定する。
# この例ではプロジェクトIDはrails-cloud-run-test
$ gcloud config set project rails-cloud-run-test
Railsのサンプルアプリの取得
$ git clone https://github.com/GoogleCloudPlatform/ruby-docs-samples.git
$ cd ruby-docs-samples/run/rails
$ bundle install
# 前回の学びで、Gemfile.lockにx86 platformを追加しないとCloud Buildのビルド時にコケるため、対応
$ bundle lock --add-platform x86_64-linux
bundle installでsasscのインストールエラー
bundle install
中、sassc
のインストールでエラーが出る。
An error occurred while installing sassc (2.1.0), and Bundler cannot continue.
In Gemfile:
sass-rails was resolved to 6.0.0, which depends on
sassc-rails was resolved to 2.1.2, which depends on
sassc
どこで落ちてるか。もう少し上のほうのログを確認する。
compiling ./libsass/src/ast.cpp
clang: error: the clang compiler does not support '-march=native'
make: *** [ast.o] Error 1
make failed, exit code 2
解決策
解決策はここに書かれていた。
要するに以下を実行すればよい。
$ gem install sassc -- --disable-march-tune-native
ただ、せっかくGemfileがあるのに別個にgem installするのは面倒である。
これをbundleで実行する方法は以下。
$ bundle config build.sassc --disable-march-tune-native
これを実行後、bundle install
するとすんなり通る。
DB(CloudSQLインスタンス)の作成
# インスタンス名はrails-cloud-run-devとする
$ gcloud sql instances create rails-cloud-run-dev \
--database-version POSTGRES_14 \
--tier db-f1-micro \
--region asia-northeast1
インスタンス作成には結構時間がかかる。上記コマンドは12分完了までに必要だった。
CloudSQLインスタンス内へのデータベースの作成
# インスタンス名はrails-cloud-run-dev
# データベース名はmain
$ gcloud sql databases create main --instance rails-cloud-run-dev
CloudSQLインスタンスへ接続できるユーザーの作成
# パスワードを生成し、dbpasswordというファイルに書き込む
$ cat /dev/urandom | LC_ALL=C tr -dc '[:alpha:]'| fold -w 50 | head -n1 > dbpassword
# インスタンス名はrails-cloud-run-dev
# ユーザー名はdevuser
$ gcloud sql users create devuser \
--instance=rails-cloud-run-dev --password=$(cat dbpassword)
Cloud Storageバケットの作成
gsutil
コマンドを使う。
gsutil は、コマンドラインから Cloud Storage にアクセスできる Python アプリケーションです。
awscliでいうaws s3と同じか。GCPではCloud Storageに特化したコマンドとしてgsutilがあるらしい。
# 最後の引数はgs://[PROJECT_ID]-[MEDIA_BUCKET]
# この例のPROJECT_IDはrails-cloud-run-test
# MEDIA_BUCKETはimage-storage
$ gsutil mb -l asia-northeast1 gs://rails-cloud-run-test-image-storage
サブコマンドのmb
はMake buckets
の略。
このバケットへは、誰でも見られる画像ファイルを配置する予定のため、バケット内を閲覧可能にする。
$ gsutil iam ch allUsers:objectViewer gs://rails-cloud-run-test-image-storage
ch
は change
の略。gsutil iamはサブコマンドとしてget
, set
, ch
を持つ。
Secret Managerにシークレット値を保存する
$ bin/rails credentials:edit
bin/rails credentials:editでbootsnapのエラー発生
上記コマンドを実行すると以下のエラーが出た。
/Users/ryan5500/.anyenv/envs/rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/bootsnap-1.7.5/lib/bootsnap/compile_cache/iseq.rb:13:in `to_binary': wrong argument type false (expected Symbol) (TypeError)
Ruby3.0.3で発生するバグらしく、bootsnapの1.9.2で修正されている。
解決策
Gemfileの該当箇所を修正。
gem "bootsnap", ">= 1.9.2", require: false
$ bundle update bootsnap
これで解決する。
Secret Managerにsecretを登録する。
# RAILS_SECRET_NAMEはここではrails-secret
$ gcloud secrets create rails-secret --data-file config/master.key
Cloud Runサービスにsecretへのアクセス権を付与する。
# RAILS_SECRET_NAMEはここではrails-secret
# ここでのプロジェクト番号は565343841115
$ gcloud secrets add-iam-policy-binding rails-secret \
--member serviceAccount:565343841115-compute@developer.gserviceaccount.com \
--role roles/secretmanager.secretAccessor
Cloud Buildサービスアカウントにシークレットへのアクセス権を付与する
# RAILS_SECRET_NAMEはここではrails-secret
# ここでのプロジェクト番号は565343841115
$ gcloud secrets add-iam-policy-binding rails-secret \
--member serviceAccount:565343841115@cloudbuild.gserviceaccount.com \
--role roles/secretmanager.secretAccessor
Rails アプリを本番環境のデータベースとストレージに接続する
.envを作成する。
PRODUCTION_DB_NAME: main
PRODUCTION_DB_USERNAME: devuser
CLOUD_SQL_CONNECTION_NAME: rails-cloud-run-test:asia-northeast1:rails-cloud-run-dev
GOOGLE_PROJECT_ID: rails-cloud-run-test
STORAGE_BUCKET_NAME: rails-cloud-run-test-image-storage
Cloud BuildにCloud SQLへのアクセス権を付与する
これは、Cloud Buildでアプリケーションイメージをビルドした後、DBマイグレーションを実行するので、予めCloud BuildにCloud SQLへのアクセス権を付与している。
# この例ではプロジェクトIDはrails-cloud-run-test
# ここでのプロジェクト番号は565343841115
$ gcloud projects add-iam-policy-binding rails-cloud-run-test \
--member serviceAccount:565343841115@cloudbuild.gserviceaccount.com \
--role roles/cloudsql.client
Cloud Buildで何を実行するかの指定は、gcloud builds submit
する際に設定ファイルを指定できて、今回の例では、railsのフォルダ内のcloudbuild.yml
にて行っている。
Cloud Buildでビルドを行う
以下で、cloudbuild.ymlを使ってビルドを依頼する。
# 今回の例ではSERVICE_NAMEはrails-test-service
# INSTANCE_NAMEは、上で決めたrails-cloud-run-dev
# REGIONは、asia-northeast1
# RAILS_SECRET_NAMEは、rails-secret
$ gcloud builds submit --config cloudbuild.yaml \
--substitutions _SERVICE_NAME=rails-test-service,_INSTANCE_NAME=rails-cloud-run-dev,_REGION=asia-northeast1,_SECRET_NAME=rails-secret
このコマンドを打つと、何かアップロードしているログが出る。
CloudBuildのログやストレージの内容を見る限り、以下の処理を行っている。
-
gcloud builds submit
を実行しているパスのファイル群をtarファイルにまとめ、Cloud Buildの実行用に作成されたCloud Storageにアップロードしている。 - Cloud Buildの実行用に作成されたCloud Storageから、tgzファイルを取得し、cloudbuild.ymlで指定されたdocker build等のstepを順次実行している。
ビルドされたRailsアプリイメージをCloud Runへデプロイする
$ gcloud run deploy rails-test-service \
--platform managed \
--region asia-northeast1 \
--image gcr.io/rails-cloud-run-test/rails-test-service \
--add-cloudsql-instances rails-cloud-run-test:asia-northeast1:rails-cloud-run-dev \
--allow-unauthenticated
デプロイ完了後、アクセスできるURLが表示される。
URLにアクセスしたところ、レスポンスが戻るのに5秒ぐらいはかかる。(初回起動かつRailsであるため)
前回Sinatraだと3秒程度だったので、倍ほどかかる。
気になった点
database.ymlのhost設定が謎
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"] %>"
hostが/cloudsql/rails-cloud-run-test:asia-northeast1:rails-cloud-run-dev
これになるのだが、どうやって接続しているのか。
adapterがmysqlだとsocketという設定値でソケットを使うが、adapterがpostgresqlだとhostの設定値でソケットを指定できるらしい。つまりhostの値はパスである。
ここにも記載があった。
アプリは、Cloud Run(フルマネージド)で実行されると、Cloud Run 環境から提供されるソケットを使用して PostgreSQL インスタンスに接続されます。アプリは、ローカルマシンで実行されると、Cloud SQL Auth プロキシを使用して PostgreSQL インスタンスに接続されます。