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

如何让Terraform部署Azure委托子网具备可重入性?

解决Azure策略下委托子网与NSG关联的Terraform部署问题

问题背景

我的Azure环境有一条强制策略:禁止创建未关联网络安全组(NSG)的子网。现在需要为PostgreSQL灵活服务器创建委托子网,且该子网必须与虚拟网络(VNet)分开创建。

最初的Terraform配置(不可行)

最初尝试通过独立的子网资源和NSG关联资源实现,但会触发策略违规:

resource "azurerm_virtual_network" "vnet_hub" {
  name                = "HubVnet"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/21"]
}

resource "azurerm_subnet" "db" {
  name                 = "db-subnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.vnet_hub.name
  address_prefixes     = ["10.0.3.0/24"]

  delegation {
    name = "delegation"

    service_delegation {
      name    = "Microsoft.DBforPostgreSQL/flexibleServers"
      actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
    }
  }
}

resource "azurerm_network_security_group" "db" {
  name                = "db-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "test123"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "db" {
  subnet_id                 = azurerm_subnet.db.id
  network_security_group_id = azurerm_network_security_group.db.id
}

问题原因:Terraform会先创建子网,再执行NSG关联操作,导致子网存在短暂的无NSG状态,违反Azure策略。

当前脚本方案的问题

通过null_resource调用Azure CLI脚本创建子网,首次执行正常,但二次执行terraform apply时,Terraform会认为该子网不在其管理范围内,可能将其删除,导致PostgreSQL服务器失去可用子网。


解决方案

方案一:Terraform原生配置(无需shell脚本)

核心是利用azurerm_subnet资源的network_security_group_id参数,在创建子网时直接绑定NSG,避免中间无NSG的状态,完全符合策略要求:

resource "azurerm_virtual_network" "vnet_hub" {
  name                = "HubVnet"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/21"]
}

resource "azurerm_network_security_group" "db" {
  name                = "db-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "test123"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet" "db" {
  name                 = "db-subnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.vnet_hub.name
  address_prefixes     = ["10.0.3.0/24"]
  # 直接在子网创建时关联NSG,避免策略违规
  network_security_group_id = azurerm_network_security_group.db.id

  delegation {
    name = "delegation"

    service_delegation {
      name    = "Microsoft.DBforPostgreSQL/flexibleServers"
      actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
    }
  }

  service_endpoints = ["Microsoft.Storage"]
}

优势:完全通过Terraform原生资源管理,多次执行terraform apply不会删除子网,且符合Azure策略要求,无需额外脚本。

方案二:修复shell脚本方案(保留脚本)

如果必须保留shell脚本的方式,需要将已创建的子网导入Terraform状态,让Terraform识别并管理该资源:

  1. 首次执行部署:运行terraform apply创建VNet、NSG,并通过脚本生成子网。
  2. 导入子网到Terraform状态:
    terraform import azurerm_subnet.db /subscriptions/<你的订阅ID>/resourceGroups/<资源组名>/providers/Microsoft.Network/virtualNetworks/HubVnet/subnets/db-subnet
    
  3. 添加子网的原生Terraform配置:在你的配置文件中添加和方案一相同的azurerm_subnet资源定义(包含delegation、NSG关联等)。
  4. 验证并更新:执行terraform plan确认无变更,之后再执行terraform apply就不会删除子网。

另外,也可以为null_resource添加triggers,让脚本仅在配置变更时执行:

resource "null_resource" "database_subnet" {
  depends_on = [azurerm_virtual_network.vnet_hub]

  # 仅当以下配置变更时,才重新执行脚本
  triggers = {
    subnet_name               = "db-subnet"
    address_prefix            = "10.0.3.0/24"
    network_security_group_id = azurerm_network_security_group.db.id
  }

  provisioner "local-exec" {
    command     = format("%s/%s", path.module, "add_database_subnet.sh")
    interpreter = ["sh"]
    environment = {
      RESOURCE_GROUP_NAME       = var.resource_group_name
      SUBNET_NAME               = "db-subnet"
      VNET_NAME                 = azurerm_virtual_network.vnet_hub.name
      ADDRESS_PREFIX            = "10.0.3.0/24"
      NETWORK_SECURITY_GROUP_ID = azurerm_network_security_group.db.id
    }
  }
}

内容的提问来源于stack exchange,提问作者Laurent Michel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:45:08