昨天我們完成了架構規劃,今天要正式開始動手實作~我會先從最基礎的 Infrastructure Layer 開始,建立整個專案的根基。
今天的目標是建立一個穩固的基礎設施,讓後續的平台服務和應用程式能夠安全、高效地運行。
Infrastructure Layer 負責提供:
讓我們一步步建立這些資源。
首先建立目錄結構:
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
*# 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
}
*# 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
}
*# 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]
}
*# 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"
}
}
*# 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
}
]
}
*# 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
}
}
*# 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
}
*# 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}"
}
*# 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}"
}
*# 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}"
}
*# 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
}
}
*# 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]
}
*# 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
}
*# 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:
明天我會在這個穩固的基礎上建立 Platform Layer,包含資料庫、快取、訊息佇列等平台服務!