GitHub Actions から Cloud Run with Cloud SQL をデプロイするための仕組みを構築してみた。
https://cloud.google.com/blog/ja/products/devops-sre/deploy-to-cloud-run-with-github-actions
GitHub Actions から Cloud SQL へのマイグレーションを行うステップが気になる方は、以下の記事を参照してほしい。
https://moroball14.github.io/moroball14-website/blog/migrate-cloud-sql-from-github-actions
上記ページから遷移できる、 sample repository のコードを見てみる。
これみた感じ、ステップとしては、
- Workload Identity 連携を構成する
gcloud
コマンドを実行できるようにする- Docker の認証を設定する
- Docker をビルドして、作成された Docker イメージをプッシュする
- Cloud Deploy を利用して Cloud Run にデプロイする
って感じっぽい。Cloud Deploy を使うメリットって何だろうって調べたら、DORA が提唱する four key metrics のうち、頻度だったり失敗率を計測してくれるらしい ↓
https://cloud.google.com/deploy/docs/metrics?hl=ja
これは嬉しいかも。ただそれほどリッチでなくていいので今回は Cloud Deploy は使わずに、Cloud Run にデプロイする方法を調べる。
再利用される前提のワークフローが作成されているのでそれを確認すると、、
- Dockerfile を使用してコンテナイメージをビルドする
- コンテナイメージを Artifact Registry に push する
- 該当する Google Cloud プロジェクト内の Cloud Run にコンテナイメージをデプロイする
というステップがある。
ビルドの段階では、
- checkout アクションを実行する
- auth アクション実行して Workload Identity 連携を構成する
- Docker Auth を実行して認証を行う
- tag つけてビルドおよびプッシュする
って感じだな。これが継続的インテグレーションのワークフローっぽい。
デプロイの段階では、
- 環境変数のセット
- envsubst で環境変数を埋め込んだマニフェストファイルを作成
deploy-cloudrun
アクションを実行してデプロイ
か。やること整理すると、
- ビルドの設定
- Workload Identity 連携を構成する
- Docker に対する認証を行う
build-push-action
を使ってビルドおよびプッシュする
- デプロイの設定
- push 時に動くデプロイ用の GitHub Actions ファイルを作成する
- デプロイする workflow ファイルを作成する
- 環境は一つしかないが、一旦この方針で進める
- ステップを構成する
- 環境変数のセット
- envsubst で環境変数を埋め込んだマニフェストファイルを作成
deploy-cloudrun
アクションを実行してデプロイ
- Cloud Run のマニフェストファイルを作成する
- 必要な secrets を GitHub に設定する
- デプロイが成功したら、Cloud Run の URL にアクセスして確認する
になるか。
ビルドの設定
1. Workload Identity 連携を構成する
- id: "auth"
name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v1"
with:
create_credentials_file: true
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.WORKLOAD_IDENTITY_SA }}
token_format: "access_token"
token_format は access_token
にしておく。これは、Docker に対する認証を行う際に利用するため。
2. Docker に対する認証を行う
- name: "Docker Auth"
uses: "docker/login-action@v1"
with:
username: "oauth2accesstoken"
password: ${{ steps.auth.outputs.access_token }}
registry: "${{ vars.REGION }}-docker.pkg.dev"
3. build-push-action
を使ってビルドおよびプッシュする
- name: Build, tag and push container
id: build-image
uses: docker/build-push-action@v3
with:
context: ${{ vars.code_directory }}
push: true
tags: |
${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}
デプロイの設定
1. push 時に動くデプロイ用の GitHub Actions ファイルを作成する
$ touch .github/workflows/deploy-to-cloud-run.yaml
2. デプロイする workflow ファイルを作成する
$ touch .github/workflows/_deployment.yaml
3. ステップを構成する
.github/workflows/deploy-to-cloud-run.yaml
name: Deploy to Cloud Run
on:
push:
branches: [main]
jobs:
production:
uses: ./.github/workflows/_deployment.yaml
permissions:
id-token: write
contents: read
with:
environment: production
ref: ${{ github.sha }}
secrets: inherit
secrets に inherit
を指定しないと、呼び出した workflow で secrets が参照できない。
.github/workflows/_deployment.yaml
name: Reusable Deployment
on:
workflow_call:
inputs:
environment:
description: "Environment to deploy to"
required: true
default: "staging"
type: string
ref:
description: "Git ref to deploy"
required: true
default: "main"
type: string
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: "actions/checkout@v4"
- id: "auth"
name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v1"
with:
create_credentials_file: true
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.WORKLOAD_IDENTITY_SA }}
token_format: "access_token"
- name: "Docker Auth"
uses: "docker/login-action@v1"
with:
username: "oauth2accesstoken"
password: ${{ steps.auth.outputs.access_token }}
registry: ${{ vars.REGION }}-docker.pkg.dev
- name: "Build and push image"
id: build-image
uses: "docker/build-push-action@v3"
with:
context: .
push: true
tags: |
${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}
- name: Create Service declaration
run: |-
export CONTAINER_IMAGE="${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}"
export SERVICE_NAME="${{ vars.SERVICE_NAME }}"
export PROJECT_ID="${{ vars.GCP_PROJECT_ID }}"
export REVISION_TAG="${{ inputs.ref }}"
export CLOUD_RUN_SA="${{ vars.CLOUD_RUN_SA }}"
export ENVIRONMENT="${{ inputs.environment }}"
export DATABASE_URL="${{ secrets.DATABASE_URL }}"
envsubst < ./.github/workflows/service-yaml/container.yaml > container-${{ inputs.environment }}.yaml
cat container-${{ inputs.environment }}.yaml
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v1
with:
region: ${{ vars.REGION }}
metadata: container-${{ inputs.environment }}.yaml
- name: Show Output
run: echo ${{ steps.deploy.outputs.url }}
DATABASE_URL は、Cloud Run から Cloud SQL に繋げるために必要な環境変数。ORM には Prisma を利用していて、DATABASE_URL を参照するように設定しているため必要。
4. Cloud Run のマニフェストファイルを作成する
envsubst < ./.github/workflows/service-yaml/container.yaml > container-${{ inputs.environment }}.yaml
とあるように、事前に作成したマニフェストファイルをもとに、環境ごとの環境変数を埋め込んだマニフェストファイルが作成される。(今回は 1 つの環境にしかデプロイしないが、商用では開発用、検証用などといくつか環境を用意することを想定している)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: ${SERVICE_NAME}
namespace: ${PROJECT_ID}
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: "100"
spec:
containers:
- image: ${CONTAINER_IMAGE}
ports:
- containerPort: 3000
env:
- name: ENVIRONMENT
value: ${ENVIRONMENT}
- name: DATABASE_URL
value: ${DATABASE_URL}
- name: NODE_ENV
value: ${ENVIRONMENT}
traffic:
- percent: 100
latestRevision: true
このマニフェストファイル初めて作ったので、遭遇したエラーをメモ。
Error: google-github-actions/deploy-cloudrun failed with: failed to execute gcloud command `gcloud run services replace container-production.yaml --platform managed --format json --region us-central1`: ERROR: (gcloud.run.services.replace) Namespace must be project ID [XXX] or quoted number [1234567890] for Cloud Run (fully managed).
container.yaml の metadata.namespace を指定する必要があるらしい。
5. 必要な secrets や vars を GitHub に設定する
CLOUD_RUN_SA に関しては、以下のページを参考にした。
https://cloud.google.com/sql/docs/postgres/connect-instance-cloud-run?hl=ja#deploy_sample_app_to
Cloud Run のドキュメントにも記載がある通り、デフォルトの Compute Engine サービスアカウントとして実行されるため、上記ページの設定で良さそう。
https://cloud.google.com/run/docs/configuring/service-accounts?hl=ja
また、DATABASE_URL に関しては、GitHub Actions から Cloud SQL へ接続するために利用した文字列だと接続できなかった。GitHub Actions から接続した際は Cloud SQL Auth Proxy を利用していて、Cloud SQL インスタンスの情報は Cloud SQL Auth Proxy を利用する際に渡していた。
同じ文字列を渡すと以下のエラーが出る。
Deployment failed
ERROR: (gcloud.run.services.replace) Revision 'xxxxx' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=3000 environment variable. Logs for this revision might contain more information.
ログを確認できる URL が出力されていたので確認してみると Prisma のエラーが出ていた。
has no exported member
おそらく Prisma が生成する型情報が存在しないのかなと思いつつ調べてみたら、以下の記事を見つけたので試してみる。
https://zenn.dev/tsucchiiinoko/articles/bbf61e5e69e1ab
コンテナ起動時に npx prisma generate
を実行するように変更して、Prisma が生成する型情報を生成、エラーが出なくなった。
postgres://username:password@127.0.0.1:5432/databasename?host=/cloudsql/xxx
の形式だと GitHub Actions から Cloud SQL のマイグレーションが失敗するが、
postgres://username:password@127.0.0.1:5432/databasename
の形式だと Cloud Run から Cloud SQL に繋げず、起動に失敗することが確認できた。
なので、Cloud Run 用に改めて環境変数を設定してみる。
diff --git a/.github/workflows/_deployment.yaml b/.github/workflows/_deployment.yaml
index 72c6741..8ee4051 100644
--- a/.github/workflows/_deployment.yaml
+++ b/.github/workflows/_deployment.yaml
@@ -49,7 +49,7 @@ jobs:
export REVISION_TAG="${{ inputs.ref }}"
export CLOUD_RUN_SA="${{ vars.CLOUD_RUN_SA }}"
export ENVIRONMENT="${{ inputs.environment }}"
- export DATABASE_URL="${{ secrets.DATABASE_URL }}"
+ export DATABASE_URL="${{ secrets.CLOUD_RUN_DATABASE_URL }}"
Cloud Run からと GitHub Actions からで環境変数を分けることにした。
が、それでもエラーが出る。
結局繋げていないなー。。。と思って Cloud Run から Cloud SQL に繋げるために何か必要かなーと調べたら、actions で、 flags: --add-cloudsql-instances
というオプションを指定する必要があるらしい。のでつけたが
Error: google-github-actions/deploy-cloudrun failed with: failed to execute gcloud command `gcloud run services replace container-production.yaml --platform managed --format json --region us-central1 --add-cloudsql-instances ***`: ERROR: (gcloud.run.services.replace) unrecognized arguments:
--add-cloudsql-instances
***
To search the help text of gcloud commands, run:
gcloud help -- SEARCH_TERMS
だったので、あれーと思って、もしや container.yaml
の方か?と思って調べてみると
https://cloud.google.com/run/docs/reference/yaml/v1
に run.googleapis.com/cloudsql-instances
についての記載があったので、これを設定してみる。
container.yaml
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: "100"
run.googleapis.com/cloudsql-instances: ${CLOUD_SQL_CONNECTION}
_deployment.yaml
- name: Create Service declaration
run: |-
export CONTAINER_IMAGE="${{ vars.REGION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.ARTIFACT_REPO }}/${{ vars.SERVICE_NAME }}:${{ inputs.ref }}"
~~
export CLOUD_SQL_CONNECTION="${{ secrets.CUSTOM_QUIZ_POSTGRES_INSTANCE }}" # ここを追加
envsubst < ./.github/workflows/service-yaml/container.yaml > container-${{ inputs.environment }}.yaml
cat container-${{ inputs.environment }}.yaml
CLOUD_SQL_CONNECTION を設定する。これは、接続名なので、 project-id:region:instance-name
という形式で設定する。おそらくコンソール画面にもあるはず。
これで、Cloud Run から Cloud SQL に繋げて、デプロイができた。
6. デプロイが成功したら、Cloud Run の URL にアクセスして確認する
curl コマンドで認証を通して Cloud Run に POST リクエストを送る。
以下を参考。
https://cloud.google.com/run/docs/authenticating/developers?hl=ja#curl
$ curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" SERVICE_URL
データベースとの疎通確認を行い、デプロイが完了。
おわりに
やっと公開できた。
あまり休みに時間が取れなかったので、Cloud SQL へのマイグレーションが実行できるようになってから 2 週間くらい経過してしまった。
Cloud SQL は動かし続けるとクラウド破産する気がしてならないので、必要な時だけ起動するように気をつけてほしい。