G-gen の藤岡です。当記事では Google Cloud(旧称 GCP)で Cloud Run のタグ付きリビジョン(tagged revision)機能を使い、GitHub のプルリクエストをトリガとしたプレビュー環境の自動デプロイを実装する方法を紹介します。
概要
Netlify や Vercel などのホスティングサービスでは「Deploy Previews」と呼ばれる GitHub と連携してプルリクエストをトリガとして自動でプレビュー環境をデプロイする機能が提供されています。 Google Cloud のホスティングサービスである Firebase Hosting でも同様にプルリクエストごとにプレビュー環境を自動で作ることができます。
今回は、プルリクエストをトリガにして Cloud Run サービスの「タグ付きリビジョン」をデプロイすることで、本番環境へデプロイ前に試験や動作確認をできるようにします。 構成としては以下の通りです。
前提知識
Cloud Run のタグ付きリビジョン
Cloud Run は、デプロイされるリビジョンごとにタグを付与できます。タグを付与すると通常のデプロイ時に生成される URL とは別に、タグが付与された URL が割り当てられます。
# Cloud Run の通常の URL 例 https://<servicename>-xxxxxxxxxx-an.a.run.app # タグ付きリビジョンの URL 例(リビジョンに dev タグを付与した場合) https://dev---<servicename>-xxxxxxxxxx-an.a.run.app
そのため、タグ付きリビジョン URL へのトラフィックを 0% にしてデプロイすることで、本番環境へ影響することなく事前にフロントエンド等のテストをすることができます。
Cloud Run のタグ付きリビジョンに関する詳細は、以下の記事をご参照ください。 blog.g-gen.co.jp
GitHub Actions と Workload Identity 連携
GitHub Actions では OIDC(OpenID Connect)トークンを使った認証がサポートされており、Workload Identity 連携と組み合わせることで従来のようにサービスアカウントキーを発行せずに Google Cloud へ認証が可能です。
GitHub Actions と Workload Identity 連携に関する詳細は、以下の記事をご参照ください。 blog.g-gen.co.jp
アーキテクチャ
これ以降では、以下の構成で検証をしていきます。
事前準備
前提
以下の環境および条件を前提とします。
- Google Cloud のプロジェクト作成の作成および API の有効化済み
- Workload Identity 連携の設定がされていること
- Cloud Run は第 2 世代を使用
- Cloud Run のデプロイはソースコードから行う
- Cloud Run のサンプルアプリを使用
ディレクトリ構成
ディレクトリ構成は以下の通りです。
. ├── README.md └── web # サンプルアプリ ├── go.mod └── main.go .github └── workflows # GitHub Actions を定義 ├── preview-workflow.yaml └── production-workflow.yaml
GitHub Actions のワークフローファイル
ワークフローファイルは以下の 2 つを用意しました。
# preview-workflow.yaml name: Preview Workflow on: pull_request: branches: - '**' types: - opened - synchronize env: PROJECT_ID: "PROJECT_ID" SERVICE: "sample-app" REGION: "asia-northeast1" SERVICE_ACCOUNT: "workload@PROJECT_ID.iam.gserviceaccount.com" PROJECT_NUMBER: "PROJECT_NUMBER" WORKLOAD_IDENTITY_POOL_ID: "cloud-run-test" WORKLOAD_IDENTITY_PROVIDER_ID: "github-actions" jobs: preview: runs-on: ubuntu-latest permissions: contents: read id-token: write pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 - name: Authenticate to Google Cloud uses: google-github-actions/auth@v1 with: workload_identity_provider: projects/${{ env.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.WORKLOAD_IDENTITY_POOL_ID }}/providers/${{ env.WORKLOAD_IDENTITY_PROVIDER_ID }} service_account: ${{ env.SERVICE_ACCOUNT }} - name: Extract details for tag run: | echo "SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c 1-4)" >> $GITHUB_ENV echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV - name: Deploy to Cloud Run from Source id : deploy uses: google-github-actions/deploy-cloudrun@v1 with: service: ${{ env.SERVICE }} region: ${{ env.REGION }} source: ./web tag: pr-${{ env.PR_NUMBER }}-commit-${{ env.SHORT_SHA }} flags: "--allow-unauthenticated --platform=managed --execution-environment=gen2" no_traffic: true - name: Get datetime for now run: echo "CURRENT_DATETIME=$(date)" >> $GITHUB_ENV env: TZ: Asia/Tokyo - name: Create comment uses: peter-evans/create-or-update-comment@v3 with: comment-id: ${{ steps.find_comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | :tada: Successfully deployed preview revision for this PR (updated for commit ${{ github.event.pull_request.head.sha }}): <${{ steps.deploy.outputs.url }}> <sub>(:sparkles: updated at ${{ env.CURRENT_DATETIME }})</sub> edit-mode: replace
# production-workflow.yaml name: Production Workflow on: workflow_dispatch: pull_request: types: [ closed ] env: PROJECT_ID: "PROJECT_ID" SERVICE: "sample-app" REGION: "asia-northeast1" SERVICE_ACCOUNT: "workload@PROJECT_ID.iam.gserviceaccount.com" PROJECT_NUMBER: "PROJECT_NUMBER" WORKLOAD_IDENTITY_POOL_ID: "cloud-run-test" WORKLOAD_IDENTITY_PROVIDER_ID: "github-actions" jobs: deploy: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - name: Checkout uses: actions/checkout@v4 - name: Authenticate to Google Cloud uses: google-github-actions/auth@v1 with: workload_identity_provider: projects/${{ env.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.WORKLOAD_IDENTITY_POOL_ID }}/providers/${{ env.WORKLOAD_IDENTITY_PROVIDER_ID }} service_account: ${{ env.SERVICE_ACCOUNT }} - name: Check if service exists id: check-service run: | EXISTS=$(gcloud run services list --platform managed --region ${{ env.REGION }} | grep "${{ env.SERVICE }}" || true) if [[ -z "$EXISTS" ]]; then echo "Service does not exist, setting deploy flag." echo "DEPLOY_FLAG=true" >> $GITHUB_ENV else echo "Service already exists, skipping deploy." echo "DEPLOY_FLAG=false" >> $GITHUB_ENV fi - name: Deploy to Cloud Run from Source if: env.DEPLOY_FLAG == 'true' id : deploy uses: google-github-actions/deploy-cloudrun@v1 with: service: ${{ env.SERVICE }} region: ${{ env.REGION }} source: ./web flags: "--allow-unauthenticated --platform=managed --execution-environment=gen2" - name: Extract details for tag if: env.DEPLOY_FLAG != 'true' run: | echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV echo "SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c 1-4)" >> $GITHUB_ENV - name: Remove revision with tag if: env.DEPLOY_FLAG != 'true' run: > gcloud run services update-traffic ${{ env.SERVICE }} --region ${{ env.REGION }} --remove-tags pr-${{ env.PR_NUMBER }}-commit-${{ env.SHORT_SHA }} - name: Add prod tag and Traffic routing if: env.DEPLOY_FLAG != 'true' run: > gcloud run services update-traffic ${{ env.SERVICE }} --region ${{ env.REGION }} --to-latest
デプロイとテスト
初回デプロイ
GitHub
初回は production-workflow.yaml
のワークフローを手動実行し、Cloud Run へサンプルアプリをデプロイします。
トリガを workflow_dispatch
とすることで、手動でワークフローの実行が可能になります。
# production-workflow.yaml on: workflow_dispatch: pull_request: types: [ closed ]
GitHub のコンソールからは以下のように、ワークフローが手動で実行できる画面が表示されます。
Run workflow
をクリックすると、Cloud Run へサンプルアプリがデプロイされます。
Google Cloud
デプロイが完了すると、サンプルアプリの URL は https://sample-app-xxxx-an.a.run.app
となっています。
WEB 画面
ブラウザで URL へアクセスすると以下の画面が表示されます。
プルリクエストの作成
GitHub
新しいブランチで main.go
ファイルの中身を一部変更します。
# main.go # 変更前 if name == "" { name = "World" } # 変更後 if name == "" { name = "World!!!!!!!!" }
プルリクエストを作成すると、ワークフローがトリガされます。 ワークフローが完了すると、以下のようにプルリクエストにコメントと共にタグが付与された URL が表示されます。
Google Cloud
ワークフローによってデプロイされた、タグ付きリビジョンのサービスがデプロイされました。
WEB 画面
ブラウザから確認すると、タグが付与された URL には main.go
の変更後が反映され、通常の URL は変更前のままです。
プルリクエストをマージ
GitHub
プルリクエストをマージすると、ワークフローがトリガされます。
Google Cloud
ワークフローによって、タグが削除され、トラフィックが最新のデプロイに 100% 流れるようになっています。
WEB 画面
ブラウザから確認すると、タグが付与された URL は Error: Page not found
となり、通常のサービス URL(https://sample-app-xxxx-an.a.run.app
)へ変更が反映されています。
考慮事項
Google Cloud
Cloud Run のサービスアカウント
Cloud Run のサービスアカウントはデフォルトの Compute Engine のサービスアカウントを使っているため、編集者(roles/editor)
権限が付与されています。最小限の権限の原則に従って適切な権限が付与されたサービスアカウントを使用してください。ソースコードから Cloud Run をデプロイする時に作られるリソース
ソースコードから Cloud Run をデプロイすると、Cloud Run 以外に以下のリソースが作られます。デプロイごとに容量が増えていくため、注意してください。- Cloud Storage:
<PROJECT_ID>_cloudbuild>
バケット(Cloud Build によってソースコードがアップロード) - Artifact Registry:
cloud-run-source-deploy
リポジトリ(Cloud Build によってコンテナ化されたイメージがアップロード)
- Cloud Storage:
Cloud Run のリビジョンタグ
Cloud Run のタグが付与された URL にアクセスがある場合や最小インスタンスの設定によりコンテナが起動している場合、課金が発生します。 そのため、不要なリビジョンタグは削除すると意図しない課金が発生するリスクを軽減できます。- 参考:リビジョンの管理
Cloud Run の認証
Cloud Run サービスの公開(未認証)アクセスを許可するは、リビジョンではなくサービス自体に紐づいているため、本番環境のみ認証有り、プレビュー環境は認証無し、とすることは現状できません。
GitHub Actions
環境変数
今回は 2 つのワークフローでenv
として環境変数をファイル内で直接定義しています。共通する環境変数や機密情報を含むものは、GitHub Actions の変数やシークレットを使用してください。set-output
ワークフローコマンドが廃止
ワークフロー内のステップの出力値を設定し、後続のステップでその値を参照するために使われていたset-output
ワークフローコマンドの廃止が発表されています。 当記事では使っていませんが、ワークフロー内で使っている場合は注意が必要です。
藤岡 里美 (記事一覧)
クラウドソリューション部
数年前までチキン売ったりドレスショップで働いてました!2022年9月 G-gen にジョイン。ハイキューの映画を4回は見に行きたい。
Google Cloud All Certifications Engineer / Google Cloud Partner Top Engineer 2024
Follow @fujioka57621469