#Azure ARM lessons learned and more


Azure Resource Manager (ARM) is the new model (opposed to Service Management) to create artifacts on Microsoft Azure. The two names are clearly addressing the different approach they’re taking: Service Management was basically an imperative model service oriented, while Resource Manager is, as the name implies resource oriented and it’s inherently a declarative model.

All our Azure deployment are today using the old model, so I took the chance to try ARM trying to deploy some real world workloads. After this extensive experience I must say ARM is not ready yet for prime time. This statement needs some justifications, here are a few reasons that make me thinking ARM is not ready:

  • Complete lack of documentation. The documentation is “by examples”, but that’s not enough and many examples are simply broken
  • Lack of support across other Azure Services. For example, you cannot backup ARM based Virtual Machines with Azure Backup
  • If using ARM templates, I consider this a must given the declarative model of ARM, you’re going to hit limitations and inconsistencies. For example while you can instruct ARM to iterate part of you templates (i.e. I want to create 10 VMs like this) you cannot for others (i.e. Firewall or NAT rules when you want to apply some math to the port).
    On the other hand the ARM model is much more convincing and potentially productive than the old one. After all for Service Management we ended up defining our own declarative model and feeding the manifest with the characteristics of the artifacts to a powershell script. Much more there are activities that can only be performed in the ARM Portal (try to create a Premium Storage in the old portal if you can). Microsoft knows that this experience must be fixed, the sooner the better, it’s a matter of credibility. Now that customers are really moving to the public cloud, the providers must be up to the promises they made.

The following are my notes and snippets. My deployment model was to have an infrastructure Resource Group with all the common and shared resources (virtual networks, storage accounts, automation account, …) and specific Resource Groups for application/solution roles.

Lessons learned

  • DependsOn doesn’t work for external resources. Spent a ton of time on it using the ResourceId function. DependsOn can only refer to resources in the same ARM template, you must take care required external resources are created before running your template. DependsOn, regardless of the samples you can find, always requires a complete resource id (i.e. the one you can create using resourceId() function)
  • If you change a definition in a template and redeploy the template existing objects are not modified. For example, if you modify a VM to change a vNIC association the old vNIC remains associated. I hope this will be fixed.
  • While I understand the usefulness of the model I don’t like the way it’s implemented. What I’d need is a truly template based model. For example, it would be useful to define a generic vNIC, then define instances of this model for every VM. This cannot be done every single vNIC needs to have a unique name per resource group, so vm dependent resources will end in a naming like vmname-resourcename-. And the ARM template is a copy and paste practice where if you get a single property wrong you need to modify it for every VM instance that use it. As a workaround you can use copy and CopyIndex(), but it’s not the same concept.
  • The networking model is completely different from the old one. Old endpoints are now substituted with LoadBalancers with Nat rules (without a load balancing policy if you don’t need it). Or you canh define NAT rules at the Subnet or VM level. This implies that you cannot have basic VM instances sharing the same public IP address, because basic VMs cannot have a load balancer associated. You can obviously have a 1:1 association between basic VMs and LB definition OR you can have a 1:1 with a public IP Address.
  • Remember, if you don’t define a network access rule everything is open. This cannot happen if you create your mapping from the portal cause it creates by default a NAC rule, but it can happen if you create your VMs from powershell or from templates. Seen a few on customer sites. Very dangerous.
  • Rule of thumb: everything is case sensitive.
  • You cannot use DSC extension or Script extension both from an ARM template and from powershell. You cannot have multiple DSC or Script extensions per VM, so basically you cannot be modular. This is why I opted not to use DSC extensions from templates, nor scripts, cause I want to keep my doors open in case I need to push a script to a VM once it is created. Even removing and try to set again the extension returns: BadRequest: Multiple extensions per handler not supported for OS type ‘Windows’. Extension ‘Microsoft.Powershell.DSC’ with handler ‘Microsoft.Powershell.DSC’
  • When you delete a VM from the Portal you must take care of deleting the associated disks, they’re not removed anymore.
  • I tried to move a vhd from from standard to premium storage. A complete failure, the source disk must be exactly one of the supported sizes 127, 511, …
  • There’s no builtin way to enable WinRM over https with a self signed certificate. See the deploying with Powershell for a workaround. There a sample with Azure Key Vault I’ve not been able to make it work (http://azure.microsoft.com/en-us/documentation/templates/201-winrm-windows-vm/) and after all you need to set some secret password in clear (base64 encoded) into the ARM template or pass it as a parameter, this breaks (imo) the benefit of using the key vault. A temporary sas key to access the vault would have been the answer.

ARM Template snippets and tips

You can find the references to ARM tamplate samples and documentation (basically json schemas) in the references section. So this doesn’t want to be a guide to ARM templates, but notes, snippets and tips I’m learning.

Create a storage account

See allowedValues array to specify a set of possible values for a parameter all the other constructs are straightforward.

"parameters": {
"newStorageAccountName": {
"type": "string",
"metadata": {
"Description": "Unique DNS name for the storage account."
}
},
"location": {
"type": "string",
"defaultValue": "North Europe",
"allowedValues": [
"West Europe",
"North Europe"
],
"metadata": {
"Description": "Azure DataCenter."
}
}
…
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('newStorageAccountName')]",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties": {
"accountType": "Standard_LRS"
}
},
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[concat(parameters('newStorageAccountName'),'ssd')]",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties": {
"accountType": "Premium_LRS"
}

Create Virtual Network with Subnets and default Network security Group

Things to remember:
– Priority in Network access rules must be unique, remember Azure default rules are >= 65000
– The default rule opens RDP and WinRM over https for every VM associated to the subnet with a public IP

"variables": {
"addressPrefix": "10.0.0.0/16",
"subnetName": "Subnet",
"subnetPrefix": "10.0.0.0/24",
"virtualNetworkName": "QNDDemo",
"vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Network/virtualNetworks",
"name": "[parameters('virtualNetworkName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "VMSubnet",
"properties": {
"addressPrefix": "[variables('subnetPrefix')]",
"networkSecurityGroup": "[resourceId('Microsoft.Network/networkSecurityGroups','DefaultNSG')]"
}
}
]
},
"dependsOn": [ "[resourceId('Microsoft.Network/networkSecurityGroups','DefaultNSG')]"]
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Network/networkSecurityGroups",
"name": "DefaultNSG",
"location": "[parameters('location')]",
"properties": {
"securityRules": [
{
"name": "rdp_rule",
"properties": {
"description": "Allow RDP",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "3389",
"sourceAddressPrefix": "Internet",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 100,
"direction": "Inbound"
}
},
{
"name": "WinRM_rule",
"properties": {
"description": "Allow WinRM",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "5986",
"sourceAddressPrefix": "Internet",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 102,
"direction": "Inbound"
}
},
]

Create a set of VMs

Using resources in a different resource group, behind the same load balancer with a single public IP address. Things to remember:

  • Since the VMs are associated with a load balancer the network access rules defined on the subnet are irrelevant, what matters is the NAT rule of the load balancer
  • The load balancers have a default network access rule so that they can access the VMs
  • If you want to have a NAT rule with access rules (for example I publish RDP but it should only be reachable from a predefined set of public IPs) you cannot do it at the NAT or load balancer level, instead you need o define a custom security Rule on the subnet or VMs
  • Using deployment().name doesn’t work, it would have been useful for automatic naming conventions
  • The copy property cannot be used with Load Balancers NAT rules. Indeed this is a very specific scenario where I didn’t want to have a public IP associated to every single machine, so the Load Balancer public IP is also used for management purposes.
  • The VMs will be created with just one vNIC
  • More notes and comments inline
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"infraResourceGroup": {
"type": "string",
"defaultValue": "[resourceGroup().name]",
"metadata": {
"Description": "This is the resource group where shared virtual networks and storage accounts are present ."
}
},

"storageAccountName": {
"type": "string",
"defaultValue": "",
"metadata": {
"Description": "Unique DNS name for the storage account where the virtual machine's disks will be placed."
}
},

"virtualNetworkName": {
"type": "string",
"metadata": {
"Description": ""
}
},

"subnetName": {
"type": "string",
"metadata": {
"Description": ""
}
},

"vmName": {
"type": "string",
"metadata": {
"Description": "Virtual Machine Name Prefix"
}
},

"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"allowedValues": [
"West Europe",
"North Europe"
],
"metadata": {
"Description": "Azure DataCenter."
}
},

"adminUsername": {
"type": "string",
"defaultValue": "MyLocalAdmin",
"metadata": {
"Description": "User name for the virtual machine."
}
},

"adminPassword": {
"type": "securestring",
"metadata": {
"Description": "Password for the virtual machine local administrator account."
}
},

"dnsNameForPublicIP": {
"type": "string",
"metadata": {
"Description": "Unique DNS name for the public IP used to access the virtual machine."
}
},

"windowsOSVersion": {
"type": "string",
"defaultValue": "2012-R2-Datacenter",
"allowedValues": [
"2008-R2-SP1",
"2012-Datacenter",
"2012-R2-Datacenter",
"Windows-Server-Technical-Preview"
],
"metadata": {
"Description": "The Windows version for the virtual machine. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter, Windows-Server-Technical-Preview."
}
},

"vmCount": {
"type": "int",
"defaultValue": 1,
"metadata": { "Description": "Number of VMs to create" }
},
"rdpPort": {
"type": "int",
"defaultValue": 55500,
"metadata": { "Description": "Starting point for rdp NAT rule" }
},
"vmSize": {
"type": "string",
"defaultValue": "Standard_A1",
"metadata": { }
},
"availabilitySetName": {
"type": "string",
"defaultValue": "QNDAS"
}
},
"variables": {
"imagePublisher": "MicrosoftWindowsServer",
"imageOffer": "WindowsServer",
"OSDiskName": "osdisk",
"nicName": "vNIC1",
//"publicIPAddressName": "[concat('QNDDemoPublicIP',(deployment().Name))]", Not supported sigh
"publicIPAddressName": "QNDDemoPublicIP3",
"publicIPAddressType": "Dynamic",
"vmStorageAccountContainerName": "vhds",
"vnetID": "[resourceId(parameters('infraResourceGroup'),'Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]", //vnet must be present in the infras resource group
"subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('subnetName'))]",
//"publicLBName": "[concat('publicLB-',(deployment().Name))]", doesn't work
"publicLBName": "publicLB-3",
"accountid": "[resourceId(parameters('infraResourceGroup'),'Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"

},
"resources": [
{
"type": "Microsoft.Compute/availabilitySets",
"name": "[parameters('availabilitySetName')]",
"apiVersion": "2015-05-01-preview",
"location": "[parameters('location')]",
"properties": {
"platformUpdateDomainCount": 3, //can be 2..3
"platformFaultDomainCount": 3 //can be 2..3

}
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[variables('publicIPAddressName')]",
"location": "[parameters('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
}
}
},
{
"apiVersion": "2015-05-01-preview",
"name": "[variables('publicLBName')]",
"type": "Microsoft.Network/loadBalancers",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/',variables('publicIPAddressName'))]"
],
"properties": {
"frontendIPConfigurations": [
{
"name": "LBFE",
"properties": {
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
}
}
}
],
"backendAddressPools": [
{
"name": "LBBAP"
}
],
"inboundNatRules": [
/* For NAT rules we pre-create 10 rules, 10 is the maximum number of VMs we can create with this template, haven't found a way to use copy and CopyIndex() here */
{
"name": "[concat(parameters('vmName'),'0-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[parameters('rdpPort')]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'1-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),1)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'2-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),2)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'3-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),3)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'4-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),4)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'5-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),5)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'6-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),6)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'7-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),7)]",
"backendPort": 3389,
"enableFloatingIP": false
}

},
{
"name": "[concat(parameters('vmName'),'8-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),8)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'9-','rdp')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "[add(parameters('rdpPort'),9)]",
"backendPort": 3389,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'0-','winRM')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40000",
"backendPort": 5985,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'1-','winRM')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40001",
"backendPort": 5985,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'2-','winRM')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40002",
"backendPort": 5985,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'0-','winRMSSL')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40100",
"backendPort": 5986,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'1-','winRMSSL')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40101",
"backendPort": 5986,
"enableFloatingIP": false
}
},
{
"name": "[concat(parameters('vmName'),'2-','winRMSSL')]",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"protocol": "tcp",
"frontendPort": "40102",
"backendPort": 5986,
"enableFloatingIP": false
}
}
],
/* sample load balancing rule for http */
"loadBalancingRules": [
{
"name": "LBRule",
"properties": {
"frontendIPConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/frontendIPConfigurations/LBFE')]"
},
"backendAddressPool": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/backendAddressPools/LBBAP')]"
},
"protocol": "tcp",
"frontendPort": 80,
"backendPort": 80,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 10,
"probe": {
"id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')),'/probes/httpProbe')]"
}
}
}
],
"probes": [
{
"name": "httpProbe",
"properties": {
"protocol": "tcp",
"port": 80,
"intervalInSeconds": "5",
"numberOfProbes": "2"
}
}
]
}
},

{
"apiVersion": "2015-06-15",
"type": "Microsoft.Network/networkInterfaces",
"name": "[concat(parameters('vmName'),copyIndex(),'-',variables('nicName'))]",
"location": "[parameters('location')]",
"copy": {
"name": "vmcopy",
"count": "[parameters('vmCount')]"
},
"dependsOn": [
"[resourceId('Microsoft.Network/loadBalancers',variables('publicLBName'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "[concat('ipconfig', variables('nicName'),'-',copyIndex())]",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[variables('subnetRef')]"
},
"loadBalancerBackendAddressPools": [
{
"id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/backendAddressPools/LBBAP')]"
}
],
"loadBalancerInboundNatRules": [
{ "id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/inboundNatRules/', parameters('vmName'),copyIndex(),'-','rdp')]" },
{ "id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/inboundNatRules/', parameters('vmName'),copyIndex(),'-','winRM')]" },
{ "id": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('publicLBName')),'/inboundNatRules/', parameters('vmName'),copyIndex(),'-','winRMSSL')]" }
]
}
}
]
}
},
{
"apiVersion": "2015-06-15",
"type": "Microsoft.Compute/virtualMachines",
"name": "[concat(parameters('vmName'),copyIndex())]",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', parameters('vmName'),copyIndex(),'-',variables('nicName'))]",
"[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]"
],
"copy": {
"name": "vmcopy",
"count": "[parameters('vmCount')]"
},
"properties": {
"availabilitySet": {
"id": "[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]"
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[concat(parameters('vmName'),copyIndex())]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"WindowsConfiguration": {
"provisionVMAgent": "true",
"enableAutomaticUpdates": "true" /*,
"WinRM": {
"listeners": [
{ "protocol": "http" },
{ "protocol": "https" }
]

}*/

}
},
"storageProfile": {
"imageReference": {
"publisher": "[variables('imagePublisher')]",
"offer": "[variables('imageOffer')]",
"sku": "[parameters('windowsOSVersion')]",
"version": "latest"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat('http://',parameters('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',parameters('vmName'),copyIndex(),variables('OSDiskName'),'.vhd')]"
},
"caching": "ReadOnly",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('vmName'),copyIndex(),'-',variables('nicName')))]"
}
]
}
}
}

]
}

Adding extensions

In the end I opted not to use Script and DSC extensions, basically they’re an all or nothing choice, and I prefer to the nothing approach and then add what’s missing in my deployment script. On the other hand one extension you may want to add by default is the diagnostic extension. A copuple of trick:
– To make it compatible with the Azure Portal it must be named “Microsoft.Insights.VMDiagnosticsSettings”
– To set the configuration payload the easy way is to manually configure a reference VM and then export the configuration, base64 encode it and set it into your template. See Deploying ARM templates for more info.
– Right now the BG Info Extension is not working for ARM provisioned VMs
Let’s take the previous VM resource and add the diagnostic extension:

{
"apiVersion": "2015-06-15",
"type": "Microsoft.Compute/virtualMachines",
"name": "[concat(parameters('vmName'),copyIndex())]",
"location": "[parameters('location')]",
"dependsOn": ["[concat('Microsoft.Network/networkInterfaces/', parameters('vmName'),copyIndex(),'-',variables('nicName'))]",
"Microsoft.Compute/availabilitySets/TagetikDemo2"],
"copy": {
"name": "vmcopy",
"count": "[parameters('vmCount')]"
},
"properties": {
"availabilitySet": {
"id": "[resourceId('Microsoft.Compute/availabilitySets', parameters('availabilitySetName'))]"
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"osProfile": {
"computerName": "[concat(parameters('vmName'),copyIndex())]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"WindowsConfiguration": {
"provisionVMAgent": "true",
"enableAutomaticUpdates": "true"/*,
"WinRM": {
"listeners": [{
"protocol": "http"
},
{
"protocol": "https"
}]
}*/
}
},
"storageProfile": {
"imageReference": {
"publisher": "[variables('imagePublisher')]",
"offer": "[variables('imageOffer')]",
"sku": "[parameters('windowsOSVersion')]",
"version": "latest"
},
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat('http://',parameters('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',parameters('vmName'),copyIndex(),variables('OSDiskName'),'.vhd')]"
},
"caching": "ReadOnly",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [{
"id": "[resourceId('Microsoft.Network/networkInterfaces',concat(parameters('vmName'),copyIndex(),'-',variables('nicName')))]"
}]
}
},
"resources": [{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "Microsoft.Insights.VMDiagnosticsSettings"
"apiVersion": "2015-06-15",
"location": "[parameters('location')]",
"dependsOn": ["[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'),copyIndex())]"],
"properties": {
"publisher": "Microsoft.Azure.Diagnostics",
"type": "IaaSDiagnostics",
"typeHandlerVersion": "1.2",
"settings": {
"xmlCfg": "",
"StorageAccount": "[variables('diagnosticsStg')]"
},
"protectedSettings": {
"storageAccountName": "[variables('diagnosticsStg')]",
"storageAccountKey": "[listKeys(variables('accountid'),'2015-05-01-preview').key1]",
"storageAccountEndPoint": "https://core.windows.net/"
}
}
}
]

For completeness sake the following is a snippet to add Script and DSC extensions. A few interesting highlights:
– Remember just one extension per VM, so one script and one DSC maximum per VM. After that you cannot push other scripts/DSC even from powershell
– Script uses a standard storage account / key security mechanisms
– DSC uses the more secure and modern sas Token way (see Deploying ARM templates again for more info)

…
"parameters": {
"infraResourceGroup": {
"type": "string",
"defaultValue": "[resourceGroup().name]",
"metadata": {
"Description": "Unique DNS name for the storage account where the virtual machine's disks will be placed."
}
},
…

"dscSasToken": {
"type": "string"
}
},
"variables": {
…
"assetsStorageAccountGroup": "Default-Storage-WestEurope",
"assetsStorageAccountName": "prginfralabre",
"assetsStorageAccountId": "[resourceId('Default-Storage-WestEurope','Microsoft.Storage/storageAccounts', variables('assetsStorageAccountName'))]",
"setupScriptUri": "https://URL.blob.core.windows.net/protected/ConfigureLocalWinRM.ps1",
"setupScript": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File ', 'ConfigureLocalWinRM.ps1')]",
"dscModuleURI": "https://URLblob.core.windows.net/protected/FirstConfig.ps1.zip",
"configFunction": "FirstConfig.ps1FirstConfig"

},

…
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmName'),copyIndex(),'/vmInitialScript')]",
"apiVersion": "2015-06-15",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'),copyIndex())]"
],
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.4",
"autoUpgradeMinorVersion": "true",
"settings": {
"fileUris": [
"[variables('setupScriptUri')]"
],
"commandToExecute": "[variables('setupScript')]"
},
"protectedSettings": {
"storageAccountName": "[variables('assetsStorageAccountName')]",
"storageAccountKey": "[listKeys(variables('assetsStorageAccountId'),'2015-05-01-preview').key1]"
}
}
},
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmName'),copyIndex(),'/vmExtensionDSC')]",
"apiVersion": "2015-06-15",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'),copyIndex())]"
],
"properties": {
"publisher": "Microsoft.Powershell",
"type": "DSC",
"typeHandlerVersion": "2.4",
"autoUpgradeMinorVersion": "true",
"settings": {
"ModulesUrl": "[variables('dscModuleURI')]",
"ConfigurationFunction": "[variables('configFunction')]",
"sasToken": "[parameters('dscSasToken')]",
// Properties is an array of DSC configuration parameters
"Properties": [ ],
"Protocolversion": null
},
"protectedSettings": {
"DataBlobUri": null,
"Items": { }
}

},
"tags": {}
}

References

–Daniele
This posting is provided “AS IS” with no warranties, and confers no rights.

  1. #1 by Brian Lewis on April 6, 2016 - 8:59 pm

    Great write-up!
    As follow-up on your notes. ARM VMs can be backed up with ARM based Azure backup. This released as Preview / beta on March 31st 2016.
    Also, for deleting VMs not deleting disks from the portal. The easy way to delete all your stuff for a VM is the use the “Resource Group”. If you delete the group it will delete all associated VMs, disks, Storage accounts, Networks, IP addresses, etc. Just be sure not to have things you don’t want deleted in the group.

  2. #2 by Chris Bailiss on December 23, 2015 - 11:38 pm

    Great post, thank you. Agree 100% on the first reason why this is (still) not ready for full production use. Documentation still so patchy. Much of the ARM PowerShell documentation is (still) nothing more than templates barely filled in.

  3. #3 by Jonathan Almquist on September 23, 2015 - 9:20 pm

    Wow. Excellent write-up, Daniele!

  1. Notes for #Azure #ARM: how to perform conditional deployments | Quae Nocent Docent
  2. Deploying #Azure ARM templates with #Powershell | Quae Nocent Docent
  3. #Azure: ARM templates, Automation, DSC and more. Travel notes | Quae Nocent Docent

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.