JavaScriptを有効にしてください

Azure Verified Modules で Arc JumpStart HCIBox 検証環境をデプロイして隊

 ·   10 分で読めます

はじめに

Arc JumpStart では HCIBox という Hyper‑V ベースのオ Azure Local の検証環境が用意されています。
公式テンプレートは Bicep テンプレートですが、今回は Azure Verified Modules (AVM) に書き直し、検証環境を用意してみます。

対象読者

  • Arc JumpStart をカスタマイズして使いたいインフラ/クラウドエンジニア
  • Bicep や AVM に興味がある方
  • Azure Local の検証環境を手軽に構築したい方

ざっくり手順

  1. HCIBox のアーキテクチャ確認
  2. リソース構成の棚卸し
  3. mgmtArtifactsAndPolicyDeployment モジュール
  4. networkDeployment モジュール
  5. storageAccountDeployment モジュール
  6. hostDeployment モジュール
  7. customerUsageAttribution モジュール
  8. デプロイ
  9. デプロイ後の確認
  10. まとめ
  11. 参考リンク

1. HCIBox のアーキテクチャ確認

Arc Jumpstart のページにある HCIBox のアーキテクチャを見てみます

image01
HCIBox Architecture:

Windows Server 2025 Datacenter をデプロイし、Hyper-V 上に入れ子の仮想マシンを作成しています
入れ子の仮想マシンの構成イメージも見てみます

image02
Nested VM Architecture:

Azure Local のインスタンス以外に、ドメインコントローラとルーターを用意しています
Azure の DHCP は入れ子の仮想マシンへは IP アドレスを振ってくれないので、ルーターはそういったネットワークの制約を克服するために利用するんだと思います(勝手な想像

この既存のテンプレートを実行すると下記の通りのイメージで様々なリソースが作成されるそうです

image03
HCIBox Resources:

今回はこのうち、Azure としてのリソース作成部分のみを AVM で実装していきます

2. リソース構成の棚卸し

GitHub のリポジトリにある HCIBox のテンプレートを確認してみます
https://github.com/microsoft/azure_arc/blob/main/azure_jumpstart_hcibox/bicep/main.bicep

Visual Studio Code のコンテキストメニューから Bicep ファイルの可視化を実行しました
main.bicep はそれぞれのモジュールを呼び出しているだけであることがわかりました
main.bicep は編集しない方針で、それぞれのモジュールを AVM で書き換えていこうと思います

image04
Bicep Visualizer:

3. mgmtArtifactsAndPolicyDeployment モジュール

元の Bicep ファイルをコピペして各リソースを AVM に書き換えていきます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@description('Name for your log analytics workspace')
param workspaceName string

@description('Azure Region to deploy the Log Analytics Workspace')
param location string = resourceGroup().location

@description('SKU, leave default pergb2018')
param sku string = 'pergb2018'

param resourceTags object

var automationAccountName = 'HCIBox-Automation-${uniqueString(resourceGroup().id)}'
var automationAccountLocation = ((location == 'eastus') ? 'eastus2' : ((location == 'eastus2') ? 'eastus' : location))

resource workspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
  name: workspaceName
  location: location
  properties: {
    sku: {
      name: sku
    }
  }
  tags: resourceTags
}

resource automationAccount 'Microsoft.Automation/automationAccounts@2021-06-22' = {
  name: automationAccountName
  location: automationAccountLocation
  properties: {
    sku: {
      name: 'Basic'
    }
  }
  dependsOn: [
    workspace
  ]
  tags: resourceTags
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@description('Name for your log analytics workspace')
param workspaceName string

@description('Azure Region to deploy the Log Analytics Workspace')
param location string = resourceGroup().location

@description('SKU, leave default pergb2018')
param sku string = 'pergb2018'

param resourceTags object

var automationAccountName = 'HCIBox-Automation-${uniqueString(resourceGroup().id)}'
var automationAccountLocation = ((location == 'eastus') ? 'eastus2' : ((location == 'eastus2') ? 'eastus' : location))

// Create a Log Analytics Workspace by Azure Verified Module
module workspace 'br/public:avm/res/operational-insights/workspace:0.11.1' = {
  params: {
    name: workspaceName
    location: location
    skuName: sku
    tags: resourceTags
  }
}

// Create a Automation Account by Azure Verified Module
module automationAccount 'br/public:avm/res/automation/automation-account:0.14.1' = {
  params: {
    name: automationAccountName
    location: automationAccountLocation
    skuName: 'Basic'
    tags: resourceTags
  }
  dependsOn: [
    workspace
  ]
}

このモジュールで作成されるリソースはこちら

architecture-beta
group resourcegroup(azure:resource-groups)[Resource Group]
  service loganalytics(azure:log-analytics-workspaces)[Log Analytics Workspace] in resourcegroup
  service automationaccount(azure:automation-accounts)[Automation Account] in resourcegroup

4. networkDeployment モジュール

元の Bicep ファイルをコピペして各リソースを AVM に書き換えていきます

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
@description('Name of the VNet')
param virtualNetworkName string = 'HCIBox-VNet'

@description('Name of the subnet in the virtual network')
param subnetName string = 'HCIBox-Subnet'

@description('Azure Region to deploy the Log Analytics Workspace')
param location string = resourceGroup().location

@description('Choice to deploy Bastion to connect to the client VM')
param deployBastion bool = false

@description('Name of the Network Security Group')
param networkSecurityGroupName string = 'HCIBox-NSG'

@description('Name of the Bastion Network Security Group')
param bastionNetworkSecurityGroupName string = 'HCIBox-Bastion-NSG'

param resourceTags object

var addressPrefix = '172.16.0.0/16'
var subnetAddressPrefix = '172.16.1.0/24'
var bastionSubnetName = 'AzureBastionSubnet'
var bastionSubnetRef = '${arcVirtualNetwork.id}/subnets/${bastionSubnetName}'
var bastionName = 'HCIBox-Bastion'
var bastionSubnetIpPrefix = '172.16.3.64/26'
var bastionPublicIpAddressName = '${bastionName}-PIP'

resource arcVirtualNetwork 'Microsoft.Network/virtualNetworks@2021-03-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        addressPrefix
      ]
    }
    subnets: deployBastion == true ? [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetAddressPrefix
          privateEndpointNetworkPolicies: 'Enabled'
          privateLinkServiceNetworkPolicies: 'Enabled'
          networkSecurityGroup: {
            id: networkSecurityGroup.id
          }
        }
      }
      {
        name: 'AzureBastionSubnet'
        properties: {
          addressPrefix: bastionSubnetIpPrefix
          networkSecurityGroup: {
            id: bastionNetworkSecurityGroup.id
          }
        }
      }
    ] : [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetAddressPrefix
          privateEndpointNetworkPolicies: 'Enabled'
          privateLinkServiceNetworkPolicies: 'Enabled'
          networkSecurityGroup: {
            id: networkSecurityGroup.id
          }
        }
      }
    ]
  }
 tags: resourceTags
}

resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2021-03-01' = {
  name: networkSecurityGroupName
  location: location
  properties: {
    securityRules: [

    ]
  }
  tags: resourceTags
}

resource bastionNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2021-05-01' = if (deployBastion == true) {
  name: bastionNetworkSecurityGroupName
  location: location
  properties: {
    securityRules: [
      {
        name: 'bastion_allow_https_inbound'
        properties: {
          priority: 1010
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'Internet'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_gateway_manager_inbound'
        properties: {
          priority: 1011
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'GatewayManager'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_load_balancer_inbound'
        properties: {
          priority: 1012
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'AzureLoadBalancer'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_host_comms'
        properties: {
          priority: 1013
          protocol: '*'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '8080'
            '5701'
          ]
        }
      }
      {
        name: 'bastion_allow_ssh_rdp_outbound'
        properties: {
          priority: 1014
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '22'
            '3389'
          ]
        }
      }
      {
        name: 'bastion_allow_azure_cloud_outbound'
        properties: {
          priority: 1015
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'AzureCloud'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_bastion_comms'
        properties: {
          priority: 1016
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '8080'
            '5701'
          ]
        }
      }
      {
        name: 'bastion_allow_get_session_info'
        properties: {
          priority: 1017
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'Internet'
          destinationPortRanges: [
            '80'
            '443'
          ]
        }
      }
    ]
  }
  tags: resourceTags
}

resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2021-05-01' = if (deployBastion == true) {
  name: bastionPublicIpAddressName
  location: location
  properties: {
    publicIPAllocationMethod: 'Static'
    publicIPAddressVersion: 'IPv4'
    idleTimeoutInMinutes: 4
  }
  sku: {
    name: 'Standard'
  }
  tags: resourceTags
}

resource bastionHost 'Microsoft.Network/bastionHosts@2021-05-01' = if (deployBastion == true) {
  name: bastionName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'IpConf'
        properties: {
          publicIPAddress: {
            id: publicIpAddress.id
          }
          subnet: {
            id: bastionSubnetRef
          }
        }
      }
    ]
  }
  tags: resourceTags
}

output vnetId string = arcVirtualNetwork.id
output subnetId string = arcVirtualNetwork.properties.subnets[0].id
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
@description('Name of the VNet')
param virtualNetworkName string = 'HCIBox-VNet'

@description('Name of the subnet in the virtual network')
param subnetName string = 'HCIBox-Subnet'

@description('Azure Region to deploy the Log Analytics Workspace')
param location string = resourceGroup().location

@description('Choice to deploy Bastion to connect to the client VM')
param deployBastion bool = false

@description('Name of the Network Security Group')
param networkSecurityGroupName string = 'HCIBox-NSG'

@description('Name of the Bastion Network Security Group')
param bastionNetworkSecurityGroupName string = 'HCIBox-Bastion-NSG'

param resourceTags object

var addressPrefix = '172.16.0.0/16'
var subnetAddressPrefix = '172.16.1.0/24'
var bastionSubnetName = 'AzureBastionSubnet'
var bastionName = 'HCIBox-Bastion'
var bastionSubnetIpPrefix = '172.16.3.64/26'
var bastionPublicIpAddressName = '${bastionName}-PIP'

// Create Virtual Network by Azure Verified Module
module arcVirtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = {
  params: {
    name: virtualNetworkName
    location: location
    addressPrefixes: [
      addressPrefix
    ]
    subnets: deployBastion == true ? [
      {
        name: subnetName
        addressPrefix: subnetAddressPrefix
        privateEndpointNetworkPolicies: 'Enabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        networkSecurityGroupResourceId: networkSecurityGroup.outputs.resourceId
      }
      {
        name: bastionSubnetName
        addressPrefix: bastionSubnetIpPrefix
        networkSecurityGroupResourceId: bastionNetworkSecurityGroup.outputs.resourceId
      }
    ] :[
      {
        name:subnetName
        addressPrefix: subnetAddressPrefix
        privateEndpointNetworkPolicies: 'Enabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        networkSecurityGroupResourceId: networkSecurityGroup.outputs.resourceId
      }
    ]
    tags: resourceTags
  }
}

// Create Network Security Group for HCIBox-Subnet by Azure Verified Module
module networkSecurityGroup 'br/public:avm/res/network/network-security-group:0.5.1' = {
  params: {
    name: networkSecurityGroupName
    location: location
  }
}

// Create Network Security Group for Bastion by Azure Verified Module
module bastionNetworkSecurityGroup 'br/public:avm/res/network/network-security-group:0.5.1' = if (deployBastion == true)  {
  params: {
    name: bastionNetworkSecurityGroupName
    location: location
    securityRules: [
      {
        name: 'bastion_allow_https_inbound'
        properties: {
          priority: 1010
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'Internet'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_gateway_manager_inbound'
        properties: {
          priority: 1011
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'GatewayManager'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_load_balancer_inbound'
        properties: {
          priority: 1012
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'AzureLoadBalancer'
          sourcePortRange: '*'
          destinationAddressPrefix: '*'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_host_comms'
        properties: {
          priority: 1013
          protocol: '*'
          access: 'Allow'
          direction: 'Inbound'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '8080'
            '5701'
          ]
        }
      }
      {
        name: 'bastion_allow_ssh_rdp_outbound'
        properties: {
          priority: 1014
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '22'
            '3389'
          ]
        }
      }
      {
        name: 'bastion_allow_azure_cloud_outbound'
        properties: {
          priority: 1015
          protocol: 'Tcp'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'AzureCloud'
          destinationPortRange: '443'
        }
      }
      {
        name: 'bastion_allow_bastion_comms'
        properties: {
          priority: 1016
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRanges: [
            '8080'
            '5701'
          ]
        }
      }
      {
        name: 'bastion_allow_get_session_info'
        properties: {
          priority: 1017
          protocol: '*'
          access: 'Allow'
          direction: 'Outbound'
          sourceAddressPrefix: '*'
          sourcePortRange: '*'
          destinationAddressPrefix: 'Internet'
          destinationPortRanges: [
            '80'
            '443'
          ]
        }
      }
    ]
    tags: resourceTags
  }
}

// Create Public IP Address for Bastion by Azure Verified Module
module publicIpAddress 'br/public:avm/res/network/public-ip-address:0.8.0' = if (deployBastion == true)  {
  params: {
    name: bastionPublicIpAddressName
    location: location
    publicIPAllocationMethod: 'Static'
    publicIPAddressVersion: 'IPv4'
    idleTimeoutInMinutes: 4
    skuName: 'Standard'
    tags: resourceTags
  }
}

// Create Bastion Host by Azure Verified Module
module bastionHost1 'br/public:avm/res/network/bastion-host:0.6.1' = if (deployBastion == true)  {
  params: {
    name: bastionName
    location: location
    virtualNetworkResourceId: arcVirtualNetwork.outputs.resourceId
    bastionSubnetPublicIpResourceId: publicIpAddress.outputs.resourceId
    tags: resourceTags
  }
}

output vnetId string = arcVirtualNetwork.outputs.resourceId
output subnetId string = arcVirtualNetwork.outputs.subnetResourceIds[0]

このモジュールで作成されるリソースはこちら

architecture-beta


group vnet(azure:virtual-networks)[HCIBox VNet]
  service subnet1(azure:subnet)[HCIBox Subnet] in vnet
  group subnet2(azure:subnet)[AzureBastionSubnet] in vnet
      service bastion(azure:bastions)[HCIBox Bastion] in subnet2
  service nsg1(azure:network-security-groups)[HCIBox NSG]
  service nsg2(azure:network-security-groups)[HCIBox Bastion NSG]
  service pip(azure:public-ip-addresses)[HCIBox Bastion PIP]
  
subnet1:L -- R:nsg1
bastion{group}:L -- R:nsg2
bastion:T -- B:pip

5. storageAccountDeployment モジュール

元の Bicep ファイルをコピペして各リソースを AVM に書き換えていきます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@description('Storage Account type')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_ZRS'
  'Premium_LRS'
])
param storageAccountType string = 'Standard_LRS'

@description('Location for all resources.')
param location string = resourceGroup().location

param resourceTags object

var storageAccountName = 'hcibox${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: storageAccountType
  }
  kind: 'StorageV2'
  properties: {
    supportsHttpsTrafficOnly: true
  }
  tags: resourceTags
}

output storageAccountName string = storageAccountName

AVM では Https は既定で有効化なのでパラメータとしては指定していない

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@description('Storage Account type')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_ZRS'
  'Premium_LRS'
])
param storageAccountType string = 'Standard_LRS'

@description('Location for all resources.')
param location string = resourceGroup().location

param resourceTags object

var storageAccountName = 'hcibox${uniqueString(resourceGroup().id)}'

// Create Storage Account by Azure Verified Module
module storageAccount 'br/public:avm/res/storage/storage-account:0.19.0' = {
  params: {
    name: storageAccountName
    location: location
    skuName: storageAccountType
    kind: 'StorageV2'
    tags: resourceTags
  }
}

output storageAccountName string = storageAccountName

このモジュールで作成されるリソースはこちら

architecture-beta
group resourcegroup(azure:resource-groups)[Resource Group]
  service storageaccount(azure:storage-accounts)[Storage Account] in resourcegroup

6. hostDeployment モジュール

元の Bicep ファイルをコピペして各リソースを AVM に書き換えていきます

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
@description('The name of your Virtual Machine')
param vmName string = 'HCIBox-Client'

@description('The size of the Virtual Machine')
@allowed([
  'Standard_E32s_v5'
  'Standard_E32s_v6'
])
param vmSize string = 'Standard_E32s_v5'

@description('Username for the Virtual Machine')
param windowsAdminUsername string = 'arcdemo'

@description('Password for Windows account. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character. The value must be between 12 and 123 characters long')
@minLength(12)
@maxLength(123)
@secure()
param windowsAdminPassword string

@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version')
param windowsOSVersion string = '2025-datacenter-g2'

@description('Location for all resources')
param location string = resourceGroup().location

@description('Resource Id of the subnet in the virtual network')
param subnetId string

param resourceTags object

@description('Client id of the service principal')
param spnClientId string

@description('Client secret of the service principal')
@secure()
param spnClientSecret string

@description('Tenant id of the service principal')
param spnTenantId string

@description('Azure AD object id for your Microsoft.AzureStackHCI resource provider')
param spnProviderId string

@description('Name for the staging storage account using to hold kubeconfig. This value is passed into the template as an output from mgmtStagingStorage.json')
param stagingStorageAccountName string

@description('Name for the environment Azure Log Analytics workspace')
param workspaceName string

@description('The base URL used for accessing artifacts and automation artifacts.')
param templateBaseUrl string

@description('Option to disable automatic cluster registration. Setting this to false will also disable deploying AKS and Resource bridge')
param registerCluster bool = true

@description('Choice to deploy Bastion to connect to the client VM')
param deployBastion bool = false

@description('Option to deploy AKS-HCI with HCIBox')
param deployAKSHCI bool = true

@description('Option to deploy Resource Bridge with HCIBox')
param deployResourceBridge bool = true

@description('Public DNS to use for the domain')
param natDNS string = '8.8.8.8'

@description('Override default RDP port using this parameter. Default is 3389. No changes will be made to the client VM.')
param rdpPort string = '3389'

@description('Choice to enable automatic deployment of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Default is false.')
param autoDeployClusterResource bool = false

@description('Choice to enable automatic upgrade of Azure Arc enabled HCI cluster resource after the client VM deployment is complete. Only applicable when autoDeployClusterResource is true. Default is false.')
param autoUpgradeClusterResource bool = false

@description('Enable automatic logon into HCIBox Virtual Machine')
param vmAutologon bool = false

var encodedPassword = base64(windowsAdminPassword)
var bastionName = 'HCIBox-Bastion'
var publicIpAddressName = deployBastion == false ? '${vmName}-PIP' : '${bastionName}-PIP'
var networkInterfaceName = '${vmName}-NIC'
var osDiskType = 'Premium_LRS'
var PublicIPNoBastion = {
  id: publicIpAddress.id
}

resource networkInterface 'Microsoft.Network/networkInterfaces@2021-03-01' = {
  name: networkInterfaceName
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: subnetId
          }
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: deployBastion == false ? PublicIPNoBastion : null
        }
      }
    ]
  }
  tags: resourceTags
}

resource publicIpAddress 'Microsoft.Network/publicIpAddresses@2021-03-01' = if (deployBastion == false) {
  name: publicIpAddressName
  location: location
  properties: {
    publicIPAllocationMethod: 'Static'
    publicIPAddressVersion: 'IPv4'
    idleTimeoutInMinutes: 4
  }
  sku: {
    name: 'Basic'
  }
  tags: resourceTags
}

resource vm 'Microsoft.Compute/virtualMachines@2022-03-01' = {
  name: vmName
  location: location
  tags: resourceTags
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    storageProfile: {
      osDisk: {
        name: '${vmName}-OSDisk'
        caching: 'ReadWrite'
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: osDiskType
        }
        diskSizeGB: 1024
      }
      imageReference: {
        publisher: 'MicrosoftWindowsServer'
        offer: 'WindowsServer'
        sku: windowsOSVersion
        version: 'latest'
      }
      dataDisks: [
        {
          name: 'ASHCIHost001_DataDisk_0'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 0
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_1'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 1
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_2'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 2
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_3'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 3
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_4'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 4
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_5'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 5
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_6'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 6
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
        {
          name: 'ASHCIHost001_DataDisk_7'
          diskSizeGB: 256
          createOption: 'Empty'
          lun: 7
          caching: 'None'
          writeAcceleratorEnabled: false
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
      ]
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: networkInterface.id
        }
      ]
    }
    osProfile: {
      computerName: vmName
      adminUsername: windowsAdminUsername
      adminPassword: windowsAdminPassword
      windowsConfiguration: {
        provisionVMAgent: true
        enableAutomaticUpdates: false
      }
    }
  }
}

resource vmBootstrap 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = {
  parent: vm
  name: 'Bootstrap'
  location: location
  properties: {
    publisher: 'Microsoft.Compute'
    type: 'CustomScriptExtension'
    typeHandlerVersion: '1.10'
    autoUpgradeMinorVersion: true
    protectedSettings: {
      fileUris: [
        uri(templateBaseUrl, 'artifacts/PowerShell/Bootstrap.ps1')
      ]
      commandToExecute: 'powershell.exe -ExecutionPolicy Bypass -File Bootstrap.ps1 -adminUsername ${windowsAdminUsername} -adminPassword ${encodedPassword} -spnClientId ${spnClientId} -spnClientSecret ${spnClientSecret} -spnTenantId ${spnTenantId} -subscriptionId ${subscription().subscriptionId} -spnProviderId ${spnProviderId} -resourceGroup ${resourceGroup().name} -azureLocation ${location} -stagingStorageAccountName ${stagingStorageAccountName} -workspaceName ${workspaceName} -templateBaseUrl ${templateBaseUrl} -registerCluster ${registerCluster} -deployAKSHCI ${deployAKSHCI} -deployResourceBridge ${deployResourceBridge} -natDNS ${natDNS} -rdpPort ${rdpPort} -autoDeployClusterResource ${autoDeployClusterResource} -autoUpgradeClusterResource ${autoUpgradeClusterResource} -vmAutologon ${vmAutologon}'
    }
  }
}

// Add role assignment for the VM: Owner role
resource vmRoleAssignment_Owner 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(vm.id, 'Microsoft.Authorization/roleAssignments', 'Owner')
  scope: resourceGroup()
  properties: {
    principalId: vm.identity.principalId
    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
    principalType: 'ServicePrincipal'
  }
}

output adminUsername string = windowsAdminUsername
output publicIP string = deployBastion == false ? concat(publicIpAddress.properties.ipAddress) : ''

AVM では VM をデプロイするモジュールに NIC の作成が含まれている
VM カスタムスクリプト拡張機能も整備されており利用している

ドメインの修正をしたいから、一旦保留

ディスクが多くてぐちゃぐちゃしてますが、、このモジュールで作成されるリソースはこちら

architecture-beta

group virtualnetwork(azure:virtual-networks)[Virtual Network]
  group subnet(azure:subnet)[Subnet] in virtualnetwork
    service vm(azure:virtual-machine)[Virtual Machine] in subnet
service publicip(azure:public-ip-addresses)[Public IP Address]
service osdisk(azure:disks)[OS Disk]

junction disksjjunction
service datadisk1(azure:disks)[Data Disk 1] 
service datadisk2(azure:disks)[Data Disk 2] 
service datadisk3(azure:disks)[Data Disk 3] 
service datadisk4(azure:disks)[Data Disk 4] 
service datadisk5(azure:disks)[Data Disk 5] 
service datadisk6(azure:disks)[Data Disk 6] 
service datadisk7(azure:disks)[Data Disk 7] 
service datadisk8(azure:disks)[Data Disk 8] 

service roleassignment(azure:managed-identities)[Role Assignment]

vm:R -- L:publicip
vm:B -- T:disksjjunction
disksjjunction:L -- R:osdisk
disksjjunction:L -- R:datadisk1
disksjjunction:L -- R:datadisk2
disksjjunction:B -- T:datadisk3
disksjjunction:B -- T:datadisk4
disksjjunction:B -- T:datadisk5
disksjjunction:R -- L:datadisk6
disksjjunction:R -- L:datadisk7
disksjjunction:R -- L:datadisk8
vm:L -- R:roleassignment

7. customerUsageAttribution モジュール

よくわかりませんが、空っぽなのでそのまま
調べてみると、自分が知らぬ存ぜぬのサブスクリプションでの利用状況を見れるらしいです
Azure 顧客の使用状況の属性 - Marketplace publisher | Microsoft Learn

8. デプロイ

AVM に書き換えた Bicep ファイルをデプロイしてみます
毎回 CLI でするのは面倒なので Template Spec をまずは作ります

1
az ts create --name hci_avm  --version "1.0" --resource-group rg-templateSpecs --location "japaneast" --template-file "./main.bicep"

AVM は W-AF (Well-Architected Framework) に準拠しているので、VM は既定でホストでの暗号化が有効化されます
この機能は事前にサブスクリプションで有効化しておく必要があります
完了まで10分くらいかかるので、のんびり待ちます

1
az feature register --name EncryptionAtHost  --namespace Microsoft.Compute

この Template Spec を使って HCI Box をデプロイします

image05
template Spec:

パラメータは ArcJumpStart の手順を参考に埋めます
Azure Arc Jumpstart

image06
template Spec:

9. デプロイ後の確認

Azure リソースとしては問題なくデプロイできました
HCIBox としてはこのあと、VM にログインをすると色々と処理が走ります
今回は Azure リソース部分にフォーカスしたので、VM の中身は割愛します(処理が終わるまでに数時間かかるので、、、)

image07
色んな処理が実行:

まとめ

AVM を使って HCIBox を書き換えました
HCIBox で作成されるリソースの関係性がよくわかって勉強になりました

今回作成した Bicep ファイルはこちらです
Azure_Bicep/products/jumpstart_hcibox at main · NakayamaKento/Azure_Bicep

参考

共有

Kento
著者
Kento
2020年に新卒で IT 企業に入社. インフラエンジニア(主にクラウド)として活動中