TerraformでVirtual Network/Subnetを使うときの注意事項（Azure）

自分でハマったのでメモ。
# この記事のまとめ
resource "azurerm_virtual_network"の中のsubnetとresource "azurerm_subnet"を同時に使ってはいけない（戒め）

# 経緯
Azureの環境をterraformで作るとき、初回はApplyがうまくいく。
2回目以降Applyがうまくいかない。
```
 Error: creating/updating Virtual Network: (Name "mysql-vnet" / Resource Group "mysql-test"): network.VirtualNetworksClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="InUseSubnetCannotBeDeleted" Message="Subnet ep-subnet is in use by /subscriptions/<Subscription ID>/resourceGroups/mysql-test/providers/Microsoft.Network/networkInterfaces/<EP-nic>/ipConfigurations/<EP-Config> and cannot be deleted. In order to delete the subnet, delete all the resources within the subnet. See aka.ms/deletesubnet." Details=[]
```
どうやらVirtual NetworkのSubnetを削除しようとしているものの、Private EndpointのNICがあって消せないよ、とエラーになっている模様。
そもそも削除意図なんてないので、原因を探るべくTerraformのドキュメントを見ると下記の記載が。

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network


> NOTE on Virtual Networks and Subnets:
> Terraform currently provides both a standalone Subnet resource, and allows for 
>  Subnets to be defined in-line within the Virtual Network resource. At this time you 
> cannot use a Virtual Network with in-line Subnets in conjunction with any Subnet 
> resources. Doing so will cause a conflict of Subnet configurations and will 
> overwrite subnets.

つまるところ、どういうことかというとサブネットの設定が競合するから両方を使うなってことですね。

# 従来のコード
```
# Resource Group
resource "azurerm_resource_group" "example" {
  name     = "example-rg"
  location = "japaneast"
}
# Virtual Network
resource "azurerm_virtual_network" "example" {
  name                = "example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  address_space       = ["172.24.0.0/16"]
  subnet {
    name           = "subnet1"
    address_prefix = "172.24.0.0/24"
  }
}
```
例えば、上記の例だと下記リソースが作られます。
- resource group: example-rg
- virtual network: 172.24.0.0/16
- Subnet: 172.24.0.0/24

subnetブロックは複数指定できます。
ただ、サポートされている内容が限定されています。
- name
- address_prefix
- security_group
例えば、ストレージアカウントのネットワークルールにvNETを追加したいときにServiceEndpointを指定することができません。

そのため、"azurerm_subnet"を追加します。
```
resource "azurerm_subnet" "subnet2" {
    name                                            = "subnet2"
    resource_group_name                             = azurerm_resource_group.example.name
    virtual_network_name                            = azurerm_virtual_network.example.name
    address_prefixes                                = ["172.24.100.0/24"]
    service_endpoints                               = ["Microsoft.Storage"]
}
```

そうすると、どうなるでしょうか。
初回はterraform plan → terraform applyと成功します。
2回目以降applyを実行しようとすると上記のエラーが発生します。（terraform plan時は成功する）
おそらく、resource "azurerm_virtual_network"で情報にないサブネットを削除した後に、resource "azurerm_subnet"で再度追加しようとしているのだと思います。
ただ、そこに既にリソースがある場合、Subnetの削除ができず、エラーになっているものだと思います。

# コード修正後
ドキュメントにもある通り、修正します。
```
# Resource Group
resource "azurerm_resource_group" "example" {
  name     = "example-rg"
  location = "japaneast"
}
# Virtual Network
resource "azurerm_virtual_network" "example" {
  name                = "example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  address_space       = ["172.24.0.0/16"]
}
# Subnet
resource "azurerm_subnet" "subnet1" {
    name                                            = "subnet1"
    resource_group_name                             = azurerm_resource_group.example.name
    virtual_network_name                            = azurerm_virtual_network.example.name
    address_prefixes                                = ["172.24.0.0/24"]
}
resource "azurerm_subnet" "subnet2" {
    name                                            = "subnet2"
    resource_group_name                             = azurerm_resource_group.example.name
    virtual_network_name                            = azurerm_virtual_network.example.name
    address_prefixes                                = ["172.24.100.0/24"]
    service_endpoints                               = ["Microsoft.Storage"]
}
```

# できること、できないこと
こうすることで成功します。
ただ、問題点もあります。
resource "azurerm_virtual_network"のときはNSGの割当ができましたが、resource "azurerm_subnet"にはこの記載がありません。

表にまとめると、こんな感じ。
| resource | "azurerm_virtual_network" | "azurerm_subnet" |
| ---- | ---- | ---- |
| できること | NSGの割当ができる | Service Endpointが作れる |
| できないこと | Service Endpointが作れない | NSGの割当ができない |

めんどくさいですねえ・・・

# サブネットにNSGの割当
resource "azurerm_subnet_network_security_group_association"というものがあるようです。

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association

こちらで"azurerm_subnet"で作ったサブネットを割り当てましょう。
```
# Network Security Group
resource "azurerm_network_security_group" "example" {
  name                = "example-nsg"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  security_rule {
    name                       = "allow"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "<Your Global IP>"
    destination_address_prefix = "172.24.0.0/24"
  }  
}

# NSGの割当 -> Subnet1
resource "azurerm_subnet_network_security_group_association" "subnet1" {
  subnet_id                 = azurerm_subnet.subnet1.id
  network_security_group_id = azurerm_network_security_group.example.id
}
```

これでサブネットにNSGを割り当てられました。

以上。