Young Leaves

Azure Verified Modules (AVM) のサブネットID をサブネット名で取得する

今回はAzure Verified Modules (以下AVM) で仮想ネットワークと複数のサブネットを作成した際、指定したサブネット名のサブネットID を取得する方法について説明します。

実施環境

Azure CLI

2.67.0

Bicep CLI

0.32.4

前提条件

  • Bicep の基本的な使い方を知っている

AVM を使ったサブネットの作成

AVM では2025年1月13日現在、サブネット専用のモジュールはありません。AVM で仮想ネットワークのサブネットを作成する場合は、仮想ネットワークのモジュールを使いサブネットも同時に作成する必要があります。

module vnet 'br/public:avm/res/network/virtual-network:0.5.2' = {
  name: 'vnet-avmtest'
  params: {
    name: 'vnet-avmtest'
    addressPrefixes: ['10.0.0.0/16']
    subnets: [
      {
        name: 'sub-vm'
        addressPrefix: '10.0.0.0/24'
      }
      {
        name: 'sub-db'
        addressPrefix: '10.0.1.0/24'
      }
      {
        name: 'sub-priv'
        addressPrefix: '10.0.2.0/24'
      }
    ]
  }
}

AVM で仮想ネットワークを作成すると、サブネット名、サブネットID は配列で outputs に渡されます。仮想ネットワーク作成後はこの2つの配列を後続のリソース作成で利用できます。

<省略>

@description('The names of the deployed subnets.')
output subnetNames array = [for (subnet, index) in (subnets ?? []): virtualNetwork_subnets[index].outputs.name]

@description('The resource IDs of the deployed subnets.')
output subnetResourceIds array = [
  for (subnet, index) in (subnets ?? []): virtualNetwork_subnets[index].outputs.resourceId
]

<省略>

配列に格納されたサブネットID を利用する場合、配列形式でインデックスを指定すると利用できます。ただし、Bicep の配列は順序を保証していないため、記述例の場合に想定と異なる配列が返される可能性があります。以下は最初の記述例をBicep の what-if で実行した内容です。

Scope: /subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest

  + Microsoft.Network/virtualNetworks/vnet-avmtest [2024-01-01]

      apiVersion:                      "2024-01-01"
      id:                              "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest"
      location:                        "japaneast"
      name:                            "vnet-avmtest"
      properties.addressSpace.addressPrefixes: [
        0: "10.0.0.0/16"
      ]
      properties.enableDdosProtection: false
      properties.subnets: [
        0:

          name:                     "sub-db"
          properties.addressPrefix: "10.0.1.0/24"

        1:

          name:                     "sub-priv"
          properties.addressPrefix: "10.0.2.0/24"

        2:

          name:                     "sub-vm"
          properties.addressPrefix: "10.0.0.0/24"

      ]
      type:                            "Microsoft.Network/virtualNetworks"

  + Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-db [2024-01-01]

      apiVersion:               "2024-01-01"
      id:                       "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-db"
      name:                     "sub-db"
      properties.addressPrefix: "10.0.1.0/24"
      type:                     "Microsoft.Network/virtualNetworks/subnets"

  + Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-priv [2024-01-01]

      apiVersion:               "2024-01-01"
      id:                       "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-priv"
      name:                     "sub-priv"
      properties.addressPrefix: "10.0.2.0/24"
      type:                     "Microsoft.Network/virtualNetworks/subnets"

  + Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-vm [2024-01-01]

      apiVersion:               "2024-01-01"
      id:                       "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-vm"
      name:                     "sub-vm"
      properties.addressPrefix: "10.0.0.0/24"
      type:                     "Microsoft.Network/virtualNetworks/subnets"

Resource changes: 4 to create.

サブネットの順番は sub-vm、sub-db、sub-priv の順番で定義していましたが、what-if の結果は sub-db、sub-priv、sub-vm の順でインデックスが表示されています。今回は検証で実施しているため問題ありませんが、Azure Bastion やAzure Firewall など決まったサブネット指定が必要な場合、配列のインデックスが異なることによりエラーとなる可能性があります。

Bicep またはAVM でのサブネット作成パターン

Bicep のドキュメントではサブネット作成について以下のような記述があります。再デプロイ時に仮想ネットワークで接続断となる可能性から、仮想ネットワークの subnets プロパティでの作成が推奨されています。

サブネットを子リソースとして定義しないようにします。 この方法では、後続のデプロイ中にリソースのダウンタイムが発生したり、デプロイに失敗したりする可能性があります。

<省略>


どちらの方法でもサブネットを定義して作成することができますが、重要な違いがあります。 子リソースを使用してサブネットを定義すると、Bicep ファイルが初めてデプロイされた場合、仮想ネットワークがデプロイされます。 その後、仮想ネットワークのデプロイが完了すると、各サブネットがデプロイされます。 この順序付けは、Azure Resource Manager が個々のリソースを個別にデプロイするために起こります。

同じ Bicep ファイルを再デプロイすると、同じデプロイの順序付けが起こります。 ただし、subnets プロパティが実質的に空なので、仮想ネットワークは、サブネットの構成なしにデプロイされます。 その後、仮想ネットワークが再構成された後、サブネット リソースが再デプロイされ、各サブネットが再確立されます。 状況によっては、この動作により、デプロイ中に仮想ネットワーク内のリソースの接続が失われることがあります。 その他の状況によっては、Azure により仮想ネットワークの変更が妨げられ、デプロイが失敗することがあります。

引用元:subnets プロパティを使用してサブネットを構成する

上記の点をふまえ、AVM でサブネットを作成するパターンは以下のようなものが考えられます。

  • AVM を使わずサブネットの子リソースで存在確認を行いサブネットID を取得する
  • サブネット名からサブネットID のインデックスを取得し、配列を指定する

AVM を使わずサブネットの子リソースで存在確認を行いサブネットID を取得する

1つ目は仮想ネットワークの作成のみAVM を使わず、サブネットの子リソースを使いリソースID を取得する方法です。Bicep の仕様上、サブネットの子リソースで指定できるparent はリソースで作成した仮想ネットワークとなるため、仮想ネットワークだけモジュールを利用する方法はできません。そのため、仮想ネットワーク部分のみAVM を使わず作成し、指定したサブネットが存在するかどうかを確認します。サブネットの子リソースを使うことで、シンボル名.id でサブネットID を取得できるため、利用したいサブネットID を直接指定できます。

// 仮想ネットワークを作成する
resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = {
  name: 'vnet-avmtest'
  location: 'japaneast'
  properties: {
    addressSpace: {
      addressPrefixes: ['10.0.0.0/16']
    }
    subnets: [
      {
        name: 'sub-vm'
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: 'sub-db'
        properties: {
          addressPrefix: '10.0.1.0/24'
        }
      }
      {
        name: 'sub-priv'
        properties: {
          addressPrefix: '10.0.2.0/24'
        }
      }
    ]
  }
}

// サブネット・sub-vmが存在するかどうかを確認
resource vmSubnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' existing = {
  parent: vnet
  name: 'sub-vm'
}

// 仮想マシン用のNICを作成する
module nic 'br/public:avm/res/network/network-interface:0.4.0' = {
  name: 'nic-avmtest'
  params: {
    name: 'nic-avmtest'
    ipConfigurations: [
      {
        name: 'ipconfig01'
        subnetResourceId: vmSubnet.id
      }
    ]
  }
}

小規模の環境であればサブネットID の存在確認は気になりませんが、サブネットの数が多くなる場合はその分だけ子リソースの記述が必要となるため注意が必要です。

サブネット名からサブネットID のインデックスを取得し、配列を指定する

2つ目は仮想ネットワーク作成時のサブネット名、サブネットID の output を確認したところ、サブネット名、サブネットID のインデックスで対になっているように見えます。

kdkwakaba@TESTPC:~$ az deployment group show \
    --resource-group rg-avmtest \
    --name vnet-avmtest \
    --query properties.outputs.subnetNames.value
[
  "sub-vm",
  "sub-db",
  "sub-priv"
]
kdkwakaba@TESTPC:~$ az deployment group show \
    --resource-group rg-avmtest \
    --name vnet-avmtest \
    --query properties.outputs.subnetResourceIds.value
[
  "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-vm",
  "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-db",
  "/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-priv"
]

この仕組みを活かし、サブネット名の配列から対になるインデックスを取得します。Bicep の組み込み関数には 配列の要素からインデックスを取得する indexOf 関数があります。この関数を使ってサブネット名配列のインデックスをサブネットID の配列に指定します。今回はわかりやすくするためサブネット名を直接記述していますが、実際に管理する際は変数やパラメーターファイルで記述してください。

// 仮想ネットワークとサブネットを3つ作成する
module vnet 'br/public:avm/res/network/virtual-network:0.5.2' = {
  name: 'vnet-avmtest'
  params: {
    name: 'vnet-avmtest'
    addressPrefixes: ['10.0.0.0/16']
    subnets: [
      {
        name: 'sub-vm'
        addressPrefix: '10.0.0.0/24'
      }
      {
        name: 'sub-db'
        addressPrefix: '10.0.1.0/24'
      }
      {
        name: 'sub-priv'
        addressPrefix: '10.0.2.0/24'
      }
    ]
  }
}

// 仮想マシン用のNICを作成する
module vmnic 'br/public:avm/res/network/network-interface:0.4.0' = {
  name: 'nic-vm'
  params: {
    name: 'nic-vm'
    ipConfigurations: [
      {
        name: 'ipconfig01'
        subnetResourceId: vnet.outputs.subnetResourceIds[indexOf(vnet.outputs.subnetNames, 'sub-vm')]
      }
    ]
  }
}

// プライベートエンドポイント用のNICを作成する
module privnic 'br/public:avm/res/network/network-interface:0.4.0' = {
  name: 'nic-priv'
  params: {
    name: 'nic-priv'
    ipConfigurations: [
      {
        name: 'ipconfig01'
        subnetResourceId: vnet.outputs.subnetResourceIds[indexOf(vnet.outputs.subnetNames, 'sub-priv')]
      }
    ]
  }
}

デプロイ後、NIC のサブネットが意図したサブネットに関連付けられていることを確認します。

kdkwakaba@TESTPC:~$ az network nic show -g rg-avmtest -n nic-vm --query "ipConfigurations[0].subnet.id"
"/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-vm"

kdkwakaba@TESTPC:~$ az network nic show -g rg-avmtest -n nic-priv --query "ipConfigurations[0].subnet.id"
"/subscriptions/<サブスクリプションID>/resourceGroups/rg-avmtest/providers/Microsoft.Network/virtualNetworks/vnet-avmtest/subnets/sub-priv"

こちらの方法だとサブネットの子リソースを利用しないため、複数のサブネットを作成する際もシンプルな記述になります。今回はNIC を分割して記載していますが、ループを組み合わせるとよりシンプルな記述にすることができます。

まとめ

  • AVM で仮想ネットワークのサブネットを作成すると、サブネット名、サブネットID は配列で outputs に出力される
  • サブネット名、サブネットID は配列で対になっている
  • インデックスで直接指定しない場合はサブネットの子リソースで存在確認をするか indexOf 関数で対になるサブネットID を取得する

参考資料