You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform销毁GCP服务网络连接失败,请求排查原因

问题:Terraform销毁GCP基础设施时无法删除服务网络连接

问题现象

  • terraform apply可正常创建所有资源,但执行terraform destroy -auto-approve时,无法删除服务网络连接(Service Networking Connection)或VPC对等连接,报错信息如下:

Unable to remove Service Networking Connection, err: Error waiting for Delete Service Networking Connection: Error code 9, message: Failed to delete connection; Producer services (e.g. CloudSQL, Cloud Memstore, etc.) are still using this connection.

  • 手动删除VPC对等连接并释放私有IP后,销毁操作可正常完成。
  • 销毁操作通过GitHub Actions执行,命令为:
- name: Terraform destroy
        working-directory: ${{ env.DIR }}
        run: terraform destroy -auto-approve

当前模块配置

VPC模块代码

# Creating a custom VPC
resource "google_compute_network" "custom-vpc-network" {
  name                    = var.vpc_name
  project                 = var.project_id
  auto_create_subnetworks = false
  mtu                     = 1460
  routing_mode            = var.route_mode
  description = "Custom VPC for an ecommerce architecture"
}

# Creating a custom subnet
resource "google_compute_subnetwork" "front-end-subnet" {
  ip_cidr_range            = var.ip_address_range_frontend
  region                   = var.project_region
  name                     = var.subnet_name_frontend
  network                  = google_compute_network.custom-vpc-network.name
  description = "subnet for frontend"
  private_ip_google_access = true
  stack_type               = var.stack_type
  log_config {
    aggregation_interval = var.aggregate_interval
    flow_sampling        = 0.5
    metadata             = var.include_all_metadata
  }
}

resource "google_compute_subnetwork" "back-end-subnet" {
  ip_cidr_range            = var.ip_address_range_backend
  region                   = var.project_region
  name                     = var.subnet_name_backend
  network                  = google_compute_network.custom-vpc-network.name
  description = "subnet for backend"
  private_ip_google_access = true
  stack_type               = var.stack_type
  log_config {
    aggregation_interval = var.aggregate_interval
    flow_sampling        = 0.5
    metadata             = var.include_all_metadata
  }
}

resource "google_compute_subnetwork" "database-subnet" {
  ip_cidr_range            = var.ip_address_range_database
  region                   = var.project_region
  name                     = var.subnet_name_database
  network                  = google_compute_network.custom-vpc-network.name
  description = "subnet for database"
  private_ip_google_access = true
  stack_type               = var.stack_type
  log_config {
    aggregation_interval = var.aggregate_interval
    flow_sampling        = 0.5
    metadata             = var.include_all_metadata
  }
}

数据库模块代码

resource "google_compute_global_address" "private-ip-address" {
    name = var.private_ip_address
    address_type = var.private_ip_type
    purpose = var.private_ip_purpose
    network = var.network_id  
    prefix_length = 16
    depends_on = [ var.network_id ]
}

resource "google_service_networking_connection" "private-vpc-connection" {
    network = var.network_id
    service = var.service
    reserved_peering_ranges = [google_compute_global_address.private-ip-address.name]
    lifecycle {
      create_before_destroy = true
    }
}

resource "google_sql_database_instance" "sql-instance" {
  name = var.sql_instance
  region = var.project_region
  database_version = var.database_version

  depends_on = [ google_service_networking_connection.private-vpc-connection ]

  settings {
    tier              = var.machine_type
    availability_type = var.availability_type_regional
    edition           = var.edition
    user_labels       = {
      environment = var.environment
    }
    ip_configuration {
      ipv4_enabled      = true
      private_network   = var.network_id
      enable_private_path_for_google_cloud_services = true
    }

    backup_configuration {
      enabled = true
      start_time = var.backup-time
      point_in_time_recovery_enabled = true
    }

    maintenance_window {
      day = 7
      hour = 20
      update_track = var.update_track
    }
  }
  deletion_protection = false
  lifecycle {
    create_before_destroy = true
  }
}

resource "google_sql_database_instance" "sql-instance-read-replica" {
  name = "${var.sql_instance}-replica"
  master_instance_name = google_sql_database_instance.sql-instance.name
  region = var.project_region
  database_version = var.database_version

  settings {
    tier              = var.machine_type
    availability_type = var.availability_type_zonal
    edition           = var.edition
    user_labels       = {
      environment = var.environment
    }
    ip_configuration {
      ipv4_enabled      = true
      private_network   = var.network_id
      enable_private_path_for_google_cloud_services = true
    }
  }
  deletion_protection = false
  lifecycle {
    create_before_destroy = true
  }

}

resource "google_sql_database" "postgresql-database" {
    name = var.postgresql-database
    instance = google_sql_database_instance.sql-instance.name 
}

resource "google_sql_user" "postgresql-user" {
    name = var.db_username_output
    instance = google_sql_database_instance.sql-instance.name
    password = var.db_password_output
}   

问题根源与解决方案

问题根源

当前配置中,google_service_networking_connection没有明确依赖Cloud SQL实例的销毁流程,导致Terraform尝试先删除服务网络连接,而此时Cloud SQL主实例和只读副本仍在占用该连接,触发报错。同时,SQL实例上配置的create_before_destroy会干扰销毁顺序,进一步加剧问题。

解决方案

  1. 调整服务网络连接的依赖与生命周期
    移除create_before_destroy配置,并添加依赖确保服务网络连接在所有SQL实例销毁后再删除:

    resource "google_service_networking_connection" "private-vpc-connection" {
        network = var.network_id
        service = var.service
        reserved_peering_ranges = [google_compute_global_address.private-ip-address.name]
        # 依赖所有SQL实例,确保销毁顺序
        depends_on = [
            google_sql_database_instance.sql-instance,
            google_sql_database_instance.sql-instance-read-replica
        ]
    }
    
  2. 移除SQL实例不必要的create_before_destroy
    销毁场景下,create_before_destroy会导致先创建新实例再删除旧实例,无实际意义且干扰顺序,直接移除SQL实例的该配置:

    resource "google_sql_database_instance" "sql-instance" {
      # ... 其他配置 ...
      # 移除以下生命周期配置
      # lifecycle {
      #   create_before_destroy = true
      # }
    }
    

    对只读副本实例执行同样操作。

  3. 调整私有IP地址的销毁顺序
    让私有IP地址在服务网络连接销毁后再释放,避免提前释放导致的异常:

    resource "google_compute_global_address" "private-ip-address" {
        name = var.private_ip_address
        address_type = var.private_ip_type
        purpose = var.private_ip_purpose
        network = var.network_id  
        prefix_length = 16
        depends_on = [ 
            var.network_id,
            google_service_networking_connection.private-vpc-connection
        ]
        lifecycle {
            destroy_after_create = true
        }
    }
    
  4. 验证销毁顺序
    执行terraform plan -destroy查看资源销毁顺序,确认顺序为:SQL实例 → 服务网络连接 → 私有IP地址 → VPC资源。

循环依赖处理方案

如果调整配置后出现循环依赖,可通过null_resource间接控制顺序:

resource "null_resource" "destroy_sql_first" {
    depends_on = [
        google_sql_database_instance.sql-instance,
        google_sql_database_instance.sql-instance-read-replica
    ]
}

resource "google_service_networking_connection" "private-vpc-connection" {
    # ... 其他配置 ...
    depends_on = [null_resource.destroy_sql_first]
}

内容的提问来源于stack exchange,提问作者Amith Sai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:44:56