iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0

昨天我們建立了基礎設施,今天要在這個基礎上建立 Platform Layer。這一層提供應用程式需要的共享服務:資料庫、快取、訊息佇列、監控等。Platform Layer 是應用程式和基礎設施之間的橋樑,提供高可用、可擴展的平台服務。

今天的實作目標

Platform Layer 會包含:

  • 儲存服務 (Cloud SQL、Redis、Cloud Storage)
  • 訊息服務 (Pub/Sub Topics 和 Subscriptions)
  • 監控服務 (Logging、Monitoring、Alerting)
  • 平台配置 (跨服務的整合設定)

專案結構

platform/
├── main.tf                    # 主要模組編排
├── variables.tf               # 平台變數
├── outputs.tf                 # 平台輸出
├── storage/                   # 儲存服務
│   ├── cloud-sql.tf           # 主資料庫
│   ├── redis.tf               # 快取資料庫
│   ├── cloud-storage.tf       # 檔案儲存
│   ├── variables.tf
│   └── outputs.tf
├── messaging/                 # 訊息服務
│   ├── pub-sub.tf             # 發布訂閱
│   ├── variables.tf
│   └── outputs.tf
└── monitoring/                # 監控服務
    ├── logging.tf             # 日誌管理
    ├── monitoring.tf          # 指標監控
    ├── alerting.tf            # 告警規則
    ├── variables.tf
    └── outputs.tf

Part 1: 儲存服務

1.1 Cloud SQL 資料庫

# platform/storage/cloud-sql.tf
# 使用官方 Cloud SQL 模組
module "postgresql" {
  source  = "terraform-google-modules/sql-db/google//modules/postgresql"
  version = "~> 18.0"

  name                = "${var.environment}-main-db"
  project_id          = var.project_id
  database_version    = "POSTGRES_15"
  region              = var.region
  zone                = "${var.region}-a"
  
  # 資料庫規格 (根據環境調整)
  tier = var.db_config.tier
  
  # 磁碟設定
  disk_size       = var.db_config.disk_size
  disk_type       = "PD_SSD"
  disk_autoresize = true
  
  # 高可用性設定 (生產環境啟用)
  availability_type = var.environment == "production" ? "REGIONAL" : "ZONAL"
  
  # 備份設定
  backup_configuration = {
    enabled                        = var.db_config.backup_enabled
    start_time                     = "03:00"
    location                       = var.region
    point_in_time_recovery_enabled = var.environment == "production"
    transaction_log_retention_days = var.environment == "production" ? 7 : 1
    backup_retention_settings = {
      retained_backups = var.environment == "production" ? 30 : 7
      retention_unit   = "COUNT"
    }
  }
  
  # 維護視窗
  maintenance_window_day          = 7  *# 週日*
  maintenance_window_hour         = 3  *# 凌晨 3 點*
  maintenance_window_update_track = "stable"
  
  # 網路設定
  ip_configuration = {
    ipv4_enabled                                  = false  *# 不使用公網 IP*
    private_network                               = data.terraform_remote_state.infrastructure.outputs.vpc_id
    enable_private_path_for_google_cloud_services = true
    authorized_networks                           = []
    allocated_ip_range                           = null
  }

  # 資料庫標籤
  user_labels = {
    environment = var.environment
    service     = "database"
    tier        = "platform"
  }
  
  # 刪除保護 (生產環境)
  deletion_protection = var.environment == "production"
  
  # 建立預設資料庫
  additional_databases = [
    {
      name      = "order_system"
      charset   = "UTF8"
      collation = "en_US.UTF8"
    },
    {
      name      = "analytics"  
      charset   = "UTF8"
      collation = "en_US.UTF8"
    }
  ]
  
  # 建立應用程式使用者
  additional_users = [
    {
      name            = "app_user"
      password        = random_password.db_app_password.result
      host            = ""
      random_password = false
    },
    {
      name            = "readonly_user"
      password        = random_password.db_readonly_password.result
      host            = ""  
      random_password = false
    }
  ]
}

# 產生資料庫密碼
resource "random_password" "db_app_password" {
  length  = 20
  special = true
}

resource "random_password" "db_readonly_password" {
  length  = 20
  special = true
}

# 將密碼儲存到 Secret Manager
resource "google_secret_manager_secret_version" "db_app_password" {
  secret      = data.terraform_remote_state.infrastructure.outputs.secret_names.database_url
  secret_data = "postgresql://app_user:${random_password.db_app_password.result}@${module.postgresql.private_ip_address}:5432/order_system"
}

resource "google_secret_manager_secret_version" "db_readonly_password" {
  secret      = google_secret_manager_secret.db_readonly_connection.secret_id
  secret_data = "postgresql://readonly_user:${random_password.db_readonly_password.result}@${module.postgresql.private_ip_address}:5432/order_system"
}

# 唯讀連線的機密
resource "google_secret_manager_secret" "db_readonly_connection" {
  secret_id = "${var.environment}-database-readonly-url"
  
  labels = {
    environment = var.environment
    service     = "database"
    access_type = "readonly"
  }
  
  replication {
    auto {}
  }
}

1.2 Redis 快取

# platform/storage/redis.tf
# Redis 記憶體儲存體執行個體
resource "google_redis_instance" "cache" {
  name           = "${var.environment}-redis-cache"
  memory_size_gb = var.redis_config.memory_size_gb
  region         = var.region
  
  # Redis 版本
  redis_version = "REDIS_7_0"
  
  # 網路設定
  authorized_network      = data.terraform_remote_state.infrastructure.outputs.vpc_id
  connect_mode           = "PRIVATE_SERVICE_ACCESS"
  
  *# 高可用性 (生產環境啟用)*
  tier = var.environment == "production" ? "STANDARD_HA" : "BASIC"
  
  # 維護政策
  maintenance_policy {
    weekly_maintenance_window {
      day = "SUNDAY"
      start_time {
        hours   = 3
        minutes = 0
      }
    }
  }
  
  # 標籤
  labels = {
    environment = var.environment
    service     = "cache"
    tier        = "platform"
  }
  
  # 生產環境啟用備份
  dynamic "persistence_config" {
    for_each = var.environment == "production" ? [1] : []
    
    content {
      persistence_mode    = "RDB"
      rdb_snapshot_period = "TWENTY_FOUR_HOURS"
    }
  }
}

# 將 Redis 連線資訊儲存到 Secret Manager
resource "google_secret_manager_secret" "redis_connection" {
  secret_id = "${var.environment}-redis-connection"
  
  labels = {
    environment = var.environment
    service     = "cache"
  }
  
  replication {
    auto {}
  }
}

resource "google_secret_manager_secret_version" "redis_connection" {
  secret      = google_secret_manager_secret.redis_connection.secret_id
  secret_data = "redis://${google_redis_instance.cache.host}:${google_redis_instance.cache.port}"
}

1.3 Cloud Storage

# platform/storage/cloud-storage.tf
# 應用程式檔案儲存
resource "google_storage_bucket" "app_storage" {
  for_each = var.storage_buckets
  
  name     = "${var.project_id}-${var.environment}-${each.key}"
  location = var.region
  
  # 儲存類別
  storage_class = each.value.storage_class
  
  # 版本控制 (依需要啟用)
  versioning {
    enabled = each.value.versioning_enabled
  }
  
  # 生命週期管理
  dynamic "lifecycle_rule" {
    for_each = each.value.lifecycle_rules
    
    content {
      condition {
        age = lifecycle_rule.value.age_days
      }
      action {
        type          = lifecycle_rule.value.action
        storage_class = lifecycle_rule.value.storage_class
      }
    }
  }
  
  # CORS 設定 (前端檔案上傳用)
  dynamic "cors" {
    for_each = each.value.enable_cors ? [1] : []
    
    content {
      origin          = ["*"]  *# 生產環境應該限制特定網域*
      method          = ["GET", "HEAD", "PUT", "POST", "DELETE"]
      response_header = ["*"]
      max_age_seconds = 3600
    }
  }
  
  # 統一 bucket 層級存取
  uniform_bucket_level_access = true
  
  # 標籤
  labels = {
    environment = var.environment
    service     = "storage"
    tier        = "platform"
    purpose     = each.value.purpose
  }
  
  # 防止意外刪除 (生產環境)
  lifecycle {
    prevent_destroy = var.environment == "production"
  }
}

# 設定 bucket IAM 權限
resource "google_storage_bucket_iam_member" "app_storage_access" {
  for_each = var.storage_buckets
  
  bucket = google_storage_bucket.app_storage[each.key].name
  role   = each.value.access_role
  member = "serviceAccount:${data.terraform_remote_state.infrastructure.outputs.app_service_account_email}"
}

# 公開讀取的 bucket (靜態資源用)
resource "google_storage_bucket_iam_member" "public_read" {
  for_each = {
    for k, v in var.storage_buckets : k => v
    if v.public_read
  }
  
  bucket = google_storage_bucket.app_storage[each.key].name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

1.4 儲存服務變數和輸出

# platform/storage/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 "db_config" {
  description = "Database configuration"
  type = object({
    tier            = string
    disk_size       = number
    backup_enabled  = bool
  })
}

variable "redis_config" {
  description = "Redis configuration"
  type = object({
    memory_size_gb = number
  })
}

variable "storage_buckets" {
  description = "Storage bucket configurations"
  type = map(object({
    storage_class        = string
    versioning_enabled   = bool
    enable_cors          = bool
    public_read          = bool
    access_role          = string
    purpose              = string
    lifecycle_rules = list(object({
      age_days      = number
      action        = string
      storage_class = string
    }))
  }))
}

# platform/storage/outputs.tf
output "database_instance_name" {
  description = "Cloud SQL instance name"
  value       = module.postgresql.instance_name
}

output "database_private_ip" {
  description = "Database private IP address"
  value       = module.postgresql.private_ip_address
  sensitive   = true
}

output "redis_host" {
  description = "Redis host address"
  value       = google_redis_instance.cache.host
  sensitive   = true
}

output "redis_port" {
  description = "Redis port"
  value       = google_redis_instance.cache.port
}

output "storage_bucket_names" {
  description = "Created storage bucket names"
  value = {
    for k, v in google_storage_bucket.app_storage : k => v.name
  }
}

output "storage_bucket_urls" {
  description = "Storage bucket URLs"
  value = {
    for k, v in google_storage_bucket.app_storage : k => v.url
  }
}

Part 2: 訊息服務

2.1 Pub/Sub Topics 和 Subscriptions

# platform/messaging/pub-sub.tf
# 訂單事件 Topic
resource "google_pubsub_topic" "order_events" {
  name = "${var.environment}-order-events"
  
  labels = {
    environment = var.environment
    service     = "messaging"
    purpose     = "order-processing"
  }
  
  # 訊息保留時間
  message_retention_duration = "604800s"  *# 7 天*
}

# 訂單事件訂閱 (訂單服務)
resource "google_pubsub_subscription" "order_processing" {
  name  = "${var.environment}-order-processing-sub"
  topic = google_pubsub_topic.order_events.name
  
  # 訊息確認截止時間
  ack_deadline_seconds = 20
  
  # 重試政策
  retry_policy {
    minimum_backoff = "10s"
    maximum_backoff = "600s"
  }
  
  # 死信佇列
  dead_letter_policy {
    dead_letter_topic     = google_pubsub_topic.dead_letter.id
    max_delivery_attempts = 3
  }
  
  labels = {
    environment = var.environment
    service     = "notification-service"
  }
}

# 死信佇列 Topic
resource "google_pubsub_topic" "dead_letter" {
  name = "${var.environment}-dead-letter"
  
  labels = {
    environment = var.environment
    service     = "messaging"
    purpose     = "dead-letter"
  }
  
  # 死信訊息保留較長時間以便分析
  message_retention_duration = "2592000s"  *# 30 天*
}

# 死信佇列訂閱 (用於監控和分析)
resource "google_pubsub_subscription" "dead_letter_monitoring" {
  name  = "${var.environment}-dead-letter-monitoring-sub"
  topic = google_pubsub_topic.dead_letter.name
  
  # 較長的確認時間,因為需要人工處理
  ack_deadline_seconds = 600
  
  labels = {
    environment = var.environment
    service     = "monitoring"
  }
}

# 設定 Pub/Sub IAM 權限
resource "google_pubsub_topic_iam_member" "app_publisher" {
  for_each = {
    "order_events"        = google_pubsub_topic.order_events.name
    "payment_events"      = google_pubsub_topic.payment_events.name
    "notification_events" = google_pubsub_topic.notification_events.name
  }
  
  topic  = each.value
  role   = "roles/pubsub.publisher"
  member = "serviceAccount:${data.terraform_remote_state.infrastructure.outputs.app_service_account_email}"
}

resource "google_pubsub_subscription_iam_member" "app_subscriber" {
  for_each = {
    "order_processing"        = google_pubsub_subscription.order_processing.name
    "payment_processing"      = google_pubsub_subscription.payment_processing.name
    "notification_processing" = google_pubsub_subscription.notification_processing.name
  }
  
  subscription = each.value
  role         = "roles/pubsub.subscriber"
  member       = "serviceAccount:${data.terraform_remote_state.infrastructure.outputs.app_service_account_email}"
}`

2.2 訊息服務變數和輸出

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

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

# platform/messaging/outputs.tf
output "topic_names" {
  description = "Pub/Sub topic names"
  value = {
    order_events        = google_pubsub_topic.order_events.name
    payment_events      = google_pubsub_topic.payment_events.name
    notification_events = google_pubsub_topic.notification_events.name
    dead_letter         = google_pubsub_topic.dead_letter.name
  }
}

output "subscription_names" {
  description = "Pub/Sub subscription names"
  value = {
    order_processing        = google_pubsub_subscription.order_processing.name
    payment_processing      = google_pubsub_subscription.payment_processing.name
    notification_processing = google_pubsub_subscription.notification_processing.name
    dead_letter_monitoring  = google_pubsub_subscription.dead_letter_monitoring.name
  }
}

Part 3: 監控服務

3.1 日誌管理

# platform/monitoring/logging.tf
# 應用程式日誌匯聚
resource "google_logging_project_sink" "app_logs" {
  name        = "${var.environment}-app-logs-sink"
  destination = "storage.googleapis.com/${google_storage_bucket.log_storage.name}"
  
  # 只匯聚應用程式相關日誌
  filter = <<-EOT
    resource.type="cloud_run_revision"
    OR resource.type="cloud_function"
    OR resource.type="cloudsql_database"
    OR (resource.type="pubsub_topic" AND severity>=WARNING)
  EOT
  
  *# 建立唯一的服務帳號*
  unique_writer_identity = true
}

# 日誌儲存 Bucket
resource "google_storage_bucket" "log_storage" {
  name     = "${var.project_id}-${var.environment}-logs"
  location = var.region
  
  # 使用較便宜的儲存類別
  storage_class = "STANDARD"
  
  # 日誌生命週期管理
  lifecycle_rule {
    condition {
      age = 30
    }
    action {
      type          = "SetStorageClass"
      storage_class = "NEARLINE"
    }
  }
  
  lifecycle_rule {
    condition {
      age = 90
    }
    action {
      type          = "SetStorageClass"
      storage_class = "COLDLINE"
    }
  }
  
  lifecycle_rule {
    condition {
      age = 365
    }
    action {
      type = "Delete"
    }
  }
  
  uniform_bucket_level_access = true
  
  labels = {
    environment = var.environment
    service     = "logging"
    tier        = "platform"
  }
}

# 給日誌匯聚服務帳號寫入權限
resource "google_storage_bucket_iam_member" "log_sink_writer" {
  bucket = google_storage_bucket.log_storage.name
  role   = "roles/storage.objectCreator"
  member = google_logging_project_sink.app_logs.writer_identity
}

# 錯誤日誌匯聚 (用於告警)
resource "google_logging_project_sink" "error_logs" {
  name        = "${var.environment}-error-logs-sink"
  destination = "pubsub.googleapis.com/${google_pubsub_topic.error_notifications.id}"
  
  # 只匯聚錯誤和嚴重錯誤
  filter = "severity>=ERROR"
  
  unique_writer_identity = true
}

# 錯誤通知 Topic
resource "google_pubsub_topic" "error_notifications" {
  name = "${var.environment}-error-notifications"
  
  labels = {
    environment = var.environment
    service     = "monitoring"
    purpose     = "error-alerting"
  }
}

# 錯誤通知訂閱
resource "google_pubsub_subscription" "error_alerts" {
  name  = "${var.environment}-error-alerts-sub"
  topic = google_pubsub_topic.error_notifications.name
  
  ack_deadline_seconds = 60
  
  labels = {
    environment = var.environment
    service     = "alerting"
  }
}

# 給日誌匯聚服務帳號發布權限
resource "google_pubsub_topic_iam_member" "error_log_publisher" {
  topic  = google_pubsub_topic.error_notifications.name
  role   = "roles/pubsub.publisher"
  member = google_logging_project_sink.error_logs.writer_identity
}

3.2 指標監控

# platform/monitoring/monitoring.tf
# 自定義指標:應用程式健康狀態
resource "google_monitoring_metric_descriptor" "app_health" {
  type         = "custom.googleapis.com/${var.environment}/app/health_status"
  metric_kind  = "GAUGE"
  value_type   = "BOOL"
  display_name = "${title(var.environment)} App Health Status"
  description  = "Application health check status"
  
  labels {
    key         = "service_name"
    value_type  = "STRING"
    description = "Name of the service"
  }
  
  labels {
    key         = "instance_id"
    value_type  = "STRING"
    description = "Instance identifier"
  }
}

*# 自定義指標:訂單處理率*
resource "google_monitoring_metric_descriptor" "order_rate" {
  type         = "custom.googleapis.com/${var.environment}/orders/processing_rate"
  metric_kind  = "GAUGE"
  value_type   = "DOUBLE"
  display_name = "${title(var.environment)} Order Processing Rate"
  description  = "Number of orders processed per minute"
  
  labels {
    key         = "status"
    value_type  = "STRING"
    description = "Order processing status (success/failed)"
  }
}

*# 自定義指標:資料庫連線池*
resource "google_monitoring_metric_descriptor" "db_connections" {
  type         = "custom.googleapis.com/${var.environment}/database/connection_pool"
  metric_kind  = "GAUGE"
  value_type   = "INT64"
  display_name = "${title(var.environment)} Database Connection Pool"
  description  = "Number of active database connections"
  
  labels {
    key         = "pool_name"
    value_type  = "STRING"
    description = "Connection pool identifier"
  }
}

*# 監控儀表板*
resource "google_monitoring_dashboard" "app_dashboard" {
  dashboard_json = jsonencode({
    displayName = "${title(var.environment)} Application Dashboard"
    
    mosaicLayout = {
      tiles = [
        {
          width  = 6
          height = 4
          widget = {
            title = "Cloud Run Instance Count"
            xyChart = {
              dataSets = [{
                timeSeriesQuery = {
                  timeSeriesFilter = {
                    filter = "resource.type=\"cloud_run_revision\""
                    aggregation = {
                      alignmentPeriod  = "60s"
                      perSeriesAligner = "ALIGN_MEAN"
                    }
                  }
                }
                plotType = "LINE"
              }]
            }
          }
        },
        {
          width  = 6
          height = 4
          xPos   = 6
          widget = {
            title = "Database Connections"
            xyChart = {
              dataSets = [{
                timeSeriesQuery = {
                  timeSeriesFilter = {
                    filter = "resource.type=\"cloudsql_database\""
                    aggregation = {
                      alignmentPeriod  = "60s"
                      perSeriesAligner = "ALIGN_MEAN"
                    }
                  }
                }
                plotType = "LINE"
              }]
            }
          }
        },
        {
          width  = 12
          height = 4
          yPos   = 4
          widget = {
            title = "Error Rate"
            xyChart = {
              dataSets = [{
                timeSeriesQuery = {
                  timeSeriesFilter = {
                    filter = "resource.type=\"cloud_run_revision\" AND severity>=ERROR"
                    aggregation = {
                      alignmentPeriod    = "60s"
                      perSeriesAligner   = "ALIGN_RATE"
                      crossSeriesReducer = "REDUCE_SUM"
                    }
                  }
                }
                plotType = "LINE"
              }]
            }
          }
        }
      ]
    }
  })
}

3.3 告警規則

# platform/monitoring/alerting.tf
# 通知頻道 (電子郵件)
resource "google_monitoring_notification_channel" "email" {
  count = length(var.alert_email_addresses)
  
  display_name = "Email Notification ${count.index + 1}"
  type         = "email"
  
  labels = {
    email_address = var.alert_email_addresses[count.index]
  }
  
  enabled = true
}

# 告警政策:Cloud Run 執行個體健康狀態
resource "google_monitoring_alert_policy" "cloud_run_health" {
  display_name = "${title(var.environment)} Cloud Run Health Check"
  combiner     = "OR"
  enabled      = true
  
  conditions {
    display_name = "Cloud Run instance down"
    
    condition_threshold {
      filter         = "resource.type=\"cloud_run_revision\""
      comparison     = "COMPARISON_LESS_THAN"
      threshold_value = 1
      duration       = "300s"
      
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_MEAN"
      }
    }
  }
  
  notification_channels = google_monitoring_notification_channel.email[*].name
  
  alert_strategy {
    auto_close = "1800s"  *# 30 分鐘後自動關閉*
  }
}

# 告警政策:資料庫連線數過高
resource "google_monitoring_alert_policy" "database_connections" {
  display_name = "${title(var.environment)} Database Connection High"
  combiner     = "OR"
  enabled      = true
  
  conditions {
    display_name = "Too many database connections"
    
    condition_threshold {
      filter         = "resource.type=\"cloudsql_database\""
      comparison     = "COMPARISON_GREATER_THAN"
      threshold_value = var.db_config.max_connections * 0.8  *# 80% 閾值*
      duration       = "300s"
      
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_MEAN"
      }
    }
  }
  
  notification_channels = google_monitoring_notification_channel.email[*].name
}

*# 告警政策:錯誤率過高*
resource "google_monitoring_alert_policy" "error_rate_high" {
  display_name = "${title(var.environment)} High Error Rate"
  combiner     = "OR"
  enabled      = true
  
  conditions {
    display_name = "Application error rate too high"
    
    condition_threshold {
      filter         = "resource.type=\"cloud_run_revision\" AND severity>=ERROR"
      comparison     = "COMPARISON_GREATER_THAN"
      threshold_value = var.environment == "production" ? 10 : 50  *# 生產環境更嚴格*
      duration       = "300s"
      
      aggregations {
        alignment_period     = "60s"
        per_series_aligner   = "ALIGN_RATE"
        cross_series_reducer = "REDUCE_SUM"
      }
    }
  }
  
  notification_channels = google_monitoring_notification_channel.email[*].name
}

*# 告警政策:Pub/Sub 死信佇列訊息累積*
resource "google_monitoring_alert_policy" "dead_letter_queue" {
  display_name = "${title(var.environment)} Dead Letter Queue Messages"
  combiner     = "OR"
  enabled      = true
  
  conditions {
    display_name = "Messages in dead letter queue"
    
    condition_threshold {
      filter         = "resource.type=\"pubsub_subscription\" AND resource.labels.subscription_id=\"${data.terraform_remote_state.platform.outputs.subscription_names.dead_letter_monitoring}\""
      comparison     = "COMPARISON_GREATER_THAN"
      threshold_value = 0
      duration       = "60s"
      
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_MEAN"
      }
    }
  }
  
  notification_channels = google_monitoring_notification_channel.email[*].name
}

*# 告警政策:Redis 記憶體使用率*
resource "google_monitoring_alert_policy" "redis_memory" {
  display_name = "${title(var.environment)} Redis Memory Usage"
  combiner     = "OR"
  enabled      = true
  
  conditions {
    display_name = "Redis memory usage high"
    
    condition_threshold {
      filter         = "resource.type=\"redis_instance\""
      comparison     = "COMPARISON_GREATER_THAN"
      threshold_value = 0.85  *# 85% 記憶體使用率*
      duration       = "300s"
      
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_MEAN"
      }
    }
  }
  
  notification_channels = google_monitoring_notification_channel.email[*].name
}

3.4 監控服務變數和輸出

# platform/monitoring/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 "alert_email_addresses" {
  description = "Email addresses for alerts"
  type        = list(string)
  default     = []
}

variable "db_config" {
  description = "Database configuration for alerting"
  type = object({
    max_connections = number
  })
}

*# platform/monitoring/outputs.tf*
output "dashboard_url" {
  description = "Monitoring dashboard URL"
  value       = "https://console.cloud.google.com/monitoring/dashboards/custom/${google_monitoring_dashboard.app_dashboard.id}?project=${var.project_id}"
}

output "log_bucket_name" {
  description = "Log storage bucket name"
  value       = google_storage_bucket.log_storage.name
}

output "alert_policy_names" {
  description = "Created alert policy names"
  value = [
    google_monitoring_alert_policy.cloud_run_health.display_name,
    google_monitoring_alert_policy.database_connections.display_name,
    google_monitoring_alert_policy.error_rate_high.display_name,
    google_monitoring_alert_policy.dead_letter_queue.display_name,
    google_monitoring_alert_policy.redis_memory.display_name
  ]
}`

Part 4: Platform Layer 主要編排

4.1 讀取基礎設施狀態

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

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

*# 讀取基礎設施狀態*
data "terraform_remote_state" "infrastructure" {
  backend = "gcs"
  config = {
    bucket = "order-system-terraform-state"
    prefix = "infrastructure"
  }
}

*# 儲存服務*
module "storage" {
  source = "./storage"
  
  project_id       = var.project_id
  region           = var.region
  environment      = var.environment
  db_config        = var.db_config
  redis_config     = var.redis_config
  storage_buckets  = var.storage_buckets
}

*# 訊息服務*
module "messaging" {
  source = "./messaging"
  
  project_id  = var.project_id
  environment = var.environment
}

*# 監控服務*
module "monitoring" {
  source = "./monitoring"
  
  project_id            = var.project_id
  region                = var.region
  environment           = var.environment
  alert_email_addresses = var.alert_email_addresses
  db_config = {
    max_connections = var.db_config.tier == "db-f1-micro" ? 100 : 500
  }
  
  depends_on = [module.storage, module.messaging]
}

4.2 Platform Layer 變數

*# platform/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
}

variable "db_config" {
  description = "Database configuration"
  type = object({
    tier            = string
    disk_size       = number
    backup_enabled  = bool
  })
}

variable "redis_config" {
  description = "Redis configuration"
  type = object({
    memory_size_gb = number
  })
}

variable "storage_buckets" {
  description = "Storage bucket configurations"
  type = map(object({
    storage_class        = string
    versioning_enabled   = bool
    enable_cors          = bool
    public_read          = bool
    access_role          = string
    purpose              = string
    lifecycle_rules = list(object({
      age_days      = number
      action        = string
      storage_class = string
    }))
  }))
}

variable "alert_email_addresses" {
  description = "Email addresses for monitoring alerts"
  type        = list(string)
  default     = []
}

4.3 Platform Layer 輸出

*# platform/outputs.tf
# 儲存服務輸出*
output "database_instance_name" {
  description = "Cloud SQL instance name"
  value       = module.storage.database_instance_name
}

output "database_private_ip" {
  description = "Database private IP address"
  value       = module.storage.database_private_ip
  sensitive   = true
}

output "redis_host" {
  description = "Redis host address"
  value       = module.storage.redis_host
  sensitive   = true
}

output "storage_bucket_names" {
  description = "Storage bucket names"
  value       = module.storage.storage_bucket_names
}

*# 訊息服務輸出*
output "pubsub_topics" {
  description = "Pub/Sub topic names"
  value       = module.messaging.topic_names
}

output "pubsub_subscriptions" {
  description = "Pub/Sub subscription names"
  value       = module.messaging.subscription_names
}

*# 監控服務輸出*
output "monitoring_dashboard_url" {
  description = "Monitoring dashboard URL"
  value       = module.monitoring.dashboard_url
}

output "log_bucket_name" {
  description = "Log storage bucket name"
  value       = module.monitoring.log_bucket_name
}

CLI 操作

Step 1: 準備環境配置

*# 建立開發環境的平台配置*
cat > environments/dev/platform.tfvars << 'EOF'
project_id = "order-system-dev-12345"
region     = "asia-east1"
environment = "dev"

# 開發環境:小規格配置
db_config = {
  tier            = "db-f1-micro"
  disk_size       = 20
  backup_enabled  = false
}

redis_config = {
  memory_size_gb = 1
}

# 開發環境儲存配置
storage_buckets = {
  uploads = {
    storage_class        = "STANDARD"
    versioning_enabled   = false
    enable_cors          = true
    public_read          = false
    access_role          = "roles/storage.objectAdmin"
    purpose              = "user-uploads"
    lifecycle_rules      = []
  }
  static = {
    storage_class        = "STANDARD"
    versioning_enabled   = false
    enable_cors          = true
    public_read          = true
    access_role          = "roles/storage.objectViewer"
    purpose              = "static-assets"
    lifecycle_rules      = []
  }
  backups = {
    storage_class        = "NEARLINE"
    versioning_enabled   = true
    enable_cors          = false
    public_read          = false
    access_role          = "roles/storage.objectAdmin"
    purpose              = "backups"
    lifecycle_rules = [
      {
        age_days      = 30
        action        = "SetStorageClass"
        storage_class = "COLDLINE"
      }
    ]
  }
}

# 告警電子郵件
alert_email_addresses = ["devops@company.com"]
EOF

Step 2: 部署 Platform Layer

*# 進入平台目錄*
cd platform

*# 初始化 Terraform*
terraform init

*# 檢查計畫*
terraform plan -var-file="../environments/dev/platform.tfvars"

*# 執行部署*
terraform apply -var-file="../environments/dev/platform.tfvars"

總結一下

今天成功建立了 Platform Layer,包含:

  • Artifact Registry 與 Cloud Build,提供統一的建置與映像管理
  • Cloud SQL、Redis、Pub/Sub 等平台級服務
  • Logging 與 Monitoring 監控體系
  • Secret Manager 與 IAM 整合,確保安全性

這一層就像是在基礎設施上鋪設的公共平台,讓未來的應用程式能夠直接站上來使用,不必再重複配置。同時,也因為透過 Terraform 實作,我們確保了這些平台服務可以隨時被複製或擴展到不同環境。

Infrastructure Layer vs Platform Layer

比較一下兩層的職責:

層級 核心職責 角色定位
Infrastructure Layer 建立最底層的基礎環境,確保網路、身份、存取控制等安全穩固 好比建築工地的「地基與圍牆」,其他一切都要在這之上運作
Platform Layer 在既有基礎上,提供可重複利用、跨應用共用的平台服務 類似「公共設施與水電系統」,讓應用程式能直接接上來使用

簡單來說 Infrastructure Layer 解決「能不能安全地運行」的問題,而 Platform Layer 解決「怎麼快速、方便地運行」的問題。兩者各司其職,層層堆疊,才能支撐後續的 Application Layer。

明天要在這個平台基礎上,正式部署 Application Layer,把實際的應用程式服務跑起來,讓整個系統進入可對外運作的狀態!


上一篇
Day 26 - Terraform 實作練習:Infrastructure Layer
系列文
30 天 Terraform 學習筆記:從零開始的 IaC 實戰27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言