シンプル構成で実現する - Cloud Run によるプルリクエスト単位の検証環境構築🚀
この記事では Cloud Run を用いたプルリクエスト単位の検証環境をシンプルな構成で構築する方法を共有します。
同じようなことを実現したいと考えている方の参考になれば幸いです。
作成する構成
今回作成するシステムの構成は以下の通りです。
以下の流れで処理が実行されます。
- 開発者が Pull Request に dev ラベルを付与する
- GitHub Actions のワークフローが起動して以下の処理を行う
- A レコードの設定
- タグ付きリビジョン(pr-1)のデプロイ
- 名前解決:ユーザーが
pr-1.dev.example.com
にアクセスする - ワイルドカード証明書を用いた SSL 通信
- URL マスクで対応するリビジョンにリクエストを転送する
これにより Pull Request 単位で独立した検証環境が自動的に構築され、開発者とユーザーは専用 URL で各環境にアクセスできるようになります。
構築手順
Terraform でインフラ構築
Cloud Run を使ったプルリクエスト単位の検証環境を構築するためには、まずは Terraform でインフラを準備します。
ここでは、構成の主要なポイントのみを解説します。
ワイルドカード証明書の設定
一つ目のポイントはワイルドカード証明書を利用する点です。これにより *.dev.example.com
(*
は任意の値)の形式で各 PR の環境にアクセスできるようになります。
以下はワイルドカード証明書を設定するための Terraform コードのサンプルです。
resource "google_certificate_manager_dns_authorization" "wildcard" {
name = "wildcard-dev-example"
domain = "dev.example.com"
project = var.project_id
}
resource "google_certificate_manager_certificate" "wildcard" {
name = "wildcard-dev-example"
project = var.project_id
managed {
domains = ["*.dev.example.com"]
dns_authorizations = [
google_certificate_manager_dns_authorization.wildcard.id,
]
}
}
resource "google_certificate_manager_certificate_map" "wildcard" {
name = "wildcard-dev-example"
project = var.project_id
}
resource "google_certificate_manager_certificate_map_entry" "wildcard" {
name = "wildcard-dev-example"
map = google_certificate_manager_certificate_map.wildcard.name
hostname = "*.dev.example.com"
project = var.project_id
certificates = [google_certificate_manager_certificate.wildcard.id]
}
# 証明書検証用のDNSレコード(CNAME)の設定
resource "google_dns_record_set" "cname_record" {
name = google_certificate_manager_dns_authorization.wildcard.dns_resource_record[0].name
type = "CNAME"
ttl = 300
managed_zone = var.managed_zone
project = var.project_id
rrdatas = [google_certificate_manager_dns_authorization.wildcard.dns_resource_record[0].data]
}
このコードにより *.dev.example.com
に対するワイルドカード証明書が生成され、各 PR の検証環境(pr-1.dev.example.com
, pr-2.dev.example.com
など)で HTTPS 通信が可能になります。
Serverless NEG の設定
二つ目のポイントは Serverless NEG における URL マスクの設定です。
URL マスクとは、簡単に言えば「URL のパターンに基づいて適切なサービスやリビジョンにリクエストを振り分ける仕組み」です。これを使うことで、プルリクエスト番号に応じた環境に自動的にリクエストを転送できます。
例えば、URL マスクに<tag>.dev.example.com
を設定すると:
- ユーザーが
pr-1.dev.example.com
にアクセスした場合 →pr-1
タグが付いた Cloud Run リビジョンにリクエストが転送 - ユーザーが
pr-2.dev.example.com
にアクセスした場合 →pr-2
タグが付いた Cloud Run リビジョンにリクエストが転送
これにより、プルリクエスト番号に対応する独立した環境に自動的にルーティングされます。URL マスクの <tag>
部分がプルリクエスト番号に対応するタグに置き換わる仕組みです。
URL マスクの詳細についてはこちらをご覧ください。
Serverless NEG(Network Endpoint Group)の Terraform コードのサンプルは以下の通りです。
resource "google_compute_region_network_endpoint_group" "serverless_neg" {
name = "neg-dev-example"
network_endpoint_type = "SERVERLESS"
project = "your-project-id"
region = "asia-northeast1"
cloud_run {
service = "your-cloud-run-service"
url_mask = "<tag>.dev.example.com"
}
}
GitHub Actions でワークフローを実装
開発者がプルリクエストに dev
ラベルを付与した際に自動的に環境をデプロイし、プルリクエストがクローズされた際に環境をクリーンアップする GitHub Actions ワークフローを実装します。
デプロイワークフロー
プルリクエストに dev
ラベルが付与されたときに実行される環境構築用のワークフローです。このワークフローでは、プルリクエストの番号に基づいて一意の環境を作成し、その URL をプルリクエストにコメントします。
name: プルリクエスト検証環境のデプロイ
on:
pull_request:
types:
- labeled
permissions:
contents: read
id-token: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
APP_NAME: your-app-name
PROJECT_ID: your-project-id
REGION: asia-northeast1
WORKLOAD_IDENTITY_PROVIDER: your-workload-identity-provider
SERVICE_ACCOUNT: your-service-account
DNS_ZONE: your-dns-zone
BASE_DOMAIN: dev.example.com
LOAD_BALANCER_IP: your-load-balancer-ip
jobs:
deploy-dev:
if: contains(github.event.pull_request.labels.*.name, 'dev')
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: 環境変数の設定
run: |
echo "IMAGE_URI=${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/containers/${{ env.APP_NAME }}" >> $GITHUB_ENV
echo "IMAGE_TAG=pr-${{ github.event.pull_request.number }}-${{ github.sha }}" >> $GITHUB_ENV
echo "REVISION_TAG=pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV
echo "SERVER_NAME=pr-${{ github.event.pull_request.number }}.${{ env.BASE_DOMAIN }}" >> $GITHUB_ENV
echo "DEPLOY_URL=https://pr-${{ github.event.pull_request.number }}.${{ env.BASE_DOMAIN }}" >> $GITHUB_ENV
- name: Google Cloudの認証
id: auth
uses: google-github-actions/auth@v2
with:
token_format: access_token
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.SERVICE_ACCOUNT }}
- name: アプリケーションのビルド
run: |
# ここでアプリケーションのビルド処理を実行
- name: Artifact Registryへのログイン
uses: docker/login-action@v3
with:
registry: ${{ env.REGION }}-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
- name: Dockerイメージのビルドとプッシュ
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ env.IMAGE_URI }}:${{ env.IMAGE_TAG }}
- name: Cloud Runサービスのデプロイ
run: |
gcloud run deploy your-cloud-run-service \
--region=${{ env.REGION }} \
--project=${{ env.PROJECT_ID }} \
--image=${{ env.IMAGE_URI }}:${{ env.IMAGE_TAG }} \
--tag=${{ env.REVISION_TAG }} \
--min-instances=0 \
--max-instances=2 \
--ingress=internal-and-cloud-load-balancing
- name: DNSレコードの作成
id: create-dns
run: |
RECORD_NAME="${{ env.SERVER_NAME }}"
if ! gcloud dns record-sets list \
--zone=${{ env.DNS_ZONE }} \
--name="${RECORD_NAME}." \
--project ${{ env.PROJECT_ID }} | grep -q "${RECORD_NAME}."; then
gcloud dns record-sets create "${RECORD_NAME}." \
--zone=${{ env.DNS_ZONE }} \
--type=A \
--ttl=300 \
--rrdatas=${{ env.LOAD_BALANCER_IP }} \
--project ${{ env.PROJECT_ID }}
echo "dns_created=true" >> $GITHUB_OUTPUT
else
echo "DNSレコードは既に存在します"
echo "dns_created=false" >> $GITHUB_OUTPUT
fi
- name: プルリクエストへのコメント
if: steps.create-dns.outputs.dns_created == 'true'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ github.event.pull_request.number }},
body: `### :rocket: 検証環境がデプロイされました!
**${process.env.DEPLOY_URL}**
修正内容をプッシュした場合、再度devラベルを付け直すと最新の修正内容が反映されます`
});
クリーンアップワークフロー
プルリクエストがクローズされたときに実行される環境クリーンアップ用のワークフローです。このワークフローによって、不要になった環境を自動的に削除し、リソースの無駄遣いを防ぎます。
name: プルリクエスト検証環境のクリーンアップ
on:
pull_request:
types:
- closed
permissions:
contents: read
id-token: write
defaults:
run:
shell: bash
env:
PROJECT_ID: your-project-id
WORKLOAD_IDENTITY_PROVIDER: your-workload-identity-provider
SERVICE_ACCOUNT: your-service-account
DNS_ZONE: your-dns-zone
BASE_DOMAIN: dev.example.com
jobs:
cleanup-dev:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v4
- name: Google Cloudの認証
uses: google-github-actions/auth@v2
with:
token_format: access_token
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.SERVICE_ACCOUNT }}
- name: DNSレコードの削除
run: |
RECORD_NAME="pr-${{ github.event.pull_request.number }}.${{ env.BASE_DOMAIN }}"
if gcloud dns record-sets list \
--zone=${{ env.DNS_ZONE }} \
--name="${RECORD_NAME}." \
--project ${{ env.PROJECT_ID }} | grep -q "${RECORD_NAME}."; then
gcloud dns record-sets delete "${RECORD_NAME}." \
--zone=${{ env.DNS_ZONE }} \
--type=A \
--project ${{ env.PROJECT_ID }}
echo "✅ DNSレコード ${RECORD_NAME} が正常に削除されました"
else
echo "ℹ️ DNSレコード ${RECORD_NAME} が見つかりません"
fi
これらのワークフローにより、開発者がプルリクエストに dev
ラベルを付けるだけで自動的に検証環境が構築されます。また、プルリクエストがクローズされると自動的に環境がクリーンアップされます。
おわりに
本記事では、Cloud Run を活用したプルリクエスト単位の検証環境構築方法について解説しました。
この仕組みを導入することで、以下のようなメリットが得られます:
- 各プルリクエストに対して独立した検証環境が自動的に構築されるため、並行開発がスムーズになります
- レビュアーが実際の動作を確認しながらレビューできるため、フィードバックが効率化されます
- プルリクエストがクローズされると自動的に環境がクリーンアップされるため、リソースの無駄がありません
- 本番と同等の環境でテストできるため、環境差異によるバグを開発初期段階で発見可能になります
必要に応じて各チームの要件に合わせてカスタマイズしていただければ幸いです。
最後までお読みいただき、ありがとうございました。この記事が皆様のプロジェクトの参考になれば幸いです。
Discussion