【Azure】Bicepで複数サブネットをデプロイする時のポイントを紹介!

こんにちは、サイオステクノロジーの佐藤 陽です。

今回はBicepで複数のサブネットをデプロイしたときに発生した、エラーの解消方法をご紹介します。
Bicep入門したての自分用のメモとなってますが、誰かのお役に立てれば幸いです。

はじめに

みなさんBicep使ってますか?
Azureに特化したIaCツールであり、書きやすさ・読みやすさに定評があります。
最近自分もこのBicepに入門して色々書いてるのですが、TerraformやARM Templateに比べると非常に書きやすくて気に入ってます。

このBicepですが、Azure FunctionsやCosmosDBなんかのリソースはもちろん、VNetなどネットワークリソースのデプロイも可能です。
そんな中、複数のサブネットをデプロイするときに少しハマってしまったので、個人的な備忘録として記録に残したいと思います。

結論

先に結論書いてしまうと

発生したエラー

AnotherOperationInProgress: Another operation on this or dependent resource is in progress. To retrieve status of the operation use uri: https://management.azure.com/subscriptions/~~~~

解決策

VNetとSubnetNetを別々のリソースとして定義するのではなく SubNetをVNetの子リソースとして定義するよう実装する。
MSのドキュメントにも「子リソースとして定義することが最適である」との記載がありました。

解決までの道のり

現状

まず今回想定している環境として、以下のレンジでVNetを作成します。

  • 10.0.0.0/16

このVNetに対して、以下の3つのSubnetを作成します。

  • 10.0.0.0/24
  • 10.0.1.0/24
  • 10.0.2.0/24

これを実現するために、最初に書いていたBicepのコードはこちらです。

main.bicep

targetScope = 'subscription'
param environmentName string
param location string

var tags = {
  // Tag all resources with the environment name.
  'azd-env-name': environmentName
}

resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: 'rg-sios'
  location: location
  tags: tags
}

module vnet './core/networking/vnet.bicep' = {
  name: 'vnet'
  scope: rg
  params: {
    name: 'vnet-sios'
    addressPrefix: '10.0.0.0/16'
    location: location
  }
}

module subnet1 './core/networking/subnet.bicep' = {
  name: 'subnet1'
  scope: rg
  params: {
    name: 'subnet1'
    addressPrefix: '10.0.0.0/24'
    existingVirtualNetworkName: vnet.outputs.name
  }
}

module subent2 './core/networking/subnet.bicep' = {
  name: 'subnet2'
  scope: rg
  params: {
    name: 'subnet2'
    addressPrefix: '10.0.1.0/24'
    existingVirtualNetworkName: vnet.outputs.name
  }
}

module subnet3 './core/networking/subnet.bicep' = {
  name: 'subnet3'
  scope: rg
  params: {
    name: 'subnet3'
    addressPrefix: '10.0.2.0/24'
    existingVirtualNetworkName: vnet.outputs.name
    }
}

./core/networking/vnet.bicep

param location string = resourceGroup().location
param name string
// 作成するvnetのアドレス空間
param addressPrefix string
param subnets array = []

resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = {
  name: name
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [addressPrefix]
    }
    subnets: subnets
  }
}

output name string = vnet.name
output id string = vnet.id

./core/networking/subnet.bicep

param name string
param addressPrefix string
param existingVirtualNetworkName string
param delegations array = []

// 既に存在する仮想ネットワークを参照
resource existingVirtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' existing = {
  scope: resourceGroup()
  name: existingVirtualNetworkName
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-04-01' = {
  parent: existingVirtualNetwork
  name: name
  properties: {
    addressPrefix: addressPrefix
  }
}

output id string = subnet.id
output name string = subnet.name

最初にVNetのリソースを定義し、そのあとparentのパラメータを通してSubnetを追加するような方法となります。
このBicepファイルを用いてを実行すると、以下のようなエラーが発生しました。

AnotherOperationInProgress: Another operation on this or dependent resource is in progress. To retrieve status of the operation use uri: https://management.azure.com/subscriptions/~~~~

 

Subnetを作成する際、同じVNetに同時にアクセスしてしまうため、処理がバッティングしていることを表しています。
このあたりBicepではいい感じにやってくれないようです。

ちなみにstackoverflowにも同じような境遇の人がいました。

修正案

以下のように、VNet構築時に子リソースとしてSubnetを書くことで解消しました。

module vnet './core/networking/vnet.bicep' = {
  name: 'vnet'
  scope: rg
  params: {
    name: 'vnet-sios'
    addressPrefix: '10.0.0.0/16'
    location: location
    subnets: [
        {
          name: 'subnet1'
          properties: {
            addressPrefix: '10.0.0.0/24'
          }
        }
        {
          name: 'subnet2'
          properties: {
            addressPrefix: '10.0.1.0/24'
          }
        }
        {
          name: 'subnet3'
          properties: {
            addressPrefix: '10.0.2.0/24'
          }
        }
      ]
  }
}
この実装方法に関しては、MSのドキュメントでも推奨されており、 以下のように書かれていました。
仮想ネットワーク定義内でサブネットを定義するのが最適です。
子リソースを使用してサブネットを定義すると、Bicep ファイルが初めてデプロイされた場合、仮想ネットワークがデプロイされます。 その後、仮想ネットワークのデプロイが完了すると、各サブネットがデプロイされます。 この順序付けは、Azure Resource Manager が個々のリソースを個別にデプロイするために起こります。 同じ Bicep ファイルを再デプロイすると、同じデプロイの順序付けが起こります。 ただし、subnets プロパティが実質的に空なので、仮想ネットワークは、サブネットの構成なしにデプロイされます。 その後、仮想ネットワークが再構成された後、サブネット リソースが再デプロイされ、各サブネットが再確立されます。 状況によっては、この動作により、デプロイ中に仮想ネットワーク内のリソースの接続が失われることがあります。 その他の状況によっては、Azure により仮想ネットワークの変更が妨げられ、デプロイが失敗することがあります。

また他にも、Subnetのリソース定義部分でdependsOnのパラメータを利用することで解決できそうでした。
ただ、「dependsOnは必要な時だけ使うべき」といった内容の記載も見られたので、今回は採用を見送りました。 

サブネットへの参照

一件落着かと思いましたが
次にハマったのが、作成したSubnetを参照する方法です。

プライベートエンドポイントの設定を行う場合など、他のリソースを作成するときにこのSubnetのIdが必要になるケースが多くあるかと思います。
独立したリソースとしてSubnetを定義している場合は簡単にアクセスできますが、VNetの子リソースとして定義されているため、ややアクセスし辛いです。

VNetが持つsubnetsというarrayのパラメータはすぐに取得できるのですが、そこから取得したいSubnetだけを抽出するのは冗長な実装になりそうです。
しかも配列に入れられる順番も毎回異なる可能性があるとのことで、array番号で決め打ちというのも難しそうです。

そして、この解決方法に関してもMSドキュメントに紹介されてました。
ドキュメントさまさまです。

ドキュメントから引っこ抜くと、以下のような形での実装すればOKみたいです。

param location string = resourceGroup().location

var virtualNetworkName = 'my-vnet'
var subnet1Name = 'Subnet-1'
var subnet2Name = 'Subnet-2'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: subnet1Name
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: subnet2Name
        properties: {
          addressPrefix: '10.0.1.0/24'
        }
      }
    ]
  }

  resource subnet1 'subnets' existing = {
    name: subnet1Name
  }


  resource subnet2 'subnets' existing = {
    name: subnet2Name
  }
}

output subnet1ResourceId string = virtualNetwork::subnet1.id
output subnet2ResourceId string = virtualNetwork::subnet2.id

 

ただこのコードだと

  • moduleを利用していない
  • ハードコーディング色が強い

など、いまいち柔軟性に欠ける実装に感じました。

そこで、このコードを参考に、subnet.bicepを以下のように書き直しました。

./core/networking/subnet.bicep

param name string
param existingVirtualNetworkName string

// 既に存在する仮想ネットワークを参照
resource existingVirtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' existing = {
  name: existingVirtualNetworkName

  //名前が一致するSubnetだけを抽出
  resource subnet 'subnets@2023-11-01' existing = {
    name: name
  }
}

output id string = existingVirtualNetwork::subnet.id
output name string = existingVirtualNetwork::subnet.name

このリソースに対し、既存のVNetとSubnetの名称を与えることで
既に存在しているSubnetリソースにアクセスすることが可能となり、SubnetのリソースIDを取得することが可能となります。

main.bicepとしては以下のような形になります。
(実際は、subnet名などを変数で定義しておいてあげるとよいかと思います。)

module vnet './core/networking/vnet.bicep' = {
  name: 'vnet'
  scope: rg
  params: {
    name: 'vnet-sios'
    addressPrefix: '10.0.0.0/16'
    location: location
    subnets: [
        {
          name: 'subnet1'
          properties: {
            addressPrefix: '10.0.0.0/24'
          }
        }
        {
          name: 'subnet2'
          properties: {
            addressPrefix: '10.0.1.0/24'
          }
        }
        {
          name: 'subnet3'
          properties: {
            addressPrefix: '10.0.2.0/24'
          }
        }
      ]
  }
}

module existingSubnet1 './core/networking/subnet.bicep' = {
  name:'existingSubnet1'
  scope:rg
  params:{
    name:'subnet1'
    existingVirtualNetworkName: vnet.outputs.name
  }
}

//例えばKeyVault用にPrivateEndpointの設定を行う
module keyVaultPrivateEndpoint './core/networking/private-endpoint.bicep' = {
  name: 'key-vault-private-endpoint'
  scope: rg
  params: {
    name: keyVault.outputs.name
    location: location
    subnetId: existingSubnet1.outputs.id  //ここでSubnetのIdを参照
    privateLinkServiceId: keyVault.outputs.id
    privateLinkServiceGroupIds: ['vault']
    dnsZoneName: 'vaultcore.azure.net'
    linkVnetId: vnet.outputs.id
  }
}

まとめ

今回はBicepを使って複数Subnetを構築する際のポイントをご紹介しました。
他にもこんな簡単な方法あるよ!といった方はぜひコメントで教えてください。

まだまだBicep入門したてなので、色々勉強していきたいと思います。
ではまた!

参考文献

2023-11-01 API バージョンを使って Bicep で Azure VNet のサブネットを自由にデプロイする

Azure Bicep の配列にハマる(n回目)

Bicep を使って仮想ネットワーク リソースを作成する

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です