Azure Resource Manager テンプレートを使用した仮想マシンのデプロイ

Azure Resource Manager テンプレートを使用して Azure 上に仮想マシンをデプロイします。環境は以下の通り。

- Windows 10 Enterprise
- PowerShell 5.1

前提条件として Azure RM PowerShell モジュールのインストールが必要です。インストールしていない場合は以下のコマンドでインストールを実行します。（わりと時間がかかります）

```PowerShell
Install-Module -Name AzureRM
``` 

テンプレートとパラメーターのjsonを用意します。テンプレートとパラメーターはポータルからの作成時にダウンロードできるので、それを元に多少修正したものを使います。

# テンプレート

```json:template.json
{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "type": "string"
        },
        "virtualMachineName": {
            "type": "string"
        },
        "storageAccountType": {
            "type": "string"
        },
        "virtualMachineSize": {
            "type": "string"
        },
        "virtualMachinePublisher": {
            "type": "string"
        },
        "virtualMachineOffer": {
            "type": "string"
        },
        "virtualMachineSku": {
            "type": "string"
        },
        "adminUsername": {
            "type": "string"
        },
        "adminPassword": {
            "type": "securestring"
        },
        "virtualNetworkName": {
            "type": "string"
        },
        "addressPrefix": {
            "type": "string"
        },
        "subnetName": {
            "type": "string"
        },
        "subnetPrefix": {
            "type": "string"
        },
        "networkInterfaceName": {
            "type": "string"
        },
        "privateIpAddress": {
            "type": "string"
        },
        "publicIpAddressName": {
            "type": "string"
        },
        "publicIpAddressType": {
            "type": "string"
        },
        "networkSecurityGroupName": {
            "type": "string"
        }
    },
    "variables": {
        "vnetId": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
        "subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]"
    },
    "resources": [
        {
            "name": "[parameters('virtualMachineName')]",
            "type": "Microsoft.Compute/virtualMachines",
            "apiVersion": "2016-04-30-preview",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[concat('Microsoft.Network/networkInterfaces/', parameters('networkInterfaceName'))]"
            ],
            "properties": {
                "osProfile": {
                    "computerName": "[parameters('virtualMachineName')]",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]",
                    "windowsConfiguration": {
                        "provisionVmAgent": "true"
                    }
                },
                "hardwareProfile": {
                    "vmSize": "[parameters('virtualMachineSize')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "[parameters('virtualMachinePublisher')]",
                        "offer": "[parameters('virtualMachineOffer')]",
                        "sku": "[parameters('virtualMachineSku')]",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "[parameters('virtualMachineName')]",
                        "createOption": "fromImage",
                        "managedDisk": {
                            "storageAccountType": "[parameters('storageAccountType')]"
                        }
                    },
                    "dataDisks": []
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]"
                        }
                    ]
                }
            }
        },
        {
            "name": "[parameters('virtualNetworkName')]",
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2016-09-01",
            "location": "[parameters('location')]",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[parameters('addressPrefix')]"
                    ]
                },
                "subnets": [
                    {
                        "name": "[parameters('subnetName')]",
                        "properties": {
                            "addressPrefix": "[parameters('subnetPrefix')]"
                        }
                    }
                ]
            }
        },
        {
            "name": "[parameters('networkInterfaceName')]",
            "type": "Microsoft.Network/networkInterfaces",
            "apiVersion": "2016-09-01",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[concat('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]",
                "[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIpAddressName'))]",
                "[concat('Microsoft.Network/networkSecurityGroups/', parameters('networkSecurityGroupName'))]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipconfig1",
                        "properties": {
                            "subnet": {
                                "id": "[variables('subnetRef')]"
                            },
                            "privateIPAllocationMethod": "Static",
                            "privateIPAddress": "[parameters('privateIpAddress')]",
                            "publicIpAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName'))]"
                            }
                        }
                    }
                ],
                "networkSecurityGroup": {
                    "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]"
                }
            }
        },
        {
            "name": "[parameters('publicIpAddressName')]",
            "type": "Microsoft.Network/publicIpAddresses",
            "apiVersion": "2016-09-01",
            "location": "[parameters('location')]",
            "properties": {
                "publicIpAllocationMethod": "[parameters('publicIpAddressType')]"
            }
        },
        {
            "name": "[parameters('networkSecurityGroupName')]",
            "type": "Microsoft.Network/networkSecurityGroups",
            "apiVersion": "2016-09-01",
            "location": "[parameters('location')]",
            "properties": {
                "securityRules": [
                    {
                        "name": "default-allow-rdp",
                        "properties": {
                            "priority": 1000,
                            "sourceAddressPrefix": "*",
                            "protocol": "TCP",
                            "destinationPortRange": "3389",
                            "access": "Allow",
                            "direction": "Inbound",
                            "sourcePortRange": "*",
                            "destinationAddressPrefix": "*"
                        }
                    }
                ]
            }
        }
    ],
    "outputs": {
        "adminUsername": {
            "type": "string",
            "value": "[parameters('adminUsername')]"
        }
    }
}
```

# パラメーター

```json:parameter.json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "value": "japanwest"
        },
        "virtualMachineName": {
            "value": "ap"
        },
        "storageAccountType": {
            "value": "Premium_LRS"
        },
        "virtualMachineSize": {
            "value": "Standard_DS1_v2"
        },
        "virtualMachinePublisher": {
            "value": "MicrosoftWindowsServer"
        },
        "virtualMachineOffer": {
            "value": "WindowsServer"
        },
        "virtualMachineSku": {
            "value": "2016-Datacenter"
        },
        "adminUsername": {
            "value": "CloudAdmin"
        },
        "adminPassword": {
            "value": "P@ssw0rd1234"
        },
        "virtualNetworkName": {
            "value": "vnet"
        },
        "addressPrefix": {
            "value": "10.0.0.0/16"
        },
        "subnetName": {
            "value": "default"
        },
        "subnetPrefix": {
            "value": "10.0.0.0/24"
        },
        "networkInterfaceName": {
            "value": "ap-nic"
        },
        "privateIpAddress": {
            "value": "10.0.0.11"
        },
        "publicIpAddressName": {
            "value": "ap-ip"
        },
        "publicIpAddressType": {
            "value": "Dynamic"
        },
        "networkSecurityGroupName": {
            "value": "ap-nsg"
        }
    }
}
```

上記のパラメーターを使用すると以下の状態でデプロイされます。デプロイ時は適宜パラメーターを修正してください。

- デプロイ先は西日本
- 仮想マシンの名前は ap
- ストレージアカウントは Premium LRS (SSD)
- 仮想マシンのサイズは Standard DS1 v2
- 仮想マシンの OS は Windows Server 2016 Datacenter
- 管理者ユーザーの名前は CloudAdmin
- パスワードは P@ssw0rd1234
- 仮想ネットワークの名前は vnet
- 仮想ネットワークのセグメントは 10.0.0.0/16
- 仮想ネットワークのサブネットの名前は subnet
- サブネットのセグメントは 10.0.0.0/24
- 仮想マシンの NIC の名前は ap-nic
- NIC の 内部 IP アドレスは 10.0.0.11
- 外部 IP アドレスは動的
- 仮想マシンのネットワークセキュリティグループの名前は ap-nsg

パラメーターの指定の仕方が分からない場合はとりあえずポータルで作ってみてテンプレートとパラメーターをダウンロードしてみることをお勧めします。指定できる値の一覧は `Get-AzureRm*` コマンドを使えば大体拾ってくることができます。例えば Location の一覧を確認したい場合は `Get-AzureRmLocation` コマンドで取得可能です。

# デプロイ用 PowerShell スクリプト

```PowerShell:deploy.ps1
<#
 .SYNOPSIS
    Deploys a template to Azure

 .DESCRIPTION
    Deploys an Azure Resource Manager template

 .PARAMETER subscriptionId
    The subscription id where the template will be deployed.

 .PARAMETER resourceGroupName
    The resource group where the template will be deployed. Can be the name of an existing or a new resource group.

 .PARAMETER resourceGroupLocation
    Optional, a resource group location. If specified, will try to create a new resource group in this location. If not specified, assumes resource group is existing.

 .PARAMETER deploymentName
    The deployment name.

 .PARAMETER templateFilePath
    Optional, path to the template file. Defaults to template.json.

 .PARAMETER parametersFilePath
    Optional, path to the parameters file. Defaults to parameters.json. If file is not found, will prompt for parameter values based on template.
#>

param(
 [Parameter(Mandatory=$True)]
 [string]
 $subscriptionId,

 [Parameter(Mandatory=$True)]
 [string]
 $resourceGroupName,

 [string]
 $resourceGroupLocation,

 [Parameter(Mandatory=$True)]
 [string]
 $deploymentName,

 [string]
 $templateFilePath = "template.json",

 [string]
 $parametersFilePath = "parameters.json"
)

<#
.SYNOPSIS
    Registers RPs
#>
Function RegisterRP {
    Param(
        [string]$ResourceProviderNamespace
    )

    Write-Host "Registering resource provider '$ResourceProviderNamespace'";
    Register-AzureRmResourceProvider -ProviderNamespace $ResourceProviderNamespace;
}

#******************************************************************************
# Script body
# Execution begins here
#******************************************************************************
$ErrorActionPreference = "Stop"

# sign in
try {
    Get-AzureRmSubscription | Out-Null
}
catch {
    Write-Host "Logging in...";
    Login-AzureRmAccount;
}

# select subscription
Write-Host "Selecting subscription '$subscriptionId'";
Select-AzureRmSubscription -SubscriptionID $subscriptionId;

# Register RPs
$resourceProviders = @("microsoft.compute","microsoft.network");
if($resourceProviders.length) {
    Write-Host "Registering resource providers"
    foreach($resourceProvider in $resourceProviders) {
        RegisterRP($resourceProvider);
    }
}

#Create or check for existing resource group
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if(!$resourceGroup)
{
    Write-Host "Resource group '$resourceGroupName' does not exist. To create a new resource group, please enter a location.";
    if(!$resourceGroupLocation) {
        $resourceGroupLocation = Read-Host "resourceGroupLocation";
    }
    Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
    New-AzureRmResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
}
else{
    Write-Host "Using existing resource group '$resourceGroupName'";
}

# Start the deployment
Write-Host "Starting deployment...";
if(Test-Path $parametersFilePath) {
    New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath -TemplateParameterFile $parametersFilePath;
} else {
    New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath;
}
```

# デプロイ

template.json, parameter.json, deploy.ps1 を同じフォルダーに配置し、そのフォルダーに移動して以下のコマンドを実行するとデプロイが開始されます。（Resource Group は先に作っておきます）subscriptionId はポータルから見つけてくるか、`Get-AzureRmSubscription` コマンドで確認できます。deploymentName は任意です。

```PowerShell
.\deploy.ps1 -subscriptionId <subscriptionID> -resourceGroupName <resourceGroupName> -deploymentName <deploymentName>
```

あとはデプロイが完了するのを待つだけ。

# まとめ
コード化しておくと毎度同じような設定を GUI からポチポチする必要がなくなるので手間が省けます。