Cloud Storage + LB + Cloud CDN の静的 Web サイトをホスティングする
初めに
前提
すでにdev環境は構築済みです。
フロントエンド、APIなどをまるっと管理をしたかったのでTurborepo構成になっておりますがそこは今回あまり気にしなくても大丈夫です。
今からdev環境で作成したことを振り返りながら本番環境の実装を進めていきたいと思います。
なお、細かな設定で誤りもあるかと思うので実際に本番で実装をする際は周りの有識者に確認をしながら実装をすることをお勧めいたします。
今回構築するインフラ構成
この記事では、以下のようなGCPインフラを構築していきます:
主要コンポーネント
フロントエンド配信
- Cloud Storage: 静的ファイル(HTML/CSS/JS)のホスティング
- Cloud CDN: 世界中からの高速配信とキャッシュ
- Global Load Balancer: HTTP/HTTPSトラフィックの処理
セキュリティ
- Certificate Manager: SSL/TLS証明書の自動取得・更新
- HTTPS強制: HTTPアクセスの自動HTTPS転送
- Workload Identity Federation: Service Accountキー不要の安全な認証
CI/CD
- GitHub Actions: 自動ビルド・デプロイ
- 最小権限: 必要最小限の権限のみ付与
Terraformのディレクトリ構成
以下のような構成で作成をしたいと思います。インフラをあまり普段触らないので、もっといいやり方があるのかもしれませんが、環境ごとに影響をしあうのが恐いのと、環境変数で条件分岐を書くようなことをしたくなかったため、下記のような構成にしてみました。
.
├── gcp # もしawsなどの別のプロバイダーが増えた場合は別で同じようなディレクトリを作るイメージ
│ └── environments # 環境ごとにgcpのprojectを作成をする
│ ├── dev # こちらは既に実装済み
│ ├── prd
│ │ ├── backend # terraformのバックエンド用の設定
│ │ ├── dashboard # 管理画面 <= ⭐️ここをメインで作る
│ │ ├── internal # 社内管理画面
│ │ └── shared # 共通のリソース等(IAMなど)
構築する
事前準備
必要なツールのインストール
まず、以下のツールがインストールされていることを確認してください:
# Google Cloud SDK
gcloud --version
# Terraform
terraform --version
# Node.js (GitHub Actions用)
node --version
インストールされていない場合は、各公式サイトからインストールしてください。
gcloud CLIの初期設定
1. gcloud CLIの初期化
gcloud init
このコマンドで、Googleアカウントにログインし、デフォルトプロジェクトを設定できます。
2. 現在の設定を確認
gcloud config configurations list
設定済みのGoogleアカウント一覧が表示されます。
3. 本番環境用の設定作成(設定がない場合)
本番環境用の専用設定を作成します:
# 新しい設定を作成
gcloud config configurations create terraform-gcp-prod
# 作成した設定をアクティブにする
gcloud config configurations activate terraform-gcp-prod
# アカウントでログイン
gcloud auth login
# プロジェクトを設定(後でGCPでプロジェクトを作成した後に実行)
gcloud config set project [your-project-id]
# プロジェクトを設定(後でGCPでプロジェクトを作成した後に実行)
gcloud config set account [account email]
4. 既存設定の切り替え
既に本番環境用の設定がある場合は、切り替えるだけで済みます:
gcloud config configurations activate terraform-gcp-prod
5. Application Default Credentials の設定
Terraformが認証情報を使用できるように設定します:
gcloud auth application-default login
このコマンドで、ブラウザが開いてGoogleアカウントでの認証が求められます。本番環境用のGoogleアカウントでログインしてください。
設定の確認
最終的に以下のコマンドで設定を確認します:
# アクティブな設定を確認
gcloud config configurations list
# 現在の認証アカウントを確認
gcloud auth list
# 現在のプロジェクトを確認
gcloud config get-value project
権限の確認
使用するGoogleアカウントに以下の権限があることを確認してください:
- Project Editor または Project Owner 権限
- Billing Account User 権限(課金アカウントが設定されている場合)
- 必要に応じて DNS Administrator 権限(独自ドメインを使用する場合)
何をしているか
これらの設定により以下のことが実現されます:
- 環境分離: 開発環境と本番環境のアカウントを分離
- 認証管理: Terraformが適切なアカウントでGCPリソースを操作
- セキュリティ: 本番環境専用の認証情報で安全な操作
- プロジェクト管理: 複数プロジェクトを効率的に管理
GCPプロジェクトの作成と設定
プロジェクトの作成
GUIでの作成
GCPコンソールでプロジェクトを作成します:
- GCP Console にアクセス
- 上部のプロジェクト選択ドロップダウンから「新しいプロジェクト」を選択
- プロジェクト名を入力(例:
terraform-gcp-prod
) - 組織を選択(該当する場合)
- 「作成」をクリック
プロジェクト名を入力すると自動でproject idが作成されます。
今回は プロジェクト ID: terraform-gcp-prod-468022
というIDで作成されました。
課金アカウントの設定
重要: GCPリソースを使用するには課金アカウントの設定が必要です。
課金アカウントの確認
# 利用可能な課金アカウントを確認
gcloud billing accounts list
プロジェクトに課金アカウントを関連付け
# プロジェクトに課金アカウントを設定
gcloud billing projects link your-project-id --billing-account=BILLING_ACCOUNT_ID
GUIで設定する場合:
- GCPコンソールで「お支払い」メニューを選択
- 「プロジェクトの管理」から対象プロジェクトを選択
- 課金アカウントを関連付け
プロジェクト設定の完了
作成したプロジェクトをgcloud CLIに設定します:
# プロジェクトを設定
gcloud config set project your-project-id
# 設定を確認
gcloud config get-value project
必要なAPIの事前確認
後のステップで以下のAPIを有効化します(事前確認のみ):
- Compute Engine API
- Certificate Manager API
- IAM Service Account Credentials API
これらはTerraformで自動的に有効化されるため、手動での設定は不要です。
ドメインの準備(オプション)
独自ドメインを使用する場合は、事前に以下の準備が必要です:
ドメインの取得
以下のようなドメイン登録サービスでドメインを取得してください:
- Google Domains
- お名前.com
- ムームードメイン(今回はこれを使いました)
- Route 53 (AWS)
- Cloudflare
DNS設定の準備
後のステップで以下のAレコードを設定する必要があります:
レコードタイプ: A
ホスト名: dashboard (またはサブドメイン名)
値: <Terraformで作成される静的IPアドレス>
TTL: 300秒(5分)
注意: DNS設定は後のステップで行うため、この段階では準備のみで問題ありません。
Step 1: backendを作成をする
これで、本番環境用のTerraform State管理用のCloud Storageバケットが作成されます。現在は手元で実行していますが、このバケットは将来的にCI/CDパイプラインでTerraformの状態管理に使用されます。
ディレクトリ作成
まず、backend用のディレクトリを作成します:
mkdir -p apps/terraform/gcp/environments/prod/backend
Terraformファイルの作成
backendディレクトリに以下のファイルを作成します。各ファイルの役割は以下の通りです:
- main.tf: メインのリソース定義(Cloud StorageバケットとIAM設定)
- variables.tf: 変数の定義(プロジェクトID、リージョンなど)
- terraform.tfvars: 変数の実際の値(環境固有の設定)
- outputs.tf: 他のモジュールで使用する出力値の定義
variables.tf
# プロジェクトIDの変数定義
variable "project_id" {
description = "GCP Project ID"
type = string
}
# リージョンの変数定義(デフォルト値を設定)
variable "region" {
description = "GCP Region for resources"
type = string
default = "asia-northeast1" # 東京リージョンをデフォルトに設定
}
terraform.tfvars
# GCP Project Configuration for Production Environment
project_id = "terraform-gcp-prod-468022" # 本番環境用プロジェクトID
region = "asia-northeast1" # 東京リージョン
main.tf
# GCS Backend Bucket for Production Environment Terraform State Management
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# Terraformの状態ファイル用GCSバケット
resource "google_storage_bucket" "terraform_state" {
name = "${var.project_id}-terraform-state" # プロジェクトIDを含む一意なバケット名
location = var.region
force_destroy = false # 誤った削除を防ぐ(本番環境では重要)
versioning {
enabled = true # 状態ファイルの履歴管理のため
}
public_access_prevention = "enforced" # セキュリティのためパブリックアクセスを禁止
uniform_bucket_level_access = true # 統一されたバケットレベルのアクセス制御
lifecycle_rule {
condition {
age = 100 # 100日後に古いバージョンを削除
}
action {
type = "Delete"
}
}
labels = {
purpose = "terraform-state" # バケットの用途を明確化
managed_by = "terraform"
}
}
# バケットのIAM設定(プロジェクトのオーナーのみアクセス可能)
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam
resource "google_storage_bucket_iam_binding" "terraform_state_admin" {
bucket = google_storage_bucket.terraform_state.name
role = "roles/storage.objectAdmin" # オブジェクトの読み書き権限
members = [
"projectOwner:${var.project_id}", # 学習用のため今回はプロジェクトオーナーのみとする
]
}
outputs.tf
# バケット名の出力(他のモジュールで参照可能)
output "terraform_state_bucket_name" {
description = "Name of the GCS bucket for Terraform state"
value = google_storage_bucket.terraform_state.name
}
# バケットURLの出力(他のモジュールで参照可能)
output "terraform_state_bucket_url" {
description = "URL of the GCS bucket for Terraform state"
value = google_storage_bucket.terraform_state.url
}
Terraformの実行
backendディレクトリに移動してTerraformを実行します:
cd apps/terraform/gcp/environments/prod/backend
terraform init # Terraformの初期化(プロバイダーのダウンロードなど)
terraform plan # 実行計画の確認(何が作成されるかを事前確認)
terraform apply # 実際のリソース作成
以下がでたら成功です🎉
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
これで、Terraform State管理用のCloud Storageバケットが作成されます。
Step 2: Sharedリソースの設定
backendの設定が完了したので、次にプロジェクト全体で使用する共通リソースを設定していきます。API有効化はプロジェクト全体の設定なので、sharedディレクトリで管理します。
ディレクトリ作成
まず、shared用のディレクトリを作成します:
mkdir -p apps/terraform/gcp/environments/prod/shared
変数定義ファイルの作成
sharedディレクトリに変数定義を作成します。
variables.tf
# プロジェクトIDの変数定義
variable "project_id" {
description = "GCP Project ID"
type = string
}
# リージョンの変数定義
variable "region" {
description = "GCP Region"
type = string
default = "asia-northeast1"
}
terraform.tfvars
# Shared Configuration
project_id = "terraform-gcp-prod-468022"
# 他の変数はvariables.tfのdefault値を使用
Terraform State管理の設定
Step 1で作成したCloud Storageバケットを使用してTerraform Stateを管理します。
backend.tf
# Shared Terraform State Configuration
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
# Terraform State管理設定
backend "gcs" {
bucket = "terraform-gcp-prod-468022-terraform-state" # Step 1で作成したバケット名
prefix = "shared" # このモジュール用のプレフィックス
}
}
provider "google" {
project = var.project_id
region = var.region
}
API有効化リソースの作成
プロジェクト全体で使用するAPIを有効化します。
main.tf
# Shared Infrastructure Configuration for Production Environment
# ======================================
# Enable Required APIs
# ======================================
# Enable Compute Engine API
# ロードバランサー、グローバルIPアドレス、URLマップなどの作成に必要なCompute Engine APIを有効化
# 静的IPアドレスの取得やHTTPS/HTTPプロキシの作成に使用される
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service
resource "google_project_service" "compute_api" {
service = "compute.googleapis.com"
disable_dependent_services = true
disable_on_destroy = false
}
# Enable Certificate Manager API
# Google Certificate Managerを使用してSSL証明書を自動管理するためのAPIを有効化
# これにより、ドメインの証明書を自動取得・更新できるようになる
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service
resource "google_project_service" "certificate_manager_api" {
service = "certificatemanager.googleapis.com"
disable_dependent_services = true
disable_on_destroy = false
}
# Enable IAM Service Account Credentials API
# Workload Identity Federationを使用するために必要なAPIを有効化
# GitHub ActionsがService Accountの一時的な認証情報を取得するために使用される
# dashboardとinternalの両方のモジュールで使用される可能性があるためsharedに配置
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service
resource "google_project_service" "iam_credentials_api" {
service = "iamcredentials.googleapis.com"
disable_dependent_services = true
disable_on_destroy = false
}
ドキュメント
この設定により、以下の対応をしました:
- 責任の分離: API有効化はshared、アプリケーション設定はdashboard
- 再利用性: 他のモジュール(internal等)でも同じAPIを使用可能
- 管理の簡素化: プロジェクト全体の設定を一元管理
- 将来の拡張性: internalモジュール追加時もWorkload Identity Federationが即座に利用可能
なお、有効にしたいAPIのサービス名を確認をする場合は、下記のようにGUIで確認が可能です。
Sharedリソースの作成実行
sharedディレクトリに移動してTerraformを実行します:
cd apps/terraform/gcp/environments/prod/shared
terraform init # Terraform初期化
terraform plan # 実行計画確認
terraform apply # リソース作成
以下がでたら成功です🎉
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
作成されたリソース
実行結果から、以下のAPIが正常に有効化されました:
-
Compute Engine API:
compute.googleapis.com
-
Certificate Manager API:
certificatemanager.googleapis.com
-
IAM Service Account Credentials API:
iamcredentials.googleapis.com
これらのAPIは、dashboardリソースの作成とWorkload Identity Federationに必要です。特にIAM Service Account Credentials APIは、将来のinternalモジュールでも共通して使用されます。
Step 3: Dashboardリソースの設定
sharedの設定が完了したので、次にDashboardリソースを設定していきます。dev環境の設定をベースに、本番環境特有の要件(セキュリティ強化、監視強化、パフォーマンス最適化)を含めた実装を行います。
ディレクトリ作成
まず、dashboard用のディレクトリを作成します:
mkdir -p apps/terraform/gcp/environments/prod/dashboard
変数定義ファイルの作成
まず、変数定義を作成します。これにより、環境固有の設定値を一元管理できます。default値を設定することで、terraform.tfvarsでの記述を最小限に抑えることができます。
variables.tf
# プロジェクトIDの変数定義
variable "project_id" {
description = "GCP Project ID"
type = string
}
# リージョンの変数定義
variable "region" {
description = "GCP Region"
type = string
default = "asia-northeast1"
}
# ドメイン名の変数定義
variable "domain_name" {
description = "Domain Name for Dashboard"
type = string
default = "dashboard.my-learn-iac-sample.site"
}
# バケット名の変数定義
variable "bucket_name" {
description = "Cloud Storage Bucket Name for Dashboard"
type = string
default = "terraform-gcp-prod-468022-dashboard-frontend"
}
# キャッシュTTL設定
variable "cdn_cache_ttl" {
description = "CDN Cache TTL (in seconds)"
type = number
default = 3600 # 1時間(より長いキャッシュ時間を設定)
}
terraform.tfvars
# Dashboard Configuration
project_id = "terraform-gcp-prod-468022"
# 他の変数はvariables.tfのdefault値を使用
Terraform State管理の設定
Step 1で作成したCloud Storageバケットを使用してTerraform Stateを管理します。これにより、チーム開発やCI/CDパイプラインでの状態管理が可能になります。
backend.tf
# Dashboard Terraform State Configuration
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
# Terraform State管理設定
backend "gcs" {
bucket = "terraform-gcp-prod-468022-terraform-state" # Step 1で作成したバケット名
prefix = "dashboard" # このモジュール用のプレフィックス
}
}
provider "google" {
project = var.project_id
region = var.region
}
この設定により、以下の対応をしました:
- State管理の一元化: Step 1で作成したCloud StorageバケットでTerraform Stateを管理
- チーム開発対応: 複数の開発者が同じStateを参照可能
- CI/CD対応: 将来的にCI/CDパイプラインでの自動化が可能
-
環境分離:
prefix = "dashboard"
により、他のモジュールとStateを分離
リソースの作成
次に、Dashboardリソースを一つずつ作成していきます。
1. Cloud Storageバケットの作成
まず、静的ファイルを格納するCloud Storageバケットを作成します。
# Cloud Storageバケット(静的ウェブサイトホスティング)
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket
resource "google_storage_bucket" "dashboard_frontend" {
name = var.bucket_name
location = var.region
force_destroy = false # 誤った削除を防ぐ
# 静的ウェブサイトホスティング用の設定
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
# セキュリティ設定
public_access_prevention = "inherited"
uniform_bucket_level_access = true
# ラベル設定
labels = {
purpose = "dashboard-frontend"
environment = "production"
managed_by = "terraform"
}
}
# バケットをパブリックに設定
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam
resource "google_storage_bucket_iam_member" "dashboard_frontend_public" {
bucket = google_storage_bucket.dashboard_frontend.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
ドキュメント
このリソースにより、以下のことが実現されます:
- 静的ファイルホスティング: Reactアプリケーションのビルドファイルを格納
-
セキュリティ強化:
force_destroy = false
で誤った削除を防止 - 管理性向上: ラベルによりリソースの用途を明確化
リソースの作成実行
設定したリソースを実際に作成していきます。まず、dashboardディレクトリに移動してTerraformを実行します:
cd apps/terraform/gcp/environments/prod/dashboard
terraform init # Terraform初期化
terraform plan # 実行計画確認
terraform apply # リソース作成
以下がでたら成功です🎉
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
作成されたリソース
実行結果から、以下のリソースが正常に作成されました:
-
Cloud Storageバケット:
terraform-gcp-prod-468022-dashboard-frontend
- 場所: ASIA-NORTHEAST1(東京リージョン)
- セキュリティ設定: パブリックアクセス防止、統一バケットレベルアクセス
- ラベル: environment=production, managed_by=terraform, purpose=dashboard-frontend
このバケットは、ダッシュボードの静的ファイル(HTML、CSS、JavaScript)を格納するために使用されます。
なお、この時にterraform-gcp-prod-468022-terraform-state/dashboard/default.tfstate
に今回記録をしているdashboardのリソース情報も作成をされています。
2. 静的ウェブサイト設定とパブリックアクセス設定
Cloud Storageバケットを静的ウェブサイトとして設定し、パブリックアクセスを許可します。
上記のmain.tfファイルには以下の設定が含まれています:
-
静的ウェブサイト設定:
website
ブロックでindex.htmlとnot_found_pageを設定 -
パブリックアクセス設定:
google_storage_bucket_iam_member
でパブリック読み取り権限を付与
この設定により、以下の対応をしました:
- パブリックアクセス: 誰でもダッシュボードにアクセス可能
- セキュリティ: 読み取り専用権限のみ付与
- 可用性向上: 認証なしでアクセス可能
以下のようにバケットの権限タブ内を見るとallUsers
で権限が追加されたことを確認できます
3. CDNバックエンドバケットの作成
Cloud CDNを使用してパフォーマンスを向上させます。
# ... (省略) ...
# CDNバックエンドバケット
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_bucket
resource "google_compute_backend_bucket" "dashboard_frontend" {
name = "dashboard-frontend-backend"
description = "Dashboard Frontend Backend Bucket"
bucket_name = google_storage_bucket.dashboard_frontend.name
enable_cdn = true
# キャッシュポリシー
cdn_policy {
cache_mode = "CACHE_ALL_STATIC"
client_ttl = var.cdn_cache_ttl
default_ttl = var.cdn_cache_ttl
max_ttl = var.cdn_cache_ttl * 2 # 最大TTLは通常の2倍
negative_caching = true
serve_while_stale = 86400 # 24時間
}
}
リソースの作成実行
Terraformを実行します:
terraform plan
terraform apply
以下がでたら成功です🎉
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
作成されたリソース
以下のようにCloud CDNの画面を見るとdashboard-frontend-backend
の名前で作成をされたことが確認ができます。
ドキュメント
このリソースにより、以下のことが実現されます:
- パフォーマンス向上: 静的ファイルをCDNでキャッシュ
- コスト削減: オリジンサーバーへのリクエスト削減
- 可用性向上: CDNエッジでの配信により高速アクセス
- 最適化: 1時間のキャッシュTTLで適切な更新頻度
4. URLマップの作成
ロードバランサーでURLルーティングを設定します。
# ... (省略) ...
# URLマップ
# 参考: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map
resource "google_compute_url_map" "dashboard_frontend" {
name = "dashboard-frontend-url-map"
description = "Dashboard Frontend URL Map"
default_service = google_compute_backend_bucket.dashboard_frontend.self_link
# ホストルール
host_rule {
hosts = [var.domain_name]
path_matcher = "dashboard-frontend-paths"
}
# パスマッチャー
path_matcher {
name = "dashboard-frontend-paths"
default_service = google_compute_backend_bucket.dashboard_frontend.self_link
}
}
ドキュメント
このリソースにより、以下のことが実現されます:
- ドメインルーティング: 特定のドメインへのアクセスを適切にルーティング
- パスベースルーティング: 将来的なAPI統合時の準備
- 拡張性: 新しいパスやサービスの追加が容易
リソースの作成実行
terraform plan
terraform apply
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
ホストとパスのルール
が追加をされたことが確認できました🎉
5. SSL証明書の作成
HTTPS通信のためのSSL証明書を設定します。まず、sharedモジュールで有効化されたAPIを参照するためのデータソースを定義し、その後Certificate Managerのリソースを作成します。
# ... (省略) ...
# ======================================
# Data Sources for Shared Resources
# ======================================
# Reference to Certificate Manager API enabled in shared module
# sharedモジュールで有効化されたCertificate Manager APIを参照
# データソースを使用することで、APIが確実に有効化されてからリソースを作成できる
data "google_project_service" "certificate_manager_api" {
service = "certificatemanager.googleapis.com"
}
#### ドキュメント
- [google_project_service](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project_service)
# Certificate Manager Certificate
# 指定したドメイン名用のSSL/TLS証明書をGoogleが自動で取得・管理
# Let's Encryptなどの認証局から有効な証明書を取得し、自動更新も行う
# HTTPS通信を可能にするために必要で、ブラウザに「安全な接続」として表示される
resource "google_certificate_manager_certificate" "dashboard_frontend" {
name = "dashboard-frontend-cert"
location = "global"
managed {
domains = [var.domain_name]
}
labels = {
purpose = "https-certificate"
managed_by = "terraform"
}
depends_on = [data.google_project_service.certificate_manager_api]
}
#### ドキュメント
- [google_certificate_manager_certificate](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate)
# Certificate Map for Certificate Manager
# 複数の証明書とドメインの関連付けを管理するためのマップを作成
# ロードバランサーが適切な証明書を選択できるようにする仕組み
# 将来的に複数ドメインや証明書を使用する場合の拡張性を提供
resource "google_certificate_manager_certificate_map" "dashboard_frontend" {
name = "dashboard-frontend-cert-map"
description = "Certificate map for dashboard HTTPS"
labels = {
purpose = "certificate-map"
managed_by = "terraform"
}
depends_on = [data.google_project_service.certificate_manager_api]
}
#### ドキュメント
- [google_certificate_manager_certificate_map](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate_map)
# Certificate Map Entry
# 特定のドメイン名と証明書の具体的な関連付けを定義
# このエントリーにより、指定したドメインにアクセスした際に対応する証明書が使用される
# ホスト名ベースの証明書選択を実現する重要な設定
resource "google_certificate_manager_certificate_map_entry" "dashboard_frontend" {
name = "dashboard-frontend-cert-map-entry"
map = google_certificate_manager_certificate_map.dashboard_frontend.name
certificates = [google_certificate_manager_certificate.dashboard_frontend.id]
hostname = var.domain_name
}
#### ドキュメント
- [google_certificate_manager_certificate_map_entry](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate_map_entry)
Data Sourceについて
data "google_project_service"
は、既に有効化されているAPIサービスを参照するためのデータソースです。これにより以下のメリットがあります:
-
依存関係の管理:
depends_on
でAPIが有効化されてからリソース作成を実行 - モジュール間連携: sharedモジュールで有効化されたAPIをdashboardモジュールで参照
- エラー防止: APIが無効な状態でのリソース作成エラーを防止
このリソースにより、以下のことが実現されます:
- セキュリティ向上: HTTPS通信によりデータの暗号化
- 自動管理: 証明書の取得・更新が自動化
- 信頼性向上: ブラウザで「安全な接続」として表示
- SEO効果: HTTPSがSEOランキング要因として考慮
リソースの作成実行
Terraformを実行します:
terraform plan
terraform apply
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
作成されたリソース
Certificate Managerで以下のリソースが作成されました:
-
Certificate:
dashboard-frontend-cert
- ドメイン用の証明書 -
Certificate Map:
dashboard-frontend-cert-map
- 証明書マッピング管理 -
Certificate Map Entry:
dashboard-frontend-cert-map-entry
- ドメインと証明書の関連付け
Certificate Managerのコンソールで確認すると、証明書が作成され、プロビジョニング状態になっています。
6. HTTPS Load Balancer構成の作成
Certificate Managerの設定が完了したので、次にHTTPS通信を処理するLoad Balancer構成を作成します。
# ... (省略) ...
# ======================================
# HTTPS Load Balancer Configuration
# ======================================
# Global Static IP Address for Load Balancer
# HTTPSロードバランサー用のグローバル静的IPアドレスを取得
# ドメインのDNS設定でこのIPアドレスを指定することで、独自ドメインでアクセス可能になる
# 静的IPなのでLoad Balancerが再作成されてもIPアドレスが変わることがない
resource "google_compute_global_address" "dashboard_frontend" {
name = "dashboard-frontend-ip"
description = "Static IP for dashboard HTTPS load balancer"
address_type = "EXTERNAL"
}
#### ドキュメント
- [google_compute_global_address](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address)
# HTTPS Target Proxy
# HTTPS(暗号化)接続を処理するためのプロキシを作成
# SSL/TLS証明書を使用してセキュアな通信を実現
# Certificate Managerで管理された証明書を使用してHTTPS接続を終端する
resource "google_compute_target_https_proxy" "dashboard_frontend" {
name = "dashboard-frontend-https-proxy"
url_map = google_compute_url_map.dashboard_frontend.id
certificate_map = "//certificatemanager.googleapis.com/${google_certificate_manager_certificate_map.dashboard_frontend.id}"
}
#### ドキュメント
- [google_compute_target_https_proxy](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_target_https_proxy)
# HTTPS Forwarding Rule
# インターネットからのHTTPSリクエスト(ポート443)を受け付けるためのフォワーディングルールを作成
# 事前に取得した静的IPアドレスを使用して、一貫性のあるアクセスポイントを提供
# HTTPSプロキシと連携してセキュアな通信を実現し、本番運用に適した設定
resource "google_compute_global_forwarding_rule" "dashboard_frontend_https" {
name = "dashboard-frontend-https-rule"
target = google_compute_target_https_proxy.dashboard_frontend.id
port_range = "443"
ip_protocol = "TCP"
ip_address = google_compute_global_address.dashboard_frontend.address
}
#### ドキュメント
- [google_compute_global_forwarding_rule](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_forwarding_rule)
このリソース構成により、以下のことが実現されます:
- Static IP Address: 固定IPアドレスでDNS設定が安定
- HTTPS Target Proxy: SSL/TLS証明書を使用したセキュアな通信
- Forwarding Rule: ポート443でHTTPSリクエストを受け付け
リソースの作成実行
Terraformを実行します:
terraform plan
terraform apply
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
作成されたリソース
HTTPS Load Balancer構成で以下のリソースが作成されました:
-
Static IP Address:
dashboard-frontend-ip
- 固定IPアドレス -
HTTPS Target Proxy:
dashboard-frontend-https-proxy
- HTTPS接続処理 -
HTTPS Forwarding Rule:
dashboard-frontend-https-rule
- ポート443でのリクエスト受け付け
GCPコンソールのLoad Balancerページで確認すると、HTTPS Load Balancerが正常に作成されていることが確認できます。
以下はVPCのIPアドレス画面で静的IPアドレスも確認ができます。
7. HTTPからHTTPSへのリダイレクト設定
セキュリティ強化のため、HTTPアクセスを自動的にHTTPSにリダイレクトする設定を追加します。
# ... (省略) ...
# ======================================
# HTTP to HTTPS Redirect Configuration
# ======================================
# HTTP to HTTPS redirect URL Map
# HTTPアクセスを自動的にHTTPSにリダイレクトするための専用URLマップ
# セキュアな接続を強制し、ユーザーが間違ってHTTPでアクセスしてもHTTPSに誘導
resource "google_compute_url_map" "https_redirect" {
name = "dashboard-frontend-https-redirect"
description = "URL map to redirect HTTP to HTTPS"
default_url_redirect {
redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
strip_query = false
https_redirect = true
}
}
#### ドキュメント
- [google_compute_url_map](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map)
# HTTP Target Proxy for HTTPS redirect
# HTTP(非暗号化)接続を処理してHTTPSにリダイレクトするためのプロキシを作成
# すべてのHTTPリクエストを自動的にHTTPSにリダイレクトしてセキュアな接続を強制
# リダイレクト専用のURLマップを使用してHTTPS URLに301リダイレクト
resource "google_compute_target_http_proxy" "dashboard_frontend_http" {
name = "dashboard-frontend-http-proxy"
url_map = google_compute_url_map.https_redirect.id
}
#### ドキュメント
- [google_compute_target_http_proxy](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_target_http_proxy)
# HTTP Forwarding Rule
# インターネットからのHTTPリクエスト(ポート80)を受け付けるためのフォワーディングルールを作成
# グローバルに配置され、世界中からのアクセスを受け付ける
# HTTPプロキシに接続してリクエストを処理させて、HTTPSにリダイレクト
resource "google_compute_global_forwarding_rule" "dashboard_frontend_http" {
name = "dashboard-frontend-http-rule"
target = google_compute_target_http_proxy.dashboard_frontend_http.id
port_range = "80"
ip_protocol = "TCP"
ip_address = google_compute_global_address.dashboard_frontend.address
}
#### ドキュメント
- [google_compute_global_forwarding_rule](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_forwarding_rule)
この設定により、以下の通信フローが実現されます:
HTTPアクセスの場合:
HTTP (port 80) → HTTP Target Proxy → URL Map (redirect) → HTTPS (port 443)
HTTPSアクセスの場合:
HTTPS (port 443) → HTTPS Target Proxy → Backend Bucket → Cloud Storage
リソースの作成実行
Terraformを実行します:
terraform plan
terraform apply
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
作成されたリソース
HTTPリダイレクト構成で以下のリソースが作成されました:
-
HTTP Redirect URL Map:
dashboard-frontend-https-redirect
- HTTPからHTTPSへのリダイレクト設定 -
HTTP Target Proxy:
dashboard-frontend-http-proxy
- HTTP接続処理とリダイレクト -
HTTP Forwarding Rule:
dashboard-frontend-http-rule
- ポート80でのHTTPリクエスト受け付け
これにより、同一のStatic IPアドレスでHTTPとHTTPSの両方のトラフィックを処理し、HTTPアクセスは自動的にHTTPSにリダイレクトされます。
8. Outputs設定とDNS情報の取得
DNS設定で必要な情報を取得するために、outputsを設定します。
output "static_ip_address" {
description = "Static IP address for DNS A record"
value = google_compute_global_address.dashboard_frontend.address
}
output "domain_name" {
description = "Domain name to configure"
value = var.domain_name
}
リソースの作成実行
Terraformを実行してoutputsを確認します:
terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
domain_name = "dashboard.my-learn-iac-sample.site"
static_ip_address = "34.111.212.193"
DNS設定に必要な情報
Terraformから以下の情報が取得できました:
-
ドメイン名:
dashboard.my-learn-iac-sample.site
-
Static IP Address:
34.111.212.193
次のステップでは、この情報を使ってドメインプロバイダーでAレコードを設定します。
9. DNS設定(Aレコード登録)
取得したStatic IPアドレスを使って、ドメインプロバイダーでDNSのAレコードを設定します。
設定内容
ドメインプロバイダーの管理画面で以下のAレコードを追加します:
- レコードタイプ: A
-
ホスト名:
dashboard
-
値/IPアドレス:
34.111.212.193
- TTL: 300秒(5分)
DNS設定の確認
DNS設定後は以下のコマンドで反映を確認できます:
nslookup dashboard.my-learn-iac-sample.site
DNS反映には最大48時間ほどかかる場合がありますが、通常は数分〜数時間で反映されます。
DNS反映と証明書プロビジョニング完了後、https://dashboard.my-learn-iac-sample.site
でアクセス可能になります。
10. GitHub Actions用IAM設定
最後に、GitHub ActionsからCloud Storageにデプロイするための最低限のIAM設定を追加します。
# ... (省略) ...
# ======================================
# GitHub Actions IAM Configuration
# ======================================
# Service Account for GitHub Actions to deploy to Cloud Storage
# GitHub ActionsからCloud Storageへのデプロイを行うためのサービスアカウント
# 必要最小限の権限のみを付与して、セキュリティを確保
resource "google_service_account" "github_actions_deployer" {
account_id = "github-actions-dashboard-prod"
display_name = "GitHub Actions Dashboard Deployer (Production)"
description = "Service account for GitHub Actions to deploy dashboard to Cloud Storage in production"
}
#### ドキュメント
- [google_service_account](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account)
# Grant Storage Object Admin role on the specific bucket
# バケット内のオブジェクト(ファイル)の作成・更新・削除権限のみを付与
# バケット自体の設定変更はできないため、セキュリティリスクを最小化
resource "google_storage_bucket_iam_member" "github_actions_storage_object_admin" {
bucket = google_storage_bucket.dashboard_frontend.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.github_actions_deployer.email}"
}
#### ドキュメント
- [google_storage_bucket_iam_binding](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam)
このシンプルな構成により、以下のことが実現されます:
- 最小権限の原則: Cloud Storageバケットの読み書きのみに権限を限定
- セキュリティ強化: バケット自体の設定変更はできない
- デプロイ機能: GitHub ActionsからReactアプリケーションをデプロイ可能
リソースの作成実行
Terraformを実行します:
terraform plan
terraform apply
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
作成されたリソース
GitHub Actions用のIAM設定として以下のリソースが作成されました:
-
Service Account:
github-actions-dashboard-prod
- GitHub Actions専用のサービスアカウント - Storage IAM: Cloud Storageバケットへのオブジェクト管理権限
11. Workload Identity Federation設定
セキュリティ強化のため、Service Account keyの代わりにWorkload Identity Federationを使用した認証設定を追加します。
# ... (省略) ...
# GitHub Actions Workload Identity Federation設定
variable "github_repository" {
description = "GitHub repository in the format 'owner/repo'"
type = string
default = "gonta1026/turborepo-project"
}
# ... (省略) ...
# ======================================
# Workload Identity Federation Configuration
# ======================================
# Workload Identity Pool
# GitHub ActionsからGCPリソースに安全にアクセスするためのWorkload Identity Poolを作成
# Service Account keyを使わずに、一時的なトークンベースの認証を実現
resource "google_iam_workload_identity_pool" "github_actions_pool" {
workload_identity_pool_id = "github-actions-pool-prod"
display_name = "GitHub Actions Pool Prod"
description = "Workload Identity Pool for GitHub Actions in Production"
}
#### ドキュメント
- [google_iam_workload_identity_pool](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool)
# Workload Identity Provider
# GitHub ActionsのOIDCトークンを検証するプロバイダーを設定
# 特定のGitHubリポジトリからのリクエストのみを許可してセキュリティを強化
resource "google_iam_workload_identity_pool_provider" "github_actions_provider" {
workload_identity_pool_id = google_iam_workload_identity_pool.github_actions_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider-prod"
display_name = "GitHub Actions Provider Prod"
description = "OIDC identity pool provider for GitHub Actions in Production"
# プロバイダのマッピングを構成する
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
"attribute.ref" = "assertion.ref"
}
# 特定のリポジトリからのアクセスのみを許可
attribute_condition = "assertion.repository == '${var.github_repository}'"
# OIDCプロバイダー設定
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
#### ドキュメント
- [google_iam_workload_identity_pool_provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider)
# Service Account IAM Policy Binding for Workload Identity
# GitHub ActionsがService Accountを使用できるようにするためのIAMポリシーバインディング
# 指定されたGitHubリポジトリからのアクセスのみを許可
resource "google_service_account_iam_member" "github_actions_workload_identity_user" {
service_account_id = google_service_account.github_actions_deployer.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions_pool.name}/attribute.repository/${var.github_repository}"
}
#### ドキュメント
- [google_service_account_iam_member](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account_iam)
Workload Identity Federationのメリット
この設定により以下のセキュリティメリットが得られます:
- Service Account keyが不要: 長期間有効なキーファイルを管理する必要がない
- 短期間トークン: 一時的なアクセストークンのみ使用
- リポジトリ制限: 指定したGitHubリポジトリからのアクセスのみ許可
- OIDC標準: GitHubの標準的な認証方式を使用
リソースの作成実行
terraform plan
terraform apply
Apply complete! Resources: 1 added, 1 changed, 0 destroyed.
12. GitHub Secrets用の情報出力設定
Workload Identity Federationの設定完了後、GitHub Secretsに必要な情報を出力する設定を追加します。
# ... (省略) ...
# ======================================
# GitHub Secrets Configuration
# ======================================
output "github_secrets_configuration" {
description = "Configuration values needed for GitHub Secrets"
value = {
PROD_SERVICE_ACCOUNT = google_service_account.github_actions_deployer.email
PROD_GCS_BUCKET_NAME = google_storage_bucket.dashboard_frontend.name
PROD_CDN_URL_MAP_NAME = google_compute_url_map.dashboard_frontend.name
PROD_WIF_PROVIDER = google_iam_workload_identity_pool_provider.github_actions_provider.name
}
}
リソースの作成実行とSecrets情報の取得
terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
github_secrets_configuration = {
"PROD_CDN_URL_MAP_NAME" = "dashboard-frontend-url-map"
"PROD_GCS_BUCKET_NAME" = "terraform-gcp-prod-468022-dashboard-frontend"
"PROD_SERVICE_ACCOUNT" = "github-actions-dashboard-prod@terraform-gcp-prod-468022.iam.gserviceaccount.com"
"PROD_WIF_PROVIDER" = "projects/129315987508/locations/global/workloadIdentityPools/github-actions-pool-prod/providers/github-provider-prod"
}
これで、GitHub ActionsでのWorkload Identity Federationを使った安全なデプロイに必要な全ての情報が取得できました!
Step 4: GitHub Secretsの設定とデプロイ実装
1. 必要なAPIの確認
Workload Identity Federationに必要なIAM Service Account Credentials APIは、Step 2のsharedモジュールで既に有効化済みです。これにより、dashboardとinternalの両方のモジュールでWorkload Identity Federationを使用できます。
2. CDNキャッシュ無効化権限の追加
GitHub ActionsがCDNキャッシュを無効化できるように権限を追加します:
# ... (省略) ...
# Grant Compute Network Admin role for CDN cache invalidation
# CDNキャッシュの無効化に必要な権限を付与
# URLマップへのアクセス権限を含む
resource "google_project_iam_member" "github_actions_compute_network_admin" {
project = var.project_id
role = "roles/compute.networkAdmin"
member = "serviceAccount:${google_service_account.github_actions_deployer.email}"
}
#### ドキュメント
- [google_project_iam_member](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam)
3. GitHub Secretsの設定
GitHub リポジトリの Settings > Secrets and variables > Actions で以下のSecretsを設定します:
-
PROD_SERVICE_ACCOUNT
:github-actions-dashboard-prod@terraform-gcp-prod-468022.iam.gserviceaccount.com
-
PROD_GCS_BUCKET_NAME
:terraform-gcp-prod-468022-dashboard-frontend
-
PROD_CDN_URL_MAP_NAME
:dashboard-frontend-url-map
-
PROD_WIF_PROVIDER
:projects/129315987508/locations/global/workloadIdentityPools/github-actions-pool-prod/providers/github-provider-prod
4. GitHub Actionsワークフローの作成
本番環境用のデプロイワークフローを作成します:
name: Deploy Dashboard to Production
on:
workflow_dispatch:
inputs:
message:
description: "Deployment message"
required: false
type: string
default: "Manual deployment to production environment"
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Workload Identity Federation用のOIDCトークン取得に必要
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22.10.0"
- name: Install dependencies
run: |
rm -rf node_modules package-lock.json
npm install
- name: Build dashboard
run: npm run build --workspace=dashboard
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.PROD_WIF_PROVIDER }}
service_account: ${{ secrets.PROD_SERVICE_ACCOUNT }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Deploy to Cloud Storage
run: |
gsutil -m rsync -r -d ./apps/dashboard/dist gs://${{ secrets.PROD_GCS_BUCKET_NAME }}
gsutil -m setmeta -h "Cache-Control:public, max-age=86400" -r gs://${{ secrets.PROD_GCS_BUCKET_NAME }}/**
- name: Invalidate CDN cache
run: |
gcloud compute url-maps invalidate-cdn-cache ${{ secrets.PROD_CDN_URL_MAP_NAME }} \
--path "/*" \
--global
- name: Deployment summary
run: |
echo "### 🚀 Production Deployment Complete!" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** Production" >> $GITHUB_STEP_SUMMARY
echo "**URL:** https://dashboard.my-learn-iac-sample.site/" >> $GITHUB_STEP_SUMMARY
5. デプロイの実行と結果
mainブランチにpushすることで自動デプロイが実行され、正常に完了しました!
デプロイ結果: https://dashboard.my-learn-iac-sample.site/
まとめ
お疲れ様でした!
Terraformを使用して本番環境のダッシュボードインフラが完成しました🎉
構築したリソース構成
- Cloud Storage - 静的ファイルホスティング
- CDN Backend Bucket - パフォーマンス向上のためのCDN設定
- URL Map - トラフィックルーティング設定
- Certificate Manager - SSL証明書の自動管理
- HTTPS Load Balancer - セキュアな通信の実現
- HTTP Redirect - HTTPからHTTPSへの自動リダイレクト
- DNS設定 - 独自ドメインでのアクセス
- GitHub Actions IAM - 自動デプロイのための権限設定
実現した機能
- ✅ HTTPS対応: SSL証明書による暗号化通信
- ✅ CDN配信: 世界中から高速アクセス
- ✅ 自動リダイレクト: HTTPアクセスをHTTPSに自動転送
- ✅ 独自ドメイン:
dashboard.my-learn-iac-sample.site
でアクセス可能 - ✅ 自動デプロイ: GitHub Actionsでの継続的デプロイメント対応
最後に
今回GCPのインフラリソースをterraformを活用をして触ってみました。
本業はフロントエンドがメインになり、あまり普段触ることがなかったのですが、今後自分のバリューをより発揮ができるように、細かくドキュメントを自分で確認をしてより理解に努めたいと思います。
Discussion