[検証]Azure専用プログラミング言語Bicepを試してみた(第3弾ループ処理編)~Infrastructure as Code~#Azure リレー

FIXER藤井です。先週の弊社荒井の「知っておきたいAzure認定資格のラーニングパス、まとめてみた」に続き、今週のAzure リレーを担当します。Azure リレーとして5月25日に寄稿した「[検証]Azure専用プログラミング言語Bicepを試してみた~Infrastructure as Code~#Azure リレー」および 6月22日に寄稿した 「[検証]Azure専用プログラミング言語Bicepを試してみた(第2弾VMデプロイ編)~Infrastructure as Code~#Azure リレー」の第三弾として、Azure のInfrastructure as Code用言語「Bicep」のさらなる検証結果を報告いたします。

本記事の位置づけと目的

本記事では、Bicep によるAzure 仮想ネットワークのデプロイを検証した前々回の記事と、Azure 仮想マシンのデプロイを検証した前回の記事からさらに発展させ、 Azure 仮想ネットワーク と Azure 仮想マシン を組み合わせた状態で、ループ処理を利用して複数のインスタンスをデプロイするシナリオを想定して検証します。

Infrastructure as Code(IaC)の真価が発揮されるのは、ループ処理を利用して複雑かつ規模の大きな環境を構築する場面といっても過言ではありません。もし単純な構成だったり、小規模な構成であればAzure Portalや各種コマンドラインツールから作業する方が簡単です。実際、Bicepの公式GItHubのWikiでもループ処理専用のページが設けられていることからも、その重要性がうかがい知れます。

自身が以前に関わったプロジェクトでは仮想マシン数百台の大規模環境の構築作業を、ARM テンプレートでこの「ループ処理」を駆使することで大幅に省力化しました。従来のAzure PowerShellを使った手作業では、構築作業だけで専任の人員が1人ないし2人必要だったのが、他の業務と兼任でかつより速く作業が可能となりました。

本記事は前々回および前回の記事の内容を理解していることが前提のため、まだお読みでない方は先に以下を参照してください。

検証内容

本記事での検証ではまずARMテンプレートを作成し、リソースのデプロイが問題なくできることを確認した状態で、Bicepテンプレートに変換して再度、リソースのデプロイを検証します。

ARMテンプレートのサンプルソースについて

検証で使用するARMテンプレートは以下の要件を満たすように設計し、リソースのデプロイが問題なくできることを確認済みです。組み合わせて使用するパラメーターファイルは、Bicepテンプレートでもそのまま使用します。

  • 要件(1)ネットワークセキュリティグループをループ処理で複数作成する。(サンプル内では「Windows用」と「Linux用」の2種類を作成)
  • 要件(2)上記ネットワークセキュリティグループごとにサブネットをループ処理で作成し、作成時に登録するネットワークセキュリティグループを指定する。
  • 要件(3)「Windows」と「Linux」の仮想マシンを各1台ループ処理で作成し、作成時に登録するサブネット(=ネットワークセキュリティグループ)を指定する。
  • 要件(4)仮想マシンはリソース名の末尾に「~_VmId_(数字)」の形式で、作成された順に通し番号を付与される。
  • 要件(5)各仮想マシンのログインアカウントはパラメーターファイルで指定し、ログインパスワードはデプロイの実行時に入力する。

※各仮想マシンへのログイン確認は省略するものとします。もし読者のかたが自分で試したい時は、パブリックIPアドレスの割当と、ネットワークセキュリティグループでのポート(Windows :3389,Linux:22)の受信許可設定をそれぞれ追加します。

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "adminUsername": {
        "type": "string",
        "metadata": {
          "description": "Username for the Virtual Machine."
        }
      },
      "adminPassword": {
        "type": "securestring",
        "minLength": 12,
        "metadata": {
          "description": "Password for the Virtual Machine."
        }
      }
    },
    "variables": {
      "PrefixKey1": "FIXER",
      "PrefixKey2": "blog",
      "PrefixName": "[concat(variables('PrefixKey1'), variables('PrefixKey2'))]",
      "ArrNsgsName": [
        "[concat(variables('PrefixName'), '-Nsg_Windows')]",
        "[concat(variables('PrefixName'), '-Nsg_Linux')]"
      ],
      "Vnet": {
        "name": "[concat(variables('PrefixName'), '-Vnet')]",
        "addressPrefixes": "192.168.0.0/16"
      },
      "SubnetPrefixName":"[concat(variables('Vnet').name, '/', variables('PrefixName'))]",
      "ArrSubnets": [
        {
          "name":"[concat(variables('Vnet').name, '/', variables('PrefixName'), '-Subnet1st_Windows')]",
          "subnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('Vnet').name, concat(variables('PrefixName'), '-Subnet1st_Windows'))]",
          "addressPrefix": "192.168.0.0/24",
          "nsgId":"[resourceId('Microsoft.Network/networkSecurityGroups',variables('ArrNsgsName')[0])]"
        },
        {
          "name":"[concat(variables('Vnet').name, '/', variables('PrefixName'), '-Subnet2nd_Linux')]",
          "subnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('Vnet').name, concat(variables('PrefixName'), '-Subnet2nd_Linux'))]",
          "addressPrefix": "192.168.1.0/24",
          "nsgId":"[resourceId('Microsoft.Network/networkSecurityGroups',variables('ArrNsgsName')[1])]"
        }
      ],
      "ArrVms": [
        {
          "vmNamePrefix":"[concat(variables('PrefixName'), '-VM_Windows')]",
          "nicNamePrefix":"[concat(variables('PrefixName'), '-NIC_Windows')]",
          "subnetRef":"[variables('ArrSubnets')[0].subnetId]",
          "publisher": "MicrosoftWindowsServer",
          "offer": "WindowsServer",
          "sku": "2019-Datacenter"
        },
        {
            "vmNamePrefix":"[concat(variables('PrefixName'), '-VM_Linux')]",
            "nicNamePrefix":"[concat(variables('PrefixName'), '-NIC_Linux')]",
            "subnetRef":"[variables('ArrSubnets')[1].subnetId]",
            "publisher": "OpenLogic",
            "offer": "CentOS",
            "sku": "7.5"
          }
      ]
    },
    "resources": [
      {
        "apiVersion": "2015-06-15",
        "type": "Microsoft.Network/networkSecurityGroups",
        "name": "[variables('ArrNsgsName')[copyIndex()]]",
        "copy": {
          "name": "Nsgs",
          "count": 2,
          "mode": "Serial"
        },
        "location": "[resourceGroup().location]",
        "properties": {
          "securityRules": []
        }
      },
      {
        "apiVersion": "2015-06-15",
        "type": "Microsoft.Network/virtualNetworks",
        "name": "[variables('VNet').name]",
        "location": "[resourceGroup().location]",
        "dependsOn": [
        ],
        "properties": {
          "addressSpace": {
            "addressPrefixes": [
              "[variables('VNet').addressPrefixes]"
            ]
          }
        }
      },
      {
        "apiVersion": "2015-06-15",
        "type": "Microsoft.Network/virtualNetworks/subnets",
        "name": "[variables('ArrSubnets')[copyIndex()].name]",
        "copy": {
          "name": "Subnets",
          "count": "[length(variables('ArrSubnets'))]",
          "mode": "Serial",
          "batchSize": 1
        },
        "dependsOn": [
          "[variables('ArrSubnets')[copyIndex()].nsgId]"
        ],
        "properties": {
          "addressPrefix": "[variables('ArrSubnets')[copyIndex()].addressPrefix]",
          "networkSecurityGroup": {
              "id": "[variables('ArrSubnets')[copyIndex()].nsgId]"
          }
        }
      },
      {
        "type": "Microsoft.Network/networkInterfaces",
        "apiVersion": "2020-06-01",
        "name": "[concat(variables('ArrVms')[copyIndex()].nicNamePrefix, '_VmId_',copyIndex())]",
        "copy": {
            "name": "Nics",
            "count": "[length(variables('ArrVms'))]",
            "mode": "Serial",
            "batchSize": 1
          },
        "location": "[resourceGroup().location]",
        "dependsOn": [
          "[variables('ArrVms')[copyIndex()].subnetRef]"
        ],
        "properties": {
          "ipConfigurations": [
            {
              "name": "ipconfig1",
              "properties": {
                "privateIPAllocationMethod": "Dynamic",
                "subnet": {
                  "id": "[variables('ArrVms')[copyIndex()].subnetRef]"
                }
              }
            }
          ]
        }
      },
      {
        "type": "Microsoft.Compute/virtualMachines",
        "apiVersion": "2020-06-01",
        "name": "[concat(variables('ArrVms')[copyIndex()].vmNamePrefix, '_VmId_', copyIndex())]",
        "copy": {
            "name": "Vms",
            "count": "[length(variables('ArrVms'))]",
            "mode": "Serial"
          },
        "location": "[resourceGroup().location]",
        "dependsOn": [
          "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('ArrVms')[copyIndex()].nicNamePrefix, '_VmId_', copyIndex()))]"
        ],
        "properties": {
          "hardwareProfile": {
            "vmSize": "Standard_A2_v2"
          },
          "osProfile": {
            "computerName": "fixerblogvm",
            "adminUsername": "[parameters('adminUsername')]",
            "adminPassword": "[parameters('adminPassword')]"
          },
          "storageProfile": {
            "imageReference": {
              "publisher": "[variables('ArrVms')[copyIndex()].publisher]",
              "offer": "[variables('ArrVms')[copyIndex()].offer]",
              "sku": "[variables('ArrVms')[copyIndex()].sku]",
              "version": "latest"
            },
            "osDisk": {
              "createOption": "FromImage",
              "managedDisk": {
                "storageAccountType": "StandardSSD_LRS"
              }
            }
          },
          "networkProfile": {
            "networkInterfaces": [
              {
                "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('ArrVms')[copyIndex()].nicNamePrefix, '_VmId_', copyIndex()))]"
              }
            ]
          }
        }
      }
    ],
    "outputs": {
    }
  }

パラメーターファイルは以下です。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "value": "blogadmin"
    }
  }
}

検証の実施手順詳細および結果

検証の内容は以下の2段階です。

Step1.ARMテンプレートからBicepへの変換

PowerShellのコマンドによりARMテンプレートからBicepへの変換をします。事前に必要なコンポーネントのインストール手順については前々回の記事にて解説しているのでそちらをご参照ください。

今回、検証を実施し時のBisep CLIのバージョンは「0.4.613」です。以下のコマンドをPowerShellにて実行することで確認が可能となります。

Bicep -v
Step1.ARMテンプレートからBicepへの変換の手順詳細
  1. PowerShellにてカレントディレクトリを対象ARMテンプレートの保存パスに変更します
    cd %保存ディレクトリ%
  2. 以下のコマンドによりBicepファイルへの変換を実行します。
    bicep decompile 【ファイル名】
Step1.ARMテンプレートからBicepへの変換の結果

以下の画像のようなエラーメッセージが表示されていますが、Bicepテンプレートの出力は問題なくできています。

エラー内容にある「any関数」についてはMicrosoft 公式サイトに以下のように記載があり、ARMテンプレート→Bicepで変換している今回のケースでは問題ないものと考えられます。

Bicep では、Bicep 型システムの型エラーを解決するために any() という関数がサポートされています。 この関数は、指定した値の形式が、型システムで予期されるものと一致しない場合に使用します。 たとえば、プロパティに数値が必要なときに、’0.5′ のように文字列として指定しなければならない場合があります。 any() 関数を使用すると、型システムによって報告されるエラーが抑制されます。
この関数は、Azure Resource Manager テンプレートのランタイムには存在しません。 これは Bicep によってのみ使用され、ビルド済みのテンプレートの JSON には生成されません。

https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/bicep-functions-any

変換の結果、出力されたBicepテンプレートの内容は以下です。元のARMテンプレートと比較して、以下の点で「より見やすく、使いやすく」なっていると言えます。

  • 元のARMテンプレートでは191行だったものが、Bicepでは150行まで圧縮されています。
  • 元のARMテンプレートではJsonのネストが最大8階層だったものが、Bicepでは最大7階層です。たったの1階層と思うかもしれませんが、ARMテンプレートでは全体が1つの大きな{}の中にくくりこまれていたのが、Bicepでは個々の要素が独立しているため、格段に見やすくなっています。
@description('Username for the Virtual Machine.')
param adminUsername string

@description('Password for the Virtual Machine.')
@minLength(12)
@secure()
param adminPassword string

var PrefixKey1 = 'FIXER'
var PrefixKey2 = 'blog'
var PrefixName = concat(PrefixKey1, PrefixKey2)
var ArrNsgsName_var = [
  '${PrefixName}-Nsg_Windows'
  '${PrefixName}-Nsg_Linux'
]
var Vnet = {
  name: '${PrefixName}-Vnet'
  addressPrefixes: '192.168.0.0/16'
}
var SubnetPrefixName = '${Vnet.name}/${PrefixName}'
var ArrSubnets = [
  {
    name: '${Vnet.name}/${PrefixName}-Subnet1st_Windows'
    subnetId: resourceId('Microsoft.Network/virtualNetworks/subnets', Vnet.name, '${PrefixName}-Subnet1st_Windows')
    addressPrefix: '192.168.0.0/24'
    nsgId: resourceId('Microsoft.Network/networkSecurityGroups', ArrNsgsName_var[0])
  }
  {
    name: '${Vnet.name}/${PrefixName}-Subnet2nd_Linux'
    subnetId: resourceId('Microsoft.Network/virtualNetworks/subnets', Vnet.name, '${PrefixName}-Subnet2nd_Linux')
    addressPrefix: '192.168.1.0/24'
    nsgId: resourceId('Microsoft.Network/networkSecurityGroups', ArrNsgsName_var[1])
  }
]
var ArrVms = [
  {
    vmNamePrefix: '${PrefixName}-VM_Windows'
    nicNamePrefix: '${PrefixName}-NIC_Windows'
    subnetRef: ArrSubnets[0].subnetId
    publisher: 'MicrosoftWindowsServer'
    offer: 'WindowsServer'
    sku: '2019-Datacenter'
  }
  {
    vmNamePrefix: '${PrefixName}-VM_Linux'
    nicNamePrefix: '${PrefixName}-NIC_Linux'
    subnetRef: ArrSubnets[1].subnetId
    publisher: 'OpenLogic'
    offer: 'CentOS'
    sku: '7.5'
  }
]

@batchSize(1)
resource ArrNsgsName 'Microsoft.Network/networkSecurityGroups@2015-06-15' = [for i in range(0, 2): {
  name: ArrNsgsName_var[i]
  location: resourceGroup().location
  properties: {
    securityRules: []
  }
}]

resource VNet_name 'Microsoft.Network/virtualNetworks@2015-06-15' = {
  name: Vnet.name
  location: resourceGroup().location
  properties: {
    addressSpace: {
      addressPrefixes: [
        Vnet.addressPrefixes
      ]
    }
  }
  dependsOn: []
}

@batchSize(1)
resource ArrSubnets_name 'Microsoft.Network/virtualNetworks/subnets@2015-06-15' = [for item in ArrSubnets: {
  name: item.name
  properties: {
    addressPrefix: item.addressPrefix
    networkSecurityGroup: {
      id: item.nsgId
    }
  }
  dependsOn: [
    item.nsgId
  ]
}]

@batchSize(1)
resource ArrVms_nicNamePrefix_VmId 'Microsoft.Network/networkInterfaces@2020-06-01' = [for (item, i) in ArrVms: {
  name: '${item.nicNamePrefix}_VmId_${i}'
  location: resourceGroup().location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          subnet: {
            id: item.subnetRef
          }
        }
      }
    ]
  }
  dependsOn: [
    item.subnetRef
  ]
}]

@batchSize(1)
resource ArrVms_vmNamePrefix_VmId 'Microsoft.Compute/virtualMachines@2020-06-01' = [for (item, i) in ArrVms: {
  name: '${item.vmNamePrefix}_VmId_${i}'
  location: resourceGroup().location
  properties: {
    hardwareProfile: {
      vmSize: 'Standard_A2_v2'
    }
    osProfile: {
      computerName: 'fixerblogvm'
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    storageProfile: {
      imageReference: {
        publisher: item.publisher
        offer: item.offer
        sku: item.sku
        version: 'latest'
      }
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: resourceId('Microsoft.Network/networkInterfaces', '${item.nicNamePrefix}_VmId_${i}')
        }
      ]
    }
  }
  dependsOn: [
    resourceId('Microsoft.Network/networkInterfaces', '${item.nicNamePrefix}_VmId_${i}')
  ]
}]

Step2. Bicepでのデプロイ

Azure PowerShellを使用してBicepをデプロイします。Azure PowerShellを触ったことが無い方は手前味噌ですが、過去の拙記事「Azure PowerShell に初めて挑戦する人のための手引き①【入門編】」を併せてご参照ください。

Step2.Bicepでのデプロイ の手順詳細
  1. 以下のコマンドAzureにログインします。
    Login-AzAccount
  2. 以下のコマンドによりサブスクリプションを選択します
    Select-AzSubscription -SubscriptionId 【サブスクリプションID】
  3. 以下のコマンドにより作成先のリソースグループ名を指定します。
    $rg = ‘bicep_loop’
  4. 以下のコマンドにより東日本リージョンにリソースグループを作成します。
    New-AzResourceGroup -Name $rg -Location "japaneast"
  5. 以下の対象のBicepのテンプレートファイルのローカルのフルパス(ドライブ文字からファイル名まで全て)を指定します。
    $templatefilepath = '【フルパス】'
  6. 以下の対象のパラメーターファイルのローカルのフルパス(ドライブ文字からファイル名まで全て)を指定します。
    $parameterfilepath = '【フルパス】'
  7. 以下のコマンドによりAzureのデプロイを実行します。
    New-AzResourceGroupDeployment -ResourceGroupName $rg -TemplateFile $templatefilepath -TemplateParameterFile $parameterfilepath
  8. PowerShell上でVMのログインパスワードを入力します。
Step2.Bicepでのデプロイ の結果

上記Step1で変換されたBicepテンプレートをそのままの状態でデプロイを試みるとデプロイが失敗しました。以下のように編集して再度、デプロイを試したところ成功し、問題なくデプロイが成功しました。編集した具体的な内容については、次の「重要なポイントの解説- ポイント(1)リソース間の依存関係の制御 」にて解説します。 前々回および前回の記事の検証では、ARMテンプレートから変換したBicepテンプレートでそのままデプロイができましたが、今回は編集が必要になったという点で大きく異なります。

またデプロイの成否とは直接、関係ないのですがあえて編集した箇所もあり、そちらについては「 重要なポイントの解説- ポイント(3) batchサイズの制御 」にて後述します。

@description('Username for the Virtual Machine.')
param adminUsername string

@description('Password for the Virtual Machine.')
@minLength(12)
@secure()
param adminPassword string

var PrefixKey1 = 'FIXER'
var PrefixKey2 = 'blog'
var PrefixName = concat(PrefixKey1, PrefixKey2)
var ArrNsgsName_var = [
  '${PrefixName}-Nsg_Windows'
  '${PrefixName}-Nsg_Linux'
]
var Vnet = {
  name: '${PrefixName}-Vnet'
  addressPrefixes: '192.168.0.0/16'
}
var SubnetPrefixName = '${Vnet.name}/${PrefixName}'
var ArrSubnets = [
  {
    name: '${Vnet.name}/${PrefixName}-Subnet1st_Windows'
    addressPrefix: '192.168.0.0/24'
    nsgId: ArrNsgsName[0].id
  }
  {
    name: '${Vnet.name}/${PrefixName}-Subnet2nd_Linux'
    addressPrefix: '192.168.1.0/24'
    nsgId: ArrNsgsName[1].id
  }
]
var ArrVms = [
  {
    vmNamePrefix: '${PrefixName}-VM_Windows'
    nicNamePrefix: '${PrefixName}-NIC_Windows'
    subnetRef: ArrSubnets_name[0].id
    publisher: 'MicrosoftWindowsServer'
    offer: 'WindowsServer'
    sku: '2019-Datacenter'
  }
  {
    vmNamePrefix: '${PrefixName}-VM_Linux'
    nicNamePrefix: '${PrefixName}-NIC_Linux'
    subnetRef: ArrSubnets_name[1].id
    publisher: 'OpenLogic'
    offer: 'CentOS'
    sku: '7.5'
  }
]

resource ArrNsgsName 'Microsoft.Network/networkSecurityGroups@2015-06-15' = [for i in range(0, 2): {
  name: ArrNsgsName_var[i]
  location: resourceGroup().location
  properties: {
    securityRules: []
  }
}]

resource VNet_name 'Microsoft.Network/virtualNetworks@2015-06-15' = {
  name: Vnet.name
  location: resourceGroup().location
  properties: {
    addressSpace: {
      addressPrefixes: [
        Vnet.addressPrefixes
      ]
    }
  }
  dependsOn: []
}

@batchSize(1)
resource ArrSubnets_name 'Microsoft.Network/virtualNetworks/subnets@2015-06-15' = [for item in ArrSubnets: {
  name: item.name
  properties: {
    addressPrefix: item.addressPrefix
    networkSecurityGroup: {
      id: item.nsgId
    }
  }
  dependsOn: []
}]

@batchSize(1)
resource ArrVms_nicNamePrefix_VmId 'Microsoft.Network/networkInterfaces@2020-06-01' = [for (item, i) in ArrVms: {
  name: '${item.nicNamePrefix}_VmId_${i}'
  location: resourceGroup().location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          subnet: {
            id: item.subnetRef
          }
        }
      }
    ]
  }
  dependsOn: []
}]

resource ArrVms_vmNamePrefix_VmId 'Microsoft.Compute/virtualMachines@2020-06-01' = [for (item, i) in ArrVms: {
  name: '${item.vmNamePrefix}_VmId_${i}'
  location: resourceGroup().location
  properties: {
    hardwareProfile: {
      vmSize: 'Standard_A2_v2'
    }
    osProfile: {
      computerName: 'fixerblogvm'
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    storageProfile: {
      imageReference: {
        publisher: item.publisher
        offer: item.offer
        sku: item.sku
        version: 'latest'
      }
      osDisk: {
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'StandardSSD_LRS'
        }
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: ArrVms_nicNamePrefix_VmId[i].id
        }
      ]
    }
  }
  dependsOn: []
}]

重要なポイントの解説

ポイント(1)リソース間の依存関係の制御

今回、Bicepテンプレートでのデプロイのために以下の2点の編集が必要となりましたのでそれぞれ開設します。

  • ポイント(1-1)dependsOn内の記述の削除
  • ポイント(1-2)resourceId関数からシンボリック参照への変更

上記はいずれも「リソース間の依存関係の制御」に関係します。ARMテンプレートの時代からAzure IaCにおいて最も重要なポイントで、少し昔(2019年9月)の拙記事でも解説していたのですが、Bicepでも引き続き重要であることが改めて確認できました。

ポイント(1-1)dependsOn内の記述の削除

Step1で出力されたBicepテンプレート内で 以下の3か所のいずれも dependsOnの中を削除しています。

  • l.86 item.nsgId
  • l.108 item.subnetRef
  • l.148 resourceId(‘Microsoft.Network/networkInterfaces’, ‘${item.nicNamePrefix}VmId${i}’)

前々回 記事の検証では ARMテンプレートをBicepテンプレートに変換した際に自動的に、depdensOn内の記述は自動的に削除されるのですが、現行のBicep CLIではループ処理内の depdensOn内の記述を自動削除できないものと考えられれます。

dependsOnに記載された依存関係は「明示的な依存関係」と呼ばれ、ARMテンプレートでは定義が必須なのですが、Bicepでは自動的に処理されるため、Microsoft 公式ドキュメントでは以下のように案内されています。

明示的な依存関係が必要になる場合もありますが、必要になることはめったにありません。 ほとんどの場合は、シンボリック参照を利用して、リソース間の依存関係を示します。

https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/resource-declaration?tabs=azure-powershell#explicit-dependency

Microsoft 公式ドキュメントにある「シンボリック参照」について次の「ポイント(1-2)resourceId関数からシンボリック参照への変更」にて解説します。

ポイント(1-2)resouceId関数からシンボリック参照への変更

リソース間の参照の定義について、以下の3か所計5行をBicep テンプレートを変更しています。IaCにそこまで慣れていない方でも、とても記述方法がシンプルに分かりやすくなったという印象はお持ちになるかと思います。

#1サブネット ⇒ ネットワークセキュリティグループ
変更前l.26 nsgId: resourceId(‘Microsoft.Network/networkSecurityGroups’, ArrNsgsName_var[0])
l.32 nsgId: resourceId(‘Microsoft.Network/networkSecurityGroups’, ArrNsgsName_var[1])
変更後l.25 nsgId: ArrNsgsName[0].id
l.30 nsgId: ArrNsgsName[1].id
#2仮想NIC ⇒ サブネット
変更前l.24 subnetId: resourceId(‘Microsoft.Network/virtualNetworks/subnets’, Vnet.name, ‘${PrefixName}-Subnet1st_Windows’)
l.30 subnetId: resourceId(‘Microsoft.Network/virtualNetworks/subnets’, Vnet.name, ‘${PrefixName}-Subnet2nd_Linux’)
変更後l.37 subnetRef: ArrSubnets_name[0].id
l.45 subnetRef: ArrSubnets_name[1].id
#3仮想マシン ⇒  仮想NIC
変更前l.142 id: resourceId(‘Microsoft.Network/networkInterfaces’, ‘${item.nicNamePrefix}VmId${i}’)
変更後l.134 id: ArrVms_nicNamePrefix_VmId[i].id

「シンボリック参照」自体がBicepによって新しく導入された概念です。「resource~」に続くデプロイの実行処理の各インスタンスに、「シンボリック名」というインスタン名が内部的に付与されデプロイ処理におけるリソース間の参照において利用されます。ARMテンプレートではシンボリック名に相当する概念が無く、 対象のAzure リソース名を使ってresourceIdを直接取得するのですが、式が複雑になることが多いです。

ポイント(2)ループ処理のパターン

Bicepの公式GItHubのWikiのループ処理のページ によるとBicepのループ処理には大きく分けて以下の2パターンが有ります。

  • ポイント(2-1)ループ処理の回数を静的に指定するパターン
  • ポイント(2-2)配列型変数を読み込んで、配列型変数の数だけループ処理の回数を動的に指定するパターン

特に「ポイント(2-2)配列型変数を読み込んで、配列型変数の数だけループ処理の回数を指定するパターンは画期的です。ARMテンプレートでは基本的な機能としては「ループ処理の回数を静的に指定する」ことしかできないため、配列型変数を利用するときなく風して処理を作りこむ必要が有りました。

具体的には以下のARMテンプレートの3か所が該当します。いずれも「length()関数」を使って配列の数を取得して、ループ処理のカウンターに値を渡しています。

l.101 “count”: “[length(variables(‘ArrSubnets’))]”,
l.121 “count”: “[length(variables(‘ArrVms’))]”,
l.149 “count”: “[length(variables(‘ArrVms’))]”,

ポイント(3)batchサイズの制御

ARMテンプレートから変換されたBicepテンプレートについて、l.54(ネットワークセキュリティグループの部分)とl.112( 仮想マシンの部分 )のそれぞれの「@batchSize(1)」を削除しています。元のARMテンプレートで明示的に指定していたのは、l.76(サブネット)とl.90(仮想NIC)の部分のみですが、変換すると自動的に指定されるようです。

「@batchSize(1)」とはループ処理において同時に作成される同じ種類のリソースの数を指定します。「サブネット」や「仮想NIC」は仮想ネットワークに対して登録処理をするのですが、1つずつでないと競合が発生して処理が失敗するので制御が必要です。

今後の構想

次の第4弾ではGitHubやAzure DevOpsとの連携させる観点から検証するつもりです。今まではVisual Studio CodeとPowerShellだけのローカル端末だけで完結する作業環境でやってきましたが、IaCの価値をさらに追及するため、GitHubやAzure DevOpsをターゲットとして狙っています。どうかご期待ください。

FIXER Inc. 藤井 廉
  • FIXER Inc. 藤井 廉
  • Cloud Solutions Engineer
    保有資格:
    Microsoft DevOps Engineer Expert(AZ-400)
    Microsoft Azure Solutions Architect Expert(AZ-300 & AZ-301)
    Azure Database Administrator Associate(DP-300)
    Microsoft Azure Developer Associate(AZ-203)
    Microsoft Azure Security Engineer Associate(AZ-500)
    Microsoft Azure Administrator Associate(AZ-102:制度変更により廃止された「70-533 Microsoft Azure Infrastructure Solutions の実装」の既合格者を対象とした移行試験で、AZ-103と同等の資格)