iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0

昨天我們完成了架構規劃,今天要正式開始動手實作~我會先從最基礎的 Infrastructure Layer 開始,建立整個專案的根基。

今天的目標是建立一個穩固的基礎設施,讓後續的平台服務和應用程式能夠安全、高效地運行。

今天的實作目標

Infrastructure Layer 負責提供:

  • GCP 專案基礎設定和 API 啟用
  • 網路架構 (VPC、子網路、防火牆)
  • 服務帳號和 IAM 權限
  • 安全機密管理 (Secret Manager)
  • 負載平衡器基礎配置

讓我們一步步建立這些資源。

專案結構設計

首先建立目錄結構:

mkdir -p order-system-terraform
cd order-system-terraform

mkdir -p infrastructure/{project,networking,security}
mkdir -p platform/{storage,messaging,monitoring}
mkdir -p applications/{web-app,api-services,background-jobs,cicd}
mkdir -p environments/{dev,staging,production}
mkdir -p modules/{microservice,database}
mkdir -p scripts

Part 1: 專案基礎設定

1.1 專案主要配置

*# infrastructure/project/main.tf*
terraform {
  required_version = ">= 1.0"
  
  backend "gcs" {
    bucket = "order-system-terraform-state"
    prefix = "infrastructure"
  }
  
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
  zone    = "${var.region}-a"
}

*# 啟用必要的 GCP API*
resource "google_project_service" "required_apis" {
  for_each = toset([
    "compute.googleapis.com",
    "run.googleapis.com",
    "cloudbuild.googleapis.com",
    "artifactregistry.googleapis.com",
    "secretmanager.googleapis.com",
    "sqladmin.googleapis.com",
    "redis.googleapis.com",
    "pubsub.googleapis.com",
    "monitoring.googleapis.com",
    "logging.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "iam.googleapis.com"
  ])
  
  service = each.key
  
  *# 防止意外刪除*
  disable_on_destroy = false
}

1.2 基礎變數定義

*# infrastructure/project/variables.tf*
variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
  default     = "asia-east1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be dev, staging, or production."
  }
}

variable "organization_id" {
  description = "GCP Organization ID"
  type        = string
  default     = null
}

1.3 專案輸出

*# infrastructure/project/outputs.tf*
output "project_id" {
  description = "GCP Project ID"
  value       = var.project_id
}

output "region" {
  description = "GCP region"
  value       = var.region
}

output "enabled_apis" {
  description = "List of enabled APIs"
  value       = [for api in google_project_service.required_apis : api.service]
}

Part 2: 網路架構

2.1 VPC 和子網路

*# infrastructure/networking/main.tf# 使用官方 VPC 模組*
module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = var.project_id
  network_name = "${var.environment}-vpc"
  
  *# 手動建立子網路*
  auto_create_subnetworks = false
  
  subnets = [
    {
      subnet_name           = "${var.environment}-frontend-subnet"
      subnet_ip             = "10.0.1.0/24"
      subnet_region         = var.region
      subnet_flow_logs      = true
      subnet_private_access = true
      description           = "Frontend services subnet"
    },
    {
      subnet_name           = "${var.environment}-backend-subnet"
      subnet_ip             = "10.0.2.0/24"
      subnet_region         = var.region
      subnet_flow_logs      = true
      subnet_private_access = true
      description           = "Backend services subnet"
    },
    {
      subnet_name           = "${var.environment}-data-subnet"
      subnet_ip             = "10.0.3.0/24"
      subnet_region         = var.region
      subnet_flow_logs      = true
      subnet_private_access = true
      description           = "Database and data services subnet"
    }
  ]
  
  *# 次要 IP 範圍 (用於 GKE 或其他容器服務)*
  secondary_ranges = {
    "${var.environment}-backend-subnet" = [
      {
        range_name    = "pods"
        ip_cidr_range = "10.1.0.0/16"
      },
      {
        range_name    = "services"
        ip_cidr_range = "10.2.0.0/16"
      }
    ]
  }
}

*# 建立 Cloud NAT (讓私有資源可以訪問外網)*
resource "google_compute_router" "nat_router" {
  name    = "${var.environment}-nat-router"
  region  = var.region
  network = module.vpc.network_id
}

resource "google_compute_router_nat" "nat_gateway" {
  name                               = "${var.environment}-nat-gateway"
  router                            = google_compute_router.nat_router.name
  region                            = var.region
  nat_ip_allocate_option            = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"

  log_config {
    enable = true
    filter = "ERRORS_ONLY"
  }
}

2.2 防火牆規則

*# infrastructure/networking/firewall.tf# 使用官方防火牆模組*
module "firewall_rules" {
  source  = "terraform-google-modules/network/google//modules/firewall-rules"
  version = "~> 9.0"

  project_id   = var.project_id
  network_name = module.vpc.network_name

  rules = [
    {
      name        = "${var.environment}-allow-internal"
      description = "Allow internal communication between subnets"
      priority    = 1000
      direction   = "INGRESS"
      ranges      = ["10.0.0.0/8"]
      
      allow = [
        {
          protocol = "tcp"
          ports    = ["0-65535"]
        },
        {
          protocol = "udp"  
          ports    = ["0-65535"]
        },
        {
          protocol = "icmp"
        }
      ]
      
      target_tags = null
      source_tags = null
    },
    {
      name        = "${var.environment}-allow-lb-health-check"
      description = "Allow load balancer health checks"
      priority    = 1000
      direction   = "INGRESS"
      ranges      = ["130.211.0.0/22", "35.191.0.0/16"]
      
      allow = [
        {
          protocol = "tcp"
          ports    = ["80", "443", "8080"]
        }
      ]
      
      target_tags = ["http-server", "https-server"]
      source_tags = null
    },
    {
      name        = "${var.environment}-allow-ssh"
      description = "Allow SSH for debugging (restricted IP ranges)"
      priority    = 1000
      direction   = "INGRESS"
      ranges      = var.allowed_ssh_ranges
      
      allow = [
        {
          protocol = "tcp"
          ports    = ["22"]
        }
      ]
      
      target_tags = ["ssh-server"]
      source_tags = null
    },
    {
      name        = "${var.environment}-allow-web-traffic"
      description = "Allow web traffic from internet"
      priority    = 1000
      direction   = "INGRESS"
      ranges      = ["0.0.0.0/0"]
      
      allow = [
        {
          protocol = "tcp"
          ports    = ["80", "443"]
        }
      ]
      
      target_tags = ["http-server", "https-server"]
      source_tags = null
    }
  ]
}

2.3 負載平衡器準備

*# infrastructure/networking/load-balancer.tf 
# 全域靜態 IP (用於負載平衡器)*
resource "google_compute_global_address" "lb_ip" {
  name = "${var.environment}-lb-ip"
}

*# SSL 憑證 (如果有自定義網域)*
resource "google_compute_managed_ssl_certificate" "ssl_cert" {
  count = var.custom_domain != null ? 1 : 0
  
  name = "${var.environment}-ssl-cert"
  
  managed {
    domains = [var.custom_domain]
  }
  
  lifecycle {
    create_before_destroy = true
  }
}

2.4 網路變數和輸出

*# infrastructure/networking/variables.tf*
variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "allowed_ssh_ranges" {
  description = "IP ranges allowed for SSH access"
  type        = list(string)
  default     = [] *# 生產環境應該設定特定 IP 範圍*
}

variable "custom_domain" {
  description = "Custom domain for SSL certificate"
  type        = string
  default     = null
}

*# infrastructure/networking/outputs.tf*
output "network_id" {
  description = "VPC network ID"
  value       = module.vpc.network_id
}

output "network_name" {
  description = "VPC network name"
  value       = module.vpc.network_name
}

output "subnet_names" {
  description = "List of subnet names"
  value       = module.vpc.subnets_names
}

output "subnet_ips" {
  description = "List of subnet IP ranges"
  value       = module.vpc.subnets_ips
}

output "load_balancer_ip" {
  description = "Load balancer static IP"
  value       = google_compute_global_address.lb_ip.address
}

output "ssl_certificate_id" {
  description = "SSL certificate ID"
  value       = var.custom_domain != null ? google_compute_managed_ssl_certificate.ssl_cert[0].id : null
}

Part 3: 安全和身份管理

3.1 服務帳號

*# infrastructure/security/service-accounts.tf# 應用程式服務帳號*
resource "google_service_account" "app_service_account" {
  account_id   = "${var.environment}-app-sa"
  display_name = "${title(var.environment)} Application Service Account"
  description  = "Service account for application services in ${var.environment}"
}

*# Cloud Build 服務帳號*
resource "google_service_account" "build_service_account" {
  account_id   = "${var.environment}-build-sa"
  display_name = "${title(var.environment)} Build Service Account"  
  description  = "Service account for Cloud Build in ${var.environment}"
}

*# 資料庫服務帳號*
resource "google_service_account" "db_service_account" {
  account_id   = "${var.environment}-db-sa"
  display_name = "${title(var.environment)} Database Service Account"
  description  = "Service account for database access in ${var.environment}"
}

*# 監控服務帳號*
resource "google_service_account" "monitoring_service_account" {
  account_id   = "${var.environment}-monitoring-sa"
  display_name = "${title(var.environment)} Monitoring Service Account"
  description  = "Service account for monitoring and logging in ${var.environment}"
}

3.2 IAM 權限綁定

*# infrastructure/security/iam-bindings.tf# 應用程式服務帳號權限*
resource "google_project_iam_member" "app_sa_roles" {
  for_each = toset([
    "roles/cloudsql.client",              *# 存取 Cloud SQL*
    "roles/secretmanager.secretAccessor", *# 讀取機密*
    "roles/storage.objectViewer",         *# 讀取 Cloud Storage*
    "roles/storage.objectCreator",        *# 建立 Cloud Storage 物件*
    "roles/pubsub.publisher",             *# 發布訊息*
    "roles/pubsub.subscriber",            *# 訂閱訊息*
    "roles/monitoring.metricWriter",      *# 寫入監控指標*
    "roles/logging.logWriter",            *# 寫入日誌*
  ])
  
  project = var.project_id
  role    = each.key
  member  = "serviceAccount:${google_service_account.app_service_account.email}"
}

*# Cloud Build 服務帳號權限*
resource "google_project_iam_member" "build_sa_roles" {
  for_each = toset([
    "roles/cloudbuild.builds.builder",    *# 執行建置*
    "roles/source.reader",                *# 讀取原始碼*
    "roles/storage.admin",                *# 管理 Cloud Storage*
    "roles/artifactregistry.writer",      *# 推送容器映像*
    "roles/run.admin",                    *# 部署 Cloud Run*
    "roles/iam.serviceAccountUser",       *# 使用服務帳號*
  ])
  
  project = var.project_id
  role    = each.key
  member  = "serviceAccount:${google_service_account.build_service_account.email}"
}

*# 資料庫服務帳號權限*
resource "google_project_iam_member" "db_sa_roles" {
  for_each = toset([
    "roles/cloudsql.client",
    "roles/cloudsql.instanceUser",
  ])
  
  project = var.project_id
  role    = each.key
  member  = "serviceAccount:${google_service_account.db_service_account.email}"
}

3.3 機密管理

*# infrastructure/security/secrets.tf
# 資料庫連線機密*
resource "google_secret_manager_secret" "database_url" {
  secret_id = "${var.environment}-database-url"
  
  labels = {
    environment = var.environment
    service     = "database"
  }
  
  replication {
    auto {}
  }
}

*# JWT 金鑰*
resource "google_secret_manager_secret" "jwt_secret" {
  secret_id = "${var.environment}-jwt-secret"
  
  labels = {
    environment = var.environment
    service     = "auth"
  }
  
  replication {
    auto {}
  }
}

*# 第三方 API 金鑰*
resource "google_secret_manager_secret" "payment_api_key" {
  secret_id = "${var.environment}-payment-api-key"
  
  labels = {
    environment = var.environment
    service     = "payment"
  }
  
  replication {
    auto {}
  }
}

*# 通知服務 Webhook URL*
resource "google_secret_manager_secret" "notification_webhook" {
  secret_id = "${var.environment}-notification-webhook"
  
  labels = {
    environment = var.environment
    service     = "notification"
  }
  
  replication {
    auto {}
  }
}

*# Secret 存取權限*
resource "google_secret_manager_secret_iam_member" "app_secret_access" {
  for_each = {
    database_url         = google_secret_manager_secret.database_url.id
    jwt_secret           = google_secret_manager_secret.jwt_secret.id
    payment_api_key      = google_secret_manager_secret.payment_api_key.id
    notification_webhook = google_secret_manager_secret.notification_webhook.id
  }
  
  secret_id = each.value
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.app_service_account.email}"
}

3.4 安全輸出

*# infrastructure/security/outputs.tf*
output "app_service_account_email" {
  description = "Application service account email"
  value       = google_service_account.app_service_account.email
}

output "build_service_account_email" {
  description = "Build service account email"
  value       = google_service_account.build_service_account.email
}

output "db_service_account_email" {
  description = "Database service account email"
  value       = google_service_account.db_service_account.email
}

output "secret_ids" {
  description = "Map of secret IDs"
  value = {
    database_url         = google_secret_manager_secret.database_url.id
    jwt_secret          = google_secret_manager_secret.jwt_secret.id
    payment_api_key     = google_secret_manager_secret.payment_api_key.id
    notification_webhook = google_secret_manager_secret.notification_webhook.id
  }
}

Part 4: Infrastructure 主模組整合

4.1 主要整合檔案

*# infrastructure/main.tf*
terraform {
  required_version = ">= 1.0"
  
  backend "gcs" {
    bucket = "order-system-terraform-state"
    prefix = "infrastructure"
  }
  
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

*# 專案基礎設定*
module "project" {
  source = "./project"
  
  project_id      = var.project_id
  region          = var.region
  environment     = var.environment
  organization_id = var.organization_id
}

*# 網路架構*
module "networking" {
  source = "./networking"
  
  project_id         = var.project_id
  region             = var.region
  environment        = var.environment
  allowed_ssh_ranges = var.allowed_ssh_ranges
  custom_domain      = var.custom_domain
  
  depends_on = [module.project]
}

*# 安全和身份管理*
module "security" {
  source = "./security"
  
  project_id  = var.project_id
  region      = var.region
  environment = var.environment
  
  depends_on = [module.project]
}

4.2 Infrastructure 變數

*# infrastructure/variables.tf*
variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
  default     = "asia-east1"
}

variable "environment" {
  description = "Environment name (dev/staging/production)"
  type        = string
}

variable "organization_id" {
  description = "GCP Organization ID"
  type        = string
  default     = null
}

variable "allowed_ssh_ranges" {
  description = "IP ranges allowed for SSH access"
  type        = list(string)
  default     = []
}

variable "custom_domain" {
  description = "Custom domain for SSL certificate"
  type        = string
  default     = null
}

4.3 Infrastructure 輸出

*# infrastructure/outputs.tf*
output "project_id" {
  description = "GCP Project ID"
  value       = module.project.project_id
}

output "region" {
  description = "GCP region"
  value       = module.project.region
}

*# 網路相關輸出*
output "vpc_id" {
  description = "VPC network ID"
  value       = module.networking.network_id
}

output "vpc_name" {
  description = "VPC network name"  
  value       = module.networking.network_name
}

output "subnet_names" {
  description = "List of subnet names"
  value       = module.networking.subnet_names
}

output "load_balancer_ip" {
  description = "Load balancer static IP"
  value       = module.networking.load_balancer_ip
}

*# 服務帳號輸出*
output "app_service_account_email" {
  description = "Application service account email"
  value       = module.security.app_service_account_email
}

output "build_service_account_email" {
  description = "Build service account email"
  value       = module.security.build_service_account_email
}

output "db_service_account_email" {
  description = "Database service account email"
  value       = module.security.db_service_account_email
}

*# Secret 相關輸出*
output "secret_ids" {
  description = "Map of secret IDs"
  value       = module.security.secret_ids
}

部署操作

開發環境部署

*# 建立開發環境配置*
mkdir -p environments/dev
cd environments/dev

cat > terraform.tfvars << 'EOF'
project_id   = "填入專案 id,較為敏感我就不寫了"
region       = "asia-east1"
environment  = "dev"

# 開發環境允許從任何地方 SSH (測試用)
allowed_ssh_ranges = ["0.0.0.0/0"]

# 沒有自定義網域
custom_domain = null
EOF

cat > main.tf << 'EOF'
module "infrastructure" {
  source = "../../infrastructure"
  
  project_id         = var.project_id
  region             = var.region
  environment        = var.environment
  allowed_ssh_ranges = var.allowed_ssh_ranges
  custom_domain      = var.custom_domain
}
EOF

cat > variables.tf << 'EOF'
variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "allowed_ssh_ranges" {
  description = "IP ranges allowed for SSH access"
  type        = list(string)
}

variable "custom_domain" {
  description = "Custom domain"
  type        = string
}
EOF

cat > outputs.tf << 'EOF'
output "infrastructure" {
  description = "Infrastructure outputs"
  value       = module.infrastructure
}
EOF

然後只需要確認輸出內容是否成功,也可以去 GCP 上親眼確認成功與否喔!!

總結一下:

登愣!今天我成功建立了 Infrastructure Layer:

  • 專案基礎設定 - API 啟用、基本配置
  • VPC 網路架構 - 三個子網路分別給前端、後端、資料層
  • 防火牆安全規則 - 內部通訊、健康檢查、Web 流量
  • 服務帳號管理 - 應用、建置、資料庫、監控用途
  • IAM 權限設定 - 最小權限原則
  • 機密資料管理 - Secret Manager 預先配置
  • 網路基礎建設 - NAT、負載平衡器 IP、SSL 憑證準備

明天我會在這個穩固的基礎上建立 Platform Layer,包含資料庫、快取、訊息佇列等平台服務!


上一篇
Day 25 - Terraform 實作練習:企業級專案架構規劃
下一篇
Day 27 - Terraform 實作練習:Platform Layer
系列文
30 天 Terraform 學習筆記:從零開始的 IaC 實戰27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言