ひよっこエンジニアの雑多な日記

なんとかWeb系のエンジニアをやっています。

Cloud Runを使ってRailsアプリを公開する【公式チュートリアルをなぞるよ!】

GCPのCloud RunでRailsアプリを立ち上げるチュートリアルをベースにRailsアプリを立ち上げを実践していってみます。

cloud.google.com

Cloud Runのデプロイ設定ができればコンテナの設定だけすればWebアプリの外部公開がだいぶ手軽になると思うのでまとめてみます。

今回はGUIのコンソールを使わずgcloudコマンドを使って各種サービスのインスタンスを作成したりしていきます。

サンプルアプリケーション

デプロイするサンプルアプリケーションを用意しました。

github.com

最初にDockerfileやdocker-compose.ymlなどの設定は実施されていることを前提としていきます。

Cloud SQLの準備

インスタンスの作成

まずはCloud SQLインスタンスを用意します。

今回はMySQLインスタンスを作りたいのでチュートリアルのコマンドとは --database-version の設定は MYSQL_8_0 を使用したいと思います。

リージョンは asia-northeast1インスタンス名は cloudrun-app-sample-db-instance とします。

インスタンス名は任意の好きな値にしましょう。

gcloud sql instances create cloudrun-app-sample-db-instance \
    --database-version MYSQL_8_0 \
    --tier db-f1-micro \
    --region asia-northeast1

データベースの作成

次は作成したインスタンスにデータベースを作成します。

cloudrun-app-sample-production はデータベース名なので任意の名前を記載します。

また --instanceインスタンスの作成で作成したインスタンス名を設定しましょう。

gcloud sql databases create cloudrun-app-sample-production --instance cloudrun-app-sample-db-instance

※ 認証関連でエラーが出るかもしれませんが、GCPのコンソールでCloudSQLにインスタンスが作成されれば問題なくインスタンス作成はされています。

ユーザーの作成

データベースを作成したら、データベースにアクセスするためのユーザーを作成します。

system_user はユーザー名なので任意の名前を記載します。

データベースの作成と同様に --instance は作成したインスタンスを指定します。

--password はパスワードジェネレータなどでパスワードを作成して任意の値を設定します。

gcloud sql users create system_user \
   --instance=cloudrun-app-sample-db-instance --password=hogehoge

ここまででCloudSQLの準備は完了です。

RailsプロジェクトでDBパスワードを暗号化する

ユーザー作成時に設定したパスワードをRailsのcredentials.yml.secを使って暗号化してプロジェクトに設定をしておきます。

docker compose run --rm app ash

docker-composeでアプリをコンテナで立ち上げられる前提で、docker compose runでappコンテナを起動しashでアクセスします。

EDITOR="vi" bin/rails credentials:edit

コンテナ内でcredentialを編集するコマンドを実行します。

viでエディタが起動するので以下を追記します

gcp:                                                                                              
  db_password: mysqlのユーザー作成時に設定したパスワード

追記したら保存してconfig/database.ymlのproductionの設定を更新します。

production:
  <<: *default
  database: cloudrun-app-sample-production # 作成したDBの名前
  username: system_user # 作成したユーザーの名前
  password: <%= Rails.application.credentials.gcp[:db_passsword] %>
  # socket: 後ほど追加する

ここまでできたらDBに関する設定は一旦終了です。

ちなみにsocketの部分はPostgreSQLではhostで指定するのですが、MySQLの場合はsocketとなるので注意が必要です。

Secret Managerでmaster.keyを管理

パスワードなどの秘匿情報を管理するためにSecret Managerを使います。

シークレットの作成

今回はRailsのcredentials.yml.secを利用するためmaster.keyをSecret Managerにて管理します。

gcloud secrets create sample-app-rails-master-key --data-file config/master.key

今回シークレットの名前はサンプルアプリのmaster.keyでわかるように sample-app-rails-master-key という名前にしました。(こちらも任意の名前でOKです)

--data-file でconfig/master.keyを指定することでmaster.keyに書かれている暗号鍵をシークレットに登録できます。

※ このコマンドは作成したRailsアプリのプロジェクト直下で実行しています。

作成したシークレットの確認

以下のコマンドで登録されたシークレットの値を確認することができます。

gcloud secrets versions access latest --secret sample-app-rails-master-key

--secret に記載する値は作成したシークレットの名前を記載します。

Cloud Build, Cloud Runにアクセス権をつける

Cloud BuildとCloud Runからシークレットにアクセスできるようにアクセス権を付与します。

Cloud Runへのアクセス権付与はこちら

gcloud secrets add-iam-policy-binding sample-app-rails-master-key \
    --member serviceAccount:PROJECTNUM-compute@developer.gserviceaccount.com \
    --role roles/secretmanager.secretAccessor

Cloud Buildへのアクセス権付与はこちら

gcloud secrets add-iam-policy-binding sample-app-rails-master-key \
    --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
    --role roles/secretmanager.secretAccessor

sample-app-rails-master-keyPROJECTNUM については自分のこれまでの作業などに合わせた値に設定してください。

PROJECT_NUM については以下コマンドで取得できます。

gcloud projects describe PROJECT_ID --format='value(projectNumber)'

このコマンド打たなくてもGCPコンソールのダッシュボードのプロジェクト情報の欄に記載してあるのでお好きな方法でプロジェクト番号を取得すると良さそうです。

Cloud Buildを設定

DBとシークレットが用意できたのでアプリケーションをビルドしていきます。

ビルドにはCloud Buildを利用します。

Cloud Buildを利用するために cloudbuild.yaml を作成し、以下を記載します。

steps:
  - id: "build image"
    name: "gcr.io/cloud-builders/docker"
    entrypoint: "bash"
    args: [
        "-c",
        "docker build . \
        -t gcr.io/${PROJECT_ID}/${_SERVICE_NAME} \
        --build-arg RAILS_ENV=production \
        --build-arg RAILS_MASTER_KEY=$$RAILS_KEY \
        --build-arg GOOGLE_PROJECT_ID=${PROJECT_ID} \
        --build-arg CLOUD_SQL_CONNECTION_NAME='${PROJECT_ID}:${_REGION}:${_INSTANCE_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: asia-northeast1
  _SERVICE_NAME: cloud-run-app-sample
  _INSTANCE_NAME: cloudrun-app-sample-db-instance
  _SECRET_NAME: sample-app-rails-master-key

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

images:
  - "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"

設定ファイルは基本的にGCPのサンプルアプリに記載されている内容をそのまま使っています。

github.com

ここで _SERVICE_NAME の名前は英数字か - しか使えないので注意です。

このSEVICE_NAMEがContainer Registryに登録されるイメージの名前になります。

チュートリアルと異なる点としては環境変数周りを .env を使って設定するのではなくdocker build時に --build-arg で渡している点です。

--build-arg で渡した値をイメージビルド時に活用できるようにDockerfileに以下を追記します

ARG RAILS_ENV
ARG RAILS_MASTER_KEY
ARG CLOUD_SQL_CONNECTION_NAME
ARG GOOGLE_PROJECT_ID

ENV RAILS_ENV=${RAILS_ENV}
ENV RAILS_MASTER_KEY=${RAILS_MASTER_KEY}
ENV CLOUD_SQL_CONNECTION_NAME=${CLOUD_SQL_CONNECTION_NAME}
ENV GOOGLE_PROJECT_ID=${GOOGLE_PROJECT_ID}

こうすることでdocker build時に設定したい環境変数の値をARGとして渡して、コンテナの環境変数に設定することができます。

データベース作成時点では設定していなかったconfig/database.ymlのsocketも設定します。

Cloud SQL proxyはホストで繋ぐというよりソケット通信でDBと接続するためhostではなくsocketを設定する必要があります。

production:
  ~
  socket: "<%= ENV.fetch("DB_SOCKET_DIR") { '/cloudsql' } %>/<%= ENV["CLOUD_SQL_CONNECTION_NAME"] %>"

Cloud BuildからCloud SQLにアクセスできるようにロールを追加しておきます。

gcloud projects add-iam-policy-binding PROJECT_ID \
    --member serviceAccount:PROJECTNUM@cloudbuild.gserviceaccount.com \
    --role roles/cloudsql.client

PROJECTNUM はシークレットでアクセス権を設定した際に用いたものと同様です。PROJECT_IDは自分のGCPプロジェクトのIDを設定してください。

ここまで設定できたらいよいよCloud Buildを実行してみます。

gcloud builds submit --config cloudbuild.yaml

チュートリアルでは --substitutions を指定していますが、cloudbuild.yamlに必要な内容は書いているので指定しなくても問題ありません。

gcloud buildsが完了すればDockerイメージがContainer Repositoryに登録され、Cloud SQLへのmigrateが完了した状態になります。

あとはCloud Runへのデプロイが完了すればWebアプリケーションが立ち上がります。

Cloud Runへのデプロイ実行

Cloud Runへのデプロイはコマンド一つで完了します。

gcloud run deploy cloud-run-app-sample \
     --platform managed \
     --region asia-northeast1 \
     --image gcr.io/PROJECT_ID/cloud-run-app-sample
     --add-cloudsql-instances=CONNECTION_NAME
     --allow-unauthenticated

PROJECT_ID は自身のGCPプロジェクトのID、 cloud-run-app-sample の部分はcloudbuild.ymlで _SERVICE_NAME で設定した値を当てはめてください。

CONNECTION_NAME はCloudSQLのコンソールから取得できます。( PROJECT_ID:REGION:DBインスタンス名 のやつです)

--add-cloudsql-instances オプションを付けないとアプリケーションアクセス時にCloud SQLに接続できずにConnection Errorとなるので注意が必要です。(私はこれを忘れて無駄にハマりました...チュートリアルにもちゃんとかいてあるのに...)

コマンド実行が完了したら最後に表示されるURLにアクセスすることでアプリケーションを表示することができます。

まとめ

Cloud Runへのデプロイができることでコンテナの設定だけしておけば簡単に外部公開できるアプリケーションができてしまいます。

チュートリアルだと.envで環境変数を設定していたり、PostgrSQLだったりとちょっと工夫したくなるところがあって微妙にハマりましたが、一回型ができてしまえばあとは無限に公開環境が作れてしまいますね!