Rails×Next.jsプロジェクトをGoogle Cloud Runデプロイする際の覚書
なぜAWSではなくGCP?
個人的な趣味嗜好です😎
以前にAWSのECSでコンテナ立ててデプロイするという経験をした時、
結構やること多くて大変だなーというのと、AWSの無料期間も終わり、
ただDBとECSまわり、あとELBを立てているだけで毎月8000円くらい
飛んでいくのが、個人開発のアプリでもったいないなーと思い。
よくGCPは最低インスタンス数を0にするとコールドスタート
(最初のアクセスに時間がかかる)というのがありますが、
それこそ、ステージング環境とかなら、使っていない時には
余計な費用とかかけたくないので全然OKです。
本番環境だけ最低インスタンスを1に設定すればいいかなと。
そしてFirebaseでGCPめっちゃ楽やん!って思って、GCPに
スタックを寄せたいなというのがあったのも理由の一つです👍
公式のdocsを参照します。
基本はその内容に沿った上で、自身の環境との差異で詰まった部分を記載していきます。
自身の環境
- Ruby 3.2.2
- Rails 7.0.4
- React 18
- Next.js 13
- 認証やストレージ系はFirebase
- フロント、apiともにDockerで構築
上のプロジェクトとの差異
- モノレポであること
- 以下のような構成で組んでいた
- pj_file
- api
- Dockerfile
- frontend
- Dockerfile
- .github(actions等の設定)
- api
- pj_file
- 以下のような構成で組んでいた
- staging環境を用意したいということ
- ここで、staging用の設定ファイルの是非については問いません。とりあえず検証環境を作りたい
- github actionsで自動デプロイできるようになりたい
- ローカル実行や、cloudbuild実行でも十分だがやはり一回構築してみたい。という理由
公式と異なる箇所で、個別で対応した箇所
CloudSQLの作成
こちらは公式だとpostgreSQLですが、今回はMySQLで作りました。
まず最初に、gcloudを最新にしておきます。
gcloud components update
その後、自分の場合はmysql8.0.32を使っていたので、そのようにコマンドを修正しました。
gcloud sql instances create $INSTANCE_NAME \
--database-version MYSQL_8_0_32 \
--tier db-f1-micro \
--region asia-northeast1
Secret Manager にシークレット値を保存する
公式では、1つのcredential前提で組んでいますが、各環境に分けたかったので、
こちらの記事を参考に、環境ごとにcredentialsを作成しました。
staging環境作成&デプロイ方法
staging環境の作成については様々な記事があるので割愛します。
最初は公式docsの通りにcloudbuild.ymlで実装するという前提で、
基本的な解決策は、railsやrakeコマンドにRAILS_ENV=staging追加するだけです。
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 RAILS_ENV=staging" ← こんな感じでOK,
"/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:seed RAILS_ENV=staging"
]
secretEnv: ["RAILS_KEY"]
substitutions:
_REGION: asia-northeast1
_SERVICE_NAME: YOUR_SERVICE_NAME
_INSTANCE_NAME: YOUR_INSTANCE_NAME
_SECRET_NAME: YOUR_SECRET_NAME
availableSecrets:
secretManager:
- versionName: projects/${PROJECT_ID}/secrets/${_SECRET_NAME}/versions/latest
env: RAILS_KEY
images:
- "gcr.io/${PROJECT_ID}/${_SERVICE_NAME}"
GitHubActionsでのデプロイ自動化
条件としては、
- stagingブランチにプッシュしたら発火
- apiとfrontendのデプロイを実行
- 各リポジトリごとにワークフロー定義
という感じで組みました。
大枠の設定に関しては
GitHub ActionsでCloud Runにデプロイする
を参考に構築しています。
この記事の中でWorkflow Identity poolの作成のattribute-conditionですが、公式での問題点が記載されています。
そのため、こちらの設定は、repository_owner_idを設定しようと思います。
repository_owner_idは公式のapiで確認可能です。
--attribute-condition="assertion.repository_owner_id=='9919'
これを設定することで、別のユーザーやリポジトリからこのactionsのデプロイ実行されることを防止できます。
以下が、実際に組んだデプロイのワークフローです。
on:
push:
branches:
- staging
name: (Api) Build and Deploy to Cloud Run for Staging
env:
SERVICE_NAME: {cloud runにデプロイするサービス名}
PROJECT_ID: {GCPのプロジェクトID}
REGION: asia-northeast1
SERVICE_ACCOUNT: {作ったサービスアカウント}
PROVIDER: {参考記事7番で取得したリソース名}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
defaults:
run:
working-directory: api
steps:
- name: Checkout
uses: actions/checkout@v3
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: "google-github-actions/auth@v0"
with:
workload_identity_provider: ${{ env.PROVIDER }}
service_account: ${{ env.SERVICE_ACCOUNT }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v1
- name: Authorize Docker push
run: gcloud auth configure-docker
- name: Build Docker image
run: docker build -t gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} .
- name: Push Docker Image
run: docker push gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }}
- name: Deploy to Cloud Run
run: |-
gcloud run deploy $SERVICE_NAME \
--project=$PROJECT_ID \
--image=gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} \
--region=$REGION \
--platform=managed \
--allow-unauthenticated
Github Actions実行時にエラー発生!どうする?
auth実行時にエラーが起きた際はここらへんが原因かも
もしも、docker-push時に権限が足りない、というようなエラーメッセージが表示された場合は、この辺が役に立つと思います。
-
storage.buckets.getの権限が足りない場合
- {権限}に"storage.admin"を入れてコマンド実行
-
run.services.getの権限が足りない場合
- {権限}に"run.admin"を入れてコマンド実行
-
iam.serviceaccounts.actAs
- {権限}に"iam.serviceAccountUser"を入れてコマンド実行
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/{権限}" \
--member="serviceAccount:${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
フロントに環境変数を設定したい
Firebaseのconfigに必要なデータ(FIREBASE_API_KEYなど)を渡したいと思っての動機です。
他にもやり方があるし、ベストではないと思うのですが、このやり方で一旦できたので乗せておきます。
まず、Dockerfileで以下のように環境変数を受け取れるようにしておきます。
FROM node:18 AS builder
WORKDIR /app
ARG NEXT_PUBLIC_FIREBASE_API_KEY
ARG NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
ARG NEXT_PUBLIC_FIREBASE_PROJECT_ID
ARG NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
...(他にもあれば)
COPY package.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile --production=false
COPY . .
RUN yarn build
FROM node:18-alpine3.16 AS runner
ENV NODE_ENV=production
WORKDIR /app
# standalone モードを利用すると、publicと.next/staticはデフォルトでは含まれないので明示的にコピーする必要がある
COPY /app/next.config.js ./
COPY /app/public ./public
COPY /app/.next/static ./.next/static
COPY /app/.next/standalone ./
CMD ["node", "server.js"]
そして、github actionsのデプロイのスクリプト内で
dockerをbuildする箇所で環境変数を渡してあげます。
一部省略している部分はあるので、そこは自分の環境に合わせて設定してください。
on:
push:
branches:
- staging
name: (Front) Build and Deploy to Cloud Run for Staging
env:
FIREBASE_API_KEY: testeeeeeesssssssssssssssssst
FIREBASE_AUTH_DOMAIN: test-dev.firebaseapp.com
FIREBASE_PROJECT_ID: test-dev
FIREBASE_STORAGE_BUCKET: test-dev.appspot.com
FIREBASE_MESSAGING_SENDER_ID: testesteste
SERVICE_NAME: test-dev-front
PROJECT_ID: test-dev
REGION: asia-northeast1
SERVICE_ACCOUNT: github-action-cd@test-dev.iam.gserviceaccount.com
PROVIDER: projects/testtest/locations/global/workloadIdentityPools/github-actionstest-pool/providers/github-actions-provider
jobs:
deploy:
runs-on: ubuntu-latest
permissions: ← これを設定しないと権限エラーで落ちる
contents: "read"
id-token: "write"
defaults:
run:
working-directory: frontend ← モノレポのプロジェクトの場合、これを設定することで、スクリプト実行時にfrontendをルートディレクトリとしてコマンド実行してくれる
steps:
- name: Checkout
uses: actions/checkout@v3
- id: "auth"
name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v0"
with:
workload_identity_provider: ${{ env.PROVIDER }}
service_account: ${{ env.SERVICE_ACCOUNT }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v1
- name: Authorize Docker push
run: gcloud auth configure-docker
- name: Build Docker image
run: |-
docker build -t gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} \
--build-arg NEXT_PUBLIC_FIREBASE_API_KEY=${{ env.FIREBASE_API_KEY }} \
--build-arg NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${{ env.FIREBASE_AUTH_DOMAIN }} \
--build-arg NEXT_PUBLIC_FIREBASE_PROJECT_ID=${{ env.FIREBASE_PROJECT_ID }} \
--build-arg NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${{ env.FIREBASE_STORAGE_BUCKET }} \
--build-arg NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${{ env.FIREBASE_MESSAGING_SENDER_ID }} .
- name: Push Docker Image
run: docker push gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }}
- name: Deploy to Cloud Run
run: |-
gcloud run deploy $SERVICE_NAME \
--project=$PROJECT_ID \
--image=gcr.io/$PROJECT_ID/$SERVICE_NAME:${{ github.sha }} \
--region=$REGION \
--platform=managed \
--allow-unauthenticated
もしまた新しい知見がみつかったら追記か新しい記事書きます。
ではみなさん、良き開発ライフを〜👋
Discussion