Bicepで動くコード/動かないコード(Vnet作成)

# はじめに
Bicepを勉強し始めたのですが、「こう書いてみたけどうまく動かないな…」というのがあったので備忘録的にまとめておきます。

## 作成するリソース
- Vnetを作成して、サブネットは2つ定義する。
- サブネットには、`AllowSpecificSSHInbound`というルール(IPアドレス:11.22.33.44からのSSH接続を許可)を持つNSGを紐づける。

# ⭕動くコード

それぞれ、コードの全量を載せてみます。
※もしかしたらパラメータファイルを使ったらもう少しスッキリするかと思います。

## ①VnetとNSGを定義する

```
param location string = 'eastus'
param MyName string = 'test-01'
param ipAddrPrefix string = '10.0.0.0/16'
param subnet1Prefix string = '10.0.0.0/24'
param subnet2Prefix string = '10.0.1.0/24'

var vnetName = '${MyName}-vnet'
var subnet1Name = '${MyName}-subnet1'
var subnet2Name = '${MyName}-subnet2'
var nsgName = '${MyName}-nsg'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        ipAddrPrefix
      ]
    }
    subnets: [
      {
        name: subnet1Name
        properties: {
          addressPrefix: subnet1Prefix
          networkSecurityGroup: {
            id: nsg.id
          }
        }
      }
      {
        name: subnet2Name
        properties: {
          addressPrefix: subnet2Prefix
          networkSecurityGroup: {
            id: nsg.id
          }
        }
      }
    ]
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2022-09-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: [
      {
        name: 'AllowSpecificSSHInbound'
        properties: {
          access: 'Allow'
          direction: 'Inbound'
          protocol: 'Tcp'
          sourceAddressPrefix: '11.22.33.44'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '22'
          priority: 100
        }
      }
    ]

  }
}
```

Vnetの定義の中でサブネットも併せて定義して、別で定義したNSGを紐づけるイメージです。
これが一番しっくりくる書き方なんじゃないかと思います。

### ②NSG/サブネット/NSGをそれぞれ定義する

```
param location string = 'eastus'
param MyName string = 'test-02'
param ipAddrPrefix string = '10.0.0.0/16'
param subnet1Prefix string = '10.0.0.0/24'
param subnet2Prefix string = '10.0.1.0/24'

var vnetName = '${MyName}-vnet'
var subnet1Name = '${MyName}-subnet1'
var subnet2Name = '${MyName}-subnet2'
var nsgName = '${MyName}-nsg'

resource  vnet 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        ipAddrPrefix
      ]
    }
  }
}

resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' ={
  parent: vnet
  name: subnet1Name
  properties: {
    addressPrefix: subnet1Prefix
    networkSecurityGroup: {
      id: nsg.id
    }
  }
}

resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' ={
  parent: vnet
  name: subnet2Name
  properties: {
    addressPrefix: subnet2Prefix
    networkSecurityGroup: {
      id: nsg.id
    }
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2022-09-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: [
      {
        name: 'AllowSpecificSSHInbound'
        properties: {
          access: 'Allow'
          direction: 'Inbound'
          protocol: 'Tcp'
          sourceAddressPrefix: '11.22.33.44'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '22'
          priority: 100
        }
      }
    ]
  }
}

```
サブネットをVnetの外で定義します。その際、定義したサブネットはVnetの中に含まれるということを`parent`で明示しています。
同じ設定のサブネットを複数Vnetで使いまわす際にはこの書き方が良いのかもしれません。

# ❌動かないコード

## ③Vnetの定義内にサブネットおよびNSGの定義内容もすべて含める

```
param location string = 'eastus'
param MyName string = 'test-03'
param ipAddrPrefix string = '10.0.0.0/16'
param subnet1Prefix string = '10.0.0.0/24'
param subnet2Prefix string = '10.0.1.0/24'

var vnetName = '${MyName}-vnet'
var subnet1Name = '${MyName}-subnet1'
var subnet2Name = '${MyName}-subnet2'
// var nsgName = '${MyName}-nsg'

resource vm 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        ipAddrPrefix
      ]
    }
    subnets: [
      {
        name: subnet1Name
        properties: {
          addressPrefix: subnet1Prefix
          networkSecurityGroup: {
            properties: {
              securityRules: [
                {
                  name: 'AllowSpecificSSHInbound'
                  properties: {
                    access: 'Allow'
                    direction: 'Inbound'
                    protocol: 'Tcp'
                    sourceAddressPrefix: '11.22.33.44'
                    sourcePortRange: '*'
                    destinationAddressPrefix: '*'
                    destinationPortRange: '22'
                    priority: 100
                  }
                }
              ]
            }
          }
        }
      }
      {
        name: subnet2Name
        properties: {
          addressPrefix: subnet2Prefix
          networkSecurityGroup: {
            properties: {
              securityRules: [
                {
                  name: 'AllowSpecificSSHInbound'
                  properties: {
                    access: 'Allow'
                    direction: 'Inbound'
                    protocol: 'Tcp'
                    sourceAddressPrefix: '11.22.33.44'
                    sourcePortRange: '*'
                    destinationAddressPrefix: '*'
                    destinationPortRange: '22'
                    priority: 100
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}
```

Vnetの定義ブロックの中に、すべての設定を詰め込んだ形です。
上記を実行すると以下のようなエラーメッセージが出力されました。
```json
{
    "status": "Failed",
    "error": {
        "code": "DeploymentFailed",
        "target": "/subscriptions/<サブスクリプションID>/resourceGroups/<リソースグループ名>/providers/Microsoft.Resources/deployments/03.vnet",
        "message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-deployment-operations for usage details.",
        "details": [
            {
                "code": "InvalidRequestFormat",
                "message": "Cannot parse the request.",
                "details": [
                    {
                        "code": "MissingJsonReferenceId",
                        "message": "Value for reference id is missing. Path properties.subnets[0].properties.networkSecurityGroup."
                    },
                    {
                        "code": "MissingJsonReferenceId",
                        "message": "Value for reference id is missing. Path properties.subnets[1].properties.networkSecurityGroup."
                    }
                ]
            }
        ]
    }
}
```

メッセージ的に`properties.subnets[*].properties.networkSecurityGroup`の中に`id`を指定していないためエラーになっているように思われたので、空っぽのNSG定義を追加してみました。

## ④Vnetの定義内にサブネットおよびNSGの定義内容もすべて含める(NSGはガラだけ定義)

```
param location string = 'eastus'
param MyName string = 'test-04'
param ipAddrPrefix string = '10.0.0.0/16'
param subnet1Prefix string = '10.0.0.0/24'
param subnet2Prefix string = '10.0.1.0/24'

var vnetName = '${MyName}-vnet'
var subnet1Name = '${MyName}-subnet1'
var subnet2Name = '${MyName}-subnet2'
var nsgName = '${MyName}-nsg'

resource vm 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        ipAddrPrefix
      ]
    }
    subnets: [
      {
        name: subnet1Name
        properties: {
          addressPrefix: subnet1Prefix
          networkSecurityGroup: {
            id: nsg.id
            properties: {
              securityRules: [
                {
                  name: 'AllowSpecificSSHInbound'
                  properties: {
                    access: 'Allow'
                    direction: 'Inbound'
                    protocol: 'Tcp'
                    sourceAddressPrefix: '11.22.33.44'
                    sourcePortRange: '*'
                    destinationAddressPrefix: '*'
                    destinationPortRange: '22'
                    priority: 100
                  }
                }
              ]
            }
          }
        }
      }
      {
        name: subnet2Name
        properties: {
          addressPrefix: subnet2Prefix
          networkSecurityGroup: {
            id: nsg.id
            properties: {
              securityRules: [
                {
                  name: 'AllowSpecificSSHInbound'
                  properties: {
                    access: 'Allow'
                    direction: 'Inbound'
                    protocol: 'Tcp'
                    sourceAddressPrefix: '11.22.33.44'
                    sourcePortRange: '*'
                    destinationAddressPrefix: '*'
                    destinationPortRange: '22'
                    priority: 100
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2022-09-01' = {
  name: nsgName
  location: location
}
```

`id`がないというエラーに対処するためだけに、NSGのガラだけ定義しています。実はこのコードはエラーにならず登録はできます。ただNSGの設定を見てみると追加されているはずの`AllowSpecificSSHInbound`ルールが追加されていませんでした。
![スクリーンショット 2023-04-16 205834.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/a0ecac83-f902-6276-2cce-b74c43b820e1.png)

## ⑤Vnet/サブネット/NSGをそれぞれ定義する(Vnetとサブネットの紐づけにparentを使わない)

```
param location string = 'eastus'
param MyName string = 'test-05'
param ipAddrPrefix string = '10.0.0.0/16'
param subnet1Prefix string = '10.0.0.0/24'
param subnet2Prefix string = '10.0.1.0/24'

var vnetName = '${MyName}-vnet'
var subnet1Name = '${MyName}-subnet1'
var subnet2Name = '${MyName}-subnet2'
var nsgName = '${MyName}-nsg'

resource  vnet 'Microsoft.Network/virtualNetworks@2022-09-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        ipAddrPrefix
      ]
    }
    subnets: [
      {
        id: subnet1.id
      }
      {
        id: subnet2.id
      }
    ]
  }
}

resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' ={
  name: subnet1Name
  properties: {
    addressPrefix: subnet1Prefix
    networkSecurityGroup: {
      id: nsg.id
    }
  }
}

resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' ={
  name: subnet2Name
  properties: {
    addressPrefix: subnet2Prefix
    networkSecurityGroup: {
      id: nsg.id
    }
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2022-09-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: [
      {
        name: 'AllowSpecificSSHInbound'
        properties: {
          access: 'Allow'
          direction: 'Inbound'
          protocol: 'Tcp'
          sourceAddressPrefix: '11.22.33.44'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '22'
          priority: 100
        }
      }
    ]
  }
}
```

Vnetとサブネットの紐づけに`parent`を使わず、`properties.subnets[*]`の中にサブネットそれぞれの`id`を指定してみました。
実行したところ、以下のようなエラーメッセージが出力されました。

```
InvalidTemplate - Deployment template validation failed: 'The template resource 'test-05-vnet' at line '1' and column '716' is not valid: The language expression property array index '1' is out of bounds.. Please see https://aka.ms/arm-functions for usage details.'.
```

## なぜ動かないのか(自分なりの解釈)
なぜ動かないのか(定義として許されないのか)について調べてみましたが、公式ドキュメントなどからは見つけられなかったので、自分なりにNGとなる理由を考えてみました。もしドキュメントなどをご存じの方がいれば教えてください。

③は[Vnetのテンプレートドキュメント](https://learn.microsoft.com/ja-jp/azure/templates/microsoft.network/virtualnetworks?pivots=deployment-language-arm-template
)を見る限り、NSGのセキュリティルールも細かく定義できるように読み取れます。しかし、NSGのnameに該当する設定項目が無いため、Vnetの定義ブロック内ではNSGを新規作成できないのではと推測しています。

⑤については、仮にVnetの定義を全てコメントアウトした場合でも構文上はエラーにはなりませんが、Azure的には実現できない状態になる(サブネットだけ定義することはできない)ため、エラーになるのかなと考えています(②はVnetの定義をコメントアウトすると構文エラーになります)。