はじめに
前回の記事では、ARM TemplateからBicepの移行を、リソースを限定したうえで実施して、いい結果を得ることが出来ました。
今度は、仮想マシン作成用のテンプレートを使い、Azure Compute GalleryからBicepで仮想マシンを展開してみたいと思います。
ARM Templateの確認
作成されるリソース
作成イメージはこんな感じです。

実際にデプロイした様子です。

テンプレートファイル
パラメーター、変数、ループ、依存関係などがあるのは前回同様ですが、仮想マシンや仮想マシン作成後のカスタムスクリプトの実行、Azure Key Vaultの参照など、より複雑になっています。
かなり長いので、参照時はクリックして展開したうえで見てください。
テンプレートファイル(クリックで展開)
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "numberOfInstances": { "defaultValue": 1, "minValue": 1, "maxValue": 20, "type": "Int", "metadata": { "description": "Number of VM Sets to deploy" } }, "adminPassword": { "type": "SecureString", "metadata": { "description": "Password for the Virtual Machine." } }, "labName": { "type": "String", "metadata": { "description": "Unique Prefix. ex) ad0717" } }, "scriptURI": { "type": "String" } }, "variables": { "adminUsername": "(管理者のID)", "addressPrefix": "10.0.0.0/16", "subnet1Name": "Subnet-1", "subnet1Prefix": "10.0.0.0/24", "imageReference": { "id": "(Azure Compute ImageのイメージID)" }, "location": "westus2", "privateIP-vm1" : "10.0.0.5" }, "resources": [ { "type": "Microsoft.Network/publicIPAddresses", "apiVersion": "2018-11-01", "name": "[concat(parameters('labName'),'-PIP-VM1-',copyindex())]", "location": "[variables('location')]", "properties": { "publicIPAllocationMethod": "Dynamic", "dnsSettings": { "domainNameLabel": "[concat(parameters('labName'),'-vm1-',copyindex())]" } }, "copy": { "name": "pip-vm1-Loop", "count": "[parameters('numberOfInstances')]" } }, { "type": "Microsoft.Network/networkSecurityGroups", "apiVersion": "2019-08-01", "name": "[concat(parameters('labName'),'-NSG-',copyindex())]", "location": "[variables('location')]", "properties": { "securityRules": [ { "name": "default-allow-RDP", "properties": { "priority": 1000, "access": "Allow", "direction": "Inbound", "destinationPortRange": "3389", "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationAddressPrefix": "*" } } ] }, "copy": { "name": "nsg-Loop", "count": "[parameters('numberOfInstances')]" } }, { "type": "Microsoft.Network/virtualNetworks", "apiVersion": "2016-03-30", "name": "[concat(parameters('labName'),'-VNET-',copyindex())]", "location": "[variables('location')]", "dependsOn": [ "[resourceId('Microsoft.Network/networkSecurityGroups', concat(parameters('labName'),'-NSG-',copyindex()))]" ], "properties": { "addressSpace": { "addressPrefixes": [ "[variables('addressPrefix')]" ] }, "subnets": [ { "name": "[variables('subnet1Name')]", "properties": { "addressPrefix": "[variables('subnet1Prefix')]", "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', concat(parameters('labName'),'-NSG-',copyindex()))]" } } } ] }, "copy": { "name": "vnet-Loop", "count": "[parameters('numberOfInstances')]" } }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2016-03-30", "name": "[concat(parameters('labName'),'-NIC-VM1-',copyindex())]", "location": "[variables('location')]", "dependsOn": [ "[concat(parameters('labName'),'-VNET-',copyindex())]", "pip-vm1-Loop" ], "properties": { "ipConfigurations": [ { "name": "ipconfig1", "properties": { "privateIPAllocationMethod": "Static", "privateIPAddress": "[variables('privateIP-vm1')]", "publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('labName'),'-PIP-VM1-',copyindex()))]" }, "subnet": { "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets',concat(parameters('labName'),'-VNET-',copyindex()),variables('subnet1Name'))]" } } } ] }, "copy": { "name": "nic-vm1-Loop", "count": "[parameters('numberOfInstances')]" } }, { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2022-03-01", "name": "[concat(parameters('labName'),'-VM1-',copyindex())]", "location": "[variables('location')]", "dependsOn": [ "nic-VM1-Loop" ], "properties": { "hardwareProfile": { "vmSize": "Standard_B2s" }, "osProfile": { "computerName": "[concat(parameters('labName'),'-VM1-',copyindex())]", "adminUsername": "[variables('adminUsername')]", "adminPassword": "[parameters('adminPassword')]" }, "storageProfile": { "imageReference": "[variables('imageReference')]", "osDisk": { "createOption": "FromImage", "managedDisk": { "storageAccountType": "Standard_LRS" } } }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('labName'),'-NIC-VM1-',copyindex()))]" } ] } }, "copy": { "name": "vm-VM1-Loop", "count": "[parameters('numberOfInstances')]" } }, { "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2019-12-01", "name": "[concat(parameters('labName'),'-VM1-',copyindex(),'/', 'Install-ADDS')]", "location": "[variables('location')]", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',parameters('labName'),'-VM1-',copyindex())]" ], "properties": { "publisher": "Microsoft.Compute", "type": "CustomScriptExtension", "typeHandlerVersion": "1.7", "autoUpgradeMinorVersion": true, "settings": { "fileUris": [ "[parameters('scriptURI')]" ], "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -File adds.ps1" } }, "copy": { "name": "vm-VM1-dsc-Loop", "count": "[parameters('numberOfInstances')]" } } ] }
パラメーターファイル
前出のテンプレートとセットで使用するパラメーターファイルです。
パラメーターファイル(クリックで展開)
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "adminPassword": { "reference": { "keyVault": { "id": "(Azure Key VaultのID)" }, "secretName": "(Azure Key Vaultのシークレット名)" } }, "numberOfInstances": { "value": 2 }, "scriptURI": { "value": "(ストレージアカウント上のスクリプトファイルのURI)" } } }
デプロイ用のPowershellスクリプト
テンプレートファイルとパラメーターファイルを使ってデプロイするためのスクリプトです。前回の記事と同じですが一応こちらにも書いておきます。
$labName = "(任意の英数字。lab1201など)" $location = "westus2" $today=Get-Date -Format "yyyy-MM-dd-hh-mm" $deployName = $labName + "-Deploy-" + $today $SubscriptionID = "(サブスクリプションID)" Connect-AzAccount $context = Get-AzSubscription -SubscriptionId $SubscriptionID Set-AzContext $context New-AzResourceGroup -Name $labName -Location $location New-AzResourceGroupDeployment -Name $deployName -ResourceGroupName $labName ` -TemplateFile "(テンプレートファイル名:例:test-template.json)" ` -TemplateParameterFile "(パラメーターファイル名:例:test-template.parameter.json)" ` -labName $labName
Bicepファイルの作成
ARM Templateのテンプレートファイルの変換
今回のテンプレートファイルも変換していきます。
bicep decompile this-template.json
やはりエラーが出ていてこのままでは使えないのですが、ひとまず出来ました。
Bicepファイル(クリックで展開)
@description('Number of VM Sets to deploy')
@minValue(1)
@maxValue(20)
param numberOfInstances int = 1
@description('Password for the Virtual Machine.')
@secure()
param adminPassword string
@description('Unique Prefix. ex) ad0717')
param labName string
param scriptURI string
var adminUsername = '(管理者のID)'
var addressPrefix = '10.0.0.0/16'
var subnet1Name = 'Subnet-1'
var subnet1Prefix = '10.0.0.0/24'
var imageReference = {
id: '(Azure Compute ImageのイメージID)'
}
var location = 'westus2'
var privateIP_vm1 = '10.0.0.5'
resource labName_PIP_VM1 'Microsoft.Network/publicIPAddresses@2018-11-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-PIP-VM1-${i}'
location: location
properties: {
publicIPAllocationMethod: 'Dynamic'
dnsSettings: {
domainNameLabel: '${labName}-vm1-${i}'
}
}
}]
resource labName_NSG 'Microsoft.Network/networkSecurityGroups@2019-08-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-NSG-${i}'
location: location
properties: {
securityRules: [
{
name: 'default-allow-RDP'
properties: {
priority: 1000
access: 'Allow'
direction: 'Inbound'
destinationPortRange: '3389'
protocol: 'Tcp'
sourceAddressPrefix: '*'
sourcePortRange: '*'
destinationAddressPrefix: '*'
}
}
]
}
}]
resource labName_VNET 'Microsoft.Network/virtualNetworks@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-VNET-${i}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnet1Name
properties: {
addressPrefix: subnet1Prefix
networkSecurityGroup: {
id: resourceId('Microsoft.Network/networkSecurityGroups', '${labName}-NSG-${i}')
}
}
}
]
}
dependsOn: [
resourceId('Microsoft.Network/networkSecurityGroups', '${labName}-NSG-${i}')
]
}]
resource labName_NIC_VM1 'Microsoft.Network/networkInterfaces@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-NIC-VM1-${i}'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Static'
privateIPAddress: privateIP_vm1
publicIPAddress: {
id: resourceId('Microsoft.Network/publicIPAddresses', '${labName}-PIP-VM1-${i}')
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', '${labName}-VNET-${i}', subnet1Name)
}
}
}
]
}
dependsOn: [
'${labName}-VNET-${i}'
labName_PIP_VM1
]
}]
resource labName_VM1 'Microsoft.Compute/virtualMachines@2022-03-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-VM1-${i}'
location: location
properties: {
hardwareProfile: {
vmSize: 'Standard_B2s'
}
osProfile: {
computerName: '${labName}-VM1-${i}'
adminUsername: adminUsername
adminPassword: adminPassword
}
storageProfile: {
imageReference: imageReference
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Standard_LRS'
}
}
}
networkProfile: {
networkInterfaces: [
{
id: resourceId('Microsoft.Network/networkInterfaces', '${labName}-NIC-VM1-${i}')
}
]
}
}
dependsOn: [
labName_NIC_VM1
]
}]
resource labName_VM1_Install_ADDS 'Microsoft.Compute/virtualMachines/extensions@2019-12-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-VM1-${i}/Install-ADDS'
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.7'
autoUpgradeMinorVersion: true
settings: {
fileUris: [
scriptURI
]
commandToExecute: 'powershell.exe -ExecutionPolicy Unrestricted -File adds.ps1'
}
}
dependsOn: [
'Microsoft.Compute/virtualMachines/${labName}-VM1-${i}'
]
}]
生成されたBicepファイルの修正
今回も、エラー発生個所は、各リソースのの依存関係に関する部分でした。一例です。
dependsOn: [
'Microsoft.Compute/virtualMachines/${labName}-VM1-${i}'
]
前回は、依存関係の表記を変えるだけだったのですが、そもそもBicepの場合、他のリソースを指定している場合は、暗黙的に依存関係が成り立つようです。
という事で、今回はdependsOnを削って、暗黙的な依存関係を使って修正していこうと思います。
例えば、仮想ネットワークはNSGに依存しているのですが
resource labName_VNET 'Microsoft.Network/virtualNetworks@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-VNET-${i}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnet1Name
properties: {
addressPrefix: subnet1Prefix
networkSecurityGroup: {
id: resourceId('Microsoft.Network/networkSecurityGroups', '${labName}-NSG-${i}')
}
}
}
]
}
dependsOn: [
resourceId('Microsoft.Network/networkSecurityGroups', '${labName}-NSG-${i}')
]
}]
dependsOnを削って、subnet > properties > networkSecurityGroupのidの指定方法をresource名を使った方法に変更します。ループを使っているので、それを考慮してid: labName_NSG[i].idのように書くのがポイントです。
resource labName_VNET 'Microsoft.Network/virtualNetworks@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-VNET-${i}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnet1Name
properties: {
addressPrefix: subnet1Prefix
networkSecurityGroup: {
id: labName_NSG[i].id
}
}
}
]
}
}]
同じように全体的に修正していきますが、ネットワークインターフェースでサブネットを指定するところだけ上記の書き方だと上手くいかず、次のように書きました。ループ使ってプロパティの深いところを指定する方法がちょっと難しかったです。
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', labName_VNET[i].name, subnet1Name)
}
また、最後に実施するカスタムスクリプトの実行部分だけは、暗黙的な依存関係が無かったので、dependsOnを使って明示的に設定しました。
修正後のBicepファイルはこうなりました。
修正後のBicep(クリックで展開)
@description('Number of VM Sets to deploy')
@minValue(1)
@maxValue(20)
param numberOfInstances int = 1
@description('Password for the Virtual Machine.')
@secure()
param adminPassword string
@description('Unique Prefix. ex) ad0717')
param labName string
param scriptURI string
var adminUsername = '(管理者のID)'
var addressPrefix = '10.0.0.0/16'
var subnet1Name = 'Subnet-1'
var subnet1Prefix = '10.0.0.0/24'
var imageReference = {
id: '(Azure Compute ImageのイメージID)'
}
var location = 'westus2'
var privateIP_vm1 = '10.0.0.5'
resource labName_PIP_VM1 'Microsoft.Network/publicIPAddresses@2018-11-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-PIP-VM1-${i}'
location: location
properties: {
publicIPAllocationMethod: 'Dynamic'
dnsSettings: {
domainNameLabel: '${labName}-vm1-${i}'
}
}
}]
resource labName_NSG 'Microsoft.Network/networkSecurityGroups@2019-08-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-NSG-${i}'
location: location
properties: {
securityRules: [
{
name: 'default-allow-RDP'
properties: {
priority: 1000
access: 'Allow'
direction: 'Inbound'
destinationPortRange: '3389'
protocol: 'Tcp'
sourceAddressPrefix: '*'
sourcePortRange: '*'
destinationAddressPrefix: '*'
}
}
]
}
}]
resource labName_VNET 'Microsoft.Network/virtualNetworks@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-VNET-${i}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: [
{
name: subnet1Name
properties: {
addressPrefix: subnet1Prefix
networkSecurityGroup: {
id: labName_NSG[i].id
}
}
}
]
}
}]
resource labName_NIC_VM1 'Microsoft.Network/networkInterfaces@2016-03-30' = [for i in range(0, numberOfInstances): {
name: '${labName}-NIC-VM1-${i}'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Static'
privateIPAddress: privateIP_vm1
publicIPAddress: {
id: labName_PIP_VM1[i].id
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', labName_VNET[i].name, subnet1Name)
}
}
}
]
}
}]
resource labName_VM1 'Microsoft.Compute/virtualMachines@2022-03-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-VM1-${i}'
location: location
properties: {
hardwareProfile: {
vmSize: 'Standard_B2s'
}
osProfile: {
computerName: '${labName}-VM1-${i}'
adminUsername: adminUsername
adminPassword: adminPassword
}
storageProfile: {
imageReference: imageReference
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Standard_LRS'
}
}
}
networkProfile: {
networkInterfaces: [
{
id: labName_NIC_VM1[i].id
}
]
}
}
}]
resource labName_VM1_Install_ADDS 'Microsoft.Compute/virtualMachines/extensions@2019-12-01' = [for i in range(0, numberOfInstances): {
name: '${labName}-VM1-${i}/Install-ADDS'
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.7'
autoUpgradeMinorVersion: true
settings: {
fileUris: [
scriptURI
]
commandToExecute: 'powershell.exe -ExecutionPolicy Unrestricted -File adds.ps1'
}
}
dependsOn: [
labName_VM1[i]
]
}]
実行結果
問題なくリソースを作成する事が出来ました。インストールスクリプトも問題なく動きました。
おわりに
今回は依存関係部分を暗黙的に変える対応もしたので少し苦戦しましたが、それでも比較的すんなり移行が出来ました。
ざっと触った感じのメリットです。
- 変数周りの扱いが楽になった
- 特に、パラメーターから受け取る値とテンプレート内の変数で扱いを分けなくていいのが楽
- ここ変数にしてたけどパラメーターに変えたいな、という時が特に楽
- 文字列結合周りの記述がシンプルになった
- 特に、パラメーターから受け取る値とテンプレート内の変数で扱いを分けなくていいのが楽
- 文字数が減って可読性が上がった
- 元のjsonファイルが214行(8368文字)で、Bicepファイルが152行(3904文字)なので文字数的には半分くらいになった、というところでしょうか
- 暗黙的な依存関係を使う事でシンプルになり、依存関係の考慮で悩む必要が減る
今後新しく作る場合や、既存のテンプレートを修正するときは、Bicepを積極的に使っていきたいと思います。
舟越 匠(日本ビジネスシステムズ株式会社)
人材開発部に所属。社内向けの技術研修をしつつ、JBS Tech Blog編集長を兼任。Power AutomateやLogic Appsが好きで、キーマンズネットでPower Automateの記事を書いたり、YouTubeのTechLIVE by ITmediaチャンネルでPower Automateの動画に出演したりもしています。好きなアーティストはZABADAKとSound Horizon。
担当記事一覧