Azure がついに 7 周年らしいですね。おめでとうございます。(私は 20 歳で触り始めたので、気づけば 6 年以上使っていることに…。)
さて、昨日同僚から、「明日のトレーニングで使う VM を 40 台作りたい」と相談を受けて、片手間でデプロイしたので、落書き程度にメモを残しておきます。
1. 展開方法
詳しく話を聞くと、RDS 環境のトレーニング用に VM 5 台 (CB, SH1, SH2, Web, Client) x8 セットが明日までに欲しいとのこと。
パッと思いつく方法としては、以下の 4 通りくらいでしょうか。
- ポータルからポチポチする (論外)
- Azure PowerShell で単純にループさせてシリアルでデプロイする (遅い)
- Azure PowerShell でパラレルでデプロイする (面倒)
- JSON テンプレートを使う
どの方法でも作れますが、今日はデプロイに時間をかけられないので JSON にしました。(夕方相談を受けて、明日使うということだったので。)
なお、余談ですが PowerShell で複数リソースをパラレル展開する方法は以下にメモってあります。ただ、これも作成するリソース数が増えると、エラーを吐きやすくなってデバッグが面倒なので、今回は候補から除外。
2. JSON テンプレートの作り方
使い慣れていない人に PowerShell とか JSON テンプレートって言うとあからさまに嫌な顔されるんですが、一回作って慣れてしまえば大したことはないです。
JSON が初めての人は、横着せずに以下全部きっちり目を通しましょう。これだけ知っていれば十分なので。
で、具体的にどう作るかというと、既存のテンプレートをパクってきて切り貼りします。
今回は VM を大量に複製したいので、名前に loop と入った以下をベースにします。

このまま [Deploy to Azure] ボタンでサクっとデプロイできるのですが、40 と入れると 5 台までしか作れないとエラーになります。生の JSON を開いてみると、以下の通り numberOfInstances のパラメーターが maxValue 5 となっているので、40 などに書き換えてしまいましょう。
なお、運用環境の場合は 1 つのストレージ (20000 IOPS 上限) に大量の VHD (500 IOPS 上限) を配置するとパフォーマンスに影響があるので、適切にストレージを分割する必要があります。あと、Premium ストレージについては 35 TB の容量制限もあるので気を付けましょう。

"numberOfInstances": {
"type": "int",
"defaultValue": 2,
"minValue": 2,
"maxValue": 5,
"metadata": {
"description": "Number of VMs to deploy, limit 5 since this sample is using a single storage account"
}
},
次に Visualize ボタンを押して含まれるリソースを確認すると以下が入っていることがわかります。リソース名に copyindex() とついている VM と NIC は、1 つしか定義されていませんが、ループさせて必要数だけ作成されるようになっています。

生の JSON をさらに深堀してみると、例えば NIC は以下のように定義されています。name は nic0, nic1, nic2… などとなるように、copyindex() でループのカウンタ (0, 1, 2…) をセットして、concat で文字列を結合しています。また、copy セクションで count として設定されている数だけループするように定義してあります。(ここでは、あとで任意の値が設定できるよう、numberOfInstances というパラメーターが入っています。)
{
"type": "Microsoft.Network/networkInterfaces",
"name": "[concat('nic', copyindex())]",
"apiVersion": "2016-03-30",
"location": "[resourceGroup().location]",
"copy": {
"name": "nicLoop",
"count": "[parameters('numberOfInstances')]"
},
"dependsOn": [
"[variables('virtualNetworkName')]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnet1Ref')]"
}
}
}
]
}
},
同じく VM についても 1 台分しか定義されていませんが、ループさせて numberOfInstances 台だけ展開されるようになっています。その他、コンピューター名やVHD のパス、NIC の ID なども copyIndex() を使って定義されていることに気を付けましょう。
{
"type": "Microsoft.Compute/virtualMachines",
"name": "[concat('myvm', copyIndex())]",
"apiVersion": "2016-03-30",
"location": "[resourceGroup().location]",
"copy": {
"name": "virtualMachineLoop",
"count": "[parameters('numberOfInstances')]"
},
"dependsOn": [
"nicLoop",
"[variables('storageAccountName')]"
],
"properties": {
"availabilitySet": {
"id": "[resourceId('Microsoft.Compute/availabilitySets', variables('availabilitySetName'))]"
},
"hardwareProfile": {
"vmSize": "Standard_D1_v2"
},
"osProfile": {
"computerName": "[concat('vm', copyIndex())]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]"
},
"storageProfile": {
"imageReference": "[variables('imageReference')]",
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(reference(variables('StorageAccountName'), '2016-01-01').primaryEndpoints.blob, 'vhds/osdisk', copyIndex(), '.vhd')]"
},
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces',concat('nic', copyindex()))]"
}
]
}
}
}
各パラメーターを一通り見てみると、実は Public IP が付与されていないことが判明します。このままでは直接 RDP できないので、他の Public IP 付きの VM を展開するテンプレートから Public IP の定義と、NIC の依存関係の部分をパクってきます。
なお、この際にリソース名等に使われている変数 (variables) が定義されていなかったりするので、concat(‘pip’, copyIndex()) などへ変更し、Public IP と NIC を紐づけるなど、ベースのテンプレートと整合性が取れるように微修正します。
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Dynamic",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsLabelPrefix')]"
}
}
},
{
"apiVersion": "2016-03-30",
"type": "Microsoft.Network/networkInterfaces",
"name": "[variables('nicName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
"[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
}
},
とまあ、こんな具合でそれっぽいテンプレートが出来上がったら、ポータルの [新規] – [テンプレートのデプロイ] に張り付けて、デプロイしてみましょう。(コンピューター名を役割ごとに別で定義したかったので、以下では VM やら NIC、Public IP を 5 台分コピーして定義しています)

大体どっかしら間違っていて、展開前の検証フェーズで失敗すると思います。エラーに表示された内容やキーワードを頼りに、カッコがそろっているか、パラメーターが前後と整合性がとれているかなどを確認しましょう。あとは修正とデプロイを繰り返して、期待したものが出来上がるまで微調整すれば終わりです。

3. 余談
大量の VM を展開する際は、サブスクリプションのクォータに気を付けましょう。足りない場合は引き上げ申請をすればいいんですが、数日~1 週間程度かかるので、急ぎの場合はリージョンを変えればいいと思います。(ARM はリージョン毎にクォーター設定されているので。)
ちなみに、クォーターはプレビュー ポータル (https://preview.portal.azure.com) のサブスクリプション ブレードから細かく見れるようになっています。(そのうち一般のポータルにも来るはず?)

あと、JSON の編集は Visual Studio もしくは Visual Studio Code を使うとかなり便利です。
そんなこんなで、実質 2 時間くらいで無事デプロイが終わりました。クラウドの時代なので、このくらい片手間でサクっと出来ると便利ですね。業務効率化は正義!