Using ARM to Deploy Global Solutions

Imagine deploying your secure load balanced solution to three datacenters, putting in place a worldwide load balancer and doing so in roughly 24 minutes. Did I mention that this deployment is predictable and repeatable?

Good, now that I’ve got your attention, it’s time to dive in!

Building on my previous post about managing compute resources on Azure I decided to modify the Azure Resource Manager(ARM) template to deploy a real-world environment to three datacenters (Yes I know, the diagrams shows two locations, but as I built the demo, I got greedy…). Using Azure Traffic Manager we are able positively affect a users experience by directing them to the closest datacenter.

Its important to note that ARM does not support nested copy operation. This means that we have to use a different strategy to deploy identical environments in multiple Azure regions. After a bit of research it became apparent that I had to use nested deployments. This technique requires us to break our template into multiple files. The parent template in this demo is the azuredeploy-multi-geo.json file. It contains the full list of parameters, a nested deployment that deploys instances of our environment to multiple Azure regions, and a Traffic Manager definition. The azuredeploy.json template file was refactored from the template used in my previous blog post. It contains networking, storage and Virtual Machine definitions.

Deploying the ARM Template

The following template is deployed using the Incremental mode so that if something goes wrong, you can continue from where you left off. This can definitely save you a lot of time while you develop the ARM Templates that describe your solution.

Below the New-AzureRmResourceGroupDeployment PowerShell command, I included the full verbose trace that was produced by my deployment. What I really want everyone to grasp from the trace, is that ARM works hard to do as much as it can in parallel. The Azure Service Management Cmdlets processed our deployments sequentially and resulted in long deployment times. In turn our agility suffered and our deployment resources were quite busy. Azure Resource Manager just makes me more agile.

Azure PowerShell V1.0

New-AzureRmResourceGroup -Name 'geoexperiment' -Location 'westus'

# Deploy the Template to the Resource Group
New-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                   -TemplateFile 'azuredeploy-multi-geo.json' `
                                   -TemplateParameterFile 'azuredeploy.parameters.json' `
                                   -Verbose


ResourceGroupName : geoexperiment
Location          : westus
ProvisioningState : Succeeded
Tags              : 
ResourceId        : /subscriptions/64668628-d5e6-9be4-9e577d5b8fd0/resourceGroups/geoexperiment

VERBOSE: 5:31:03 PM - Create template deployment 'azuredeploy-multi-geo'.
VERBOSE: 5:31:09 PM - Resource Microsoft.Resources/deployments 'tmgd2' provisioning status is running
VERBOSE: 5:31:09 PM - Resource Microsoft.Resources/deployments 'tmgd0' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Resources/deployments 'tmgd1' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv1' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Storage/storageAccounts 'tmgd2tmgdv' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd0tmgd0ngs' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv0' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv1' provisioning status is running
VERBOSE: 5:31:17 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Compute/availabilitySets 'tmgd1as' provisioning status is succeeded
VERBOSE: 5:31:21 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Storage/storageAccounts 'tmgd1tmgdv' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv0' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd2tmgd2ngs' provisioning status is running
VERBOSE: 5:31:21 PM - Resource Microsoft.Compute/availabilitySets 'tmgd2as' provisioning status is succeeded
VERBOSE: 5:31:21 PM - Resource Microsoft.Compute/availabilitySets 'tmgd0as' provisioning status is succeeded
VERBOSE: 5:31:21 PM - Resource Microsoft.Storage/storageAccounts 'tmgd0tmgdv' provisioning status is running
VERBOSE: 5:31:25 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv1' provisioning status is running
VERBOSE: 5:31:25 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd1tmgd1ngs' provisioning status is running
VERBOSE: 5:31:25 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv0' provisioning status is running
VERBOSE: 5:31:29 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv1' provisioning status is succeeded
VERBOSE: 5:31:29 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd2tmgdv0' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd2tmgd2ngs' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/loadBalancers 'tmgd0tmgdv' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd0tmgd0ngs' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv0' provisioning status is succeeded
VERBOSE: 5:31:33 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd0tmgdv1' provisioning status is succeeded
VERBOSE: 5:31:37 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv1' provisioning status is succeeded
VERBOSE: 5:31:37 PM - Resource Microsoft.Network/networkSecurityGroups 'tmgd1tmgd1ngs' provisioning status is succeeded
VERBOSE: 5:31:37 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv0' provisioning status is succeeded
VERBOSE: 5:31:38 PM - Resource Microsoft.Network/publicIPAddresses 'tmgd1tmgdv' provisioning status is succeeded
VERBOSE: 5:31:38 PM - Resource Microsoft.Network/loadBalancers 'tmgd2tmgdv' provisioning status is succeeded
VERBOSE: 5:31:38 PM - Resource Microsoft.Network/virtualNetworks 'tmgd2vnet' provisioning status is running
VERBOSE: 5:31:38 PM - Resource Microsoft.Network/virtualNetworks 'tmgd0vnet' provisioning status is running
VERBOSE: 5:31:41 PM - Resource Microsoft.Network/loadBalancers 'tmgd1tmgdv' provisioning status is succeeded
VERBOSE: 5:31:46 PM - Resource Microsoft.Network/virtualNetworks 'tmgd1vnet' provisioning status is running
VERBOSE: 5:31:49 PM - Resource Microsoft.Network/virtualNetworks 'tmgd2vnet' provisioning status is succeeded
VERBOSE: 5:31:54 PM - Resource Microsoft.Network/networkInterfaces 'tmgd2tmgdvNetworkInterface0' provisioning status is succeeded
VERBOSE: 5:31:54 PM - Resource Microsoft.Network/networkInterfaces 'tmgd2tmgdvNetworkInterface1' provisioning status is succeeded
VERBOSE: 5:31:54 PM - Resource Microsoft.Network/virtualNetworks 'tmgd0vnet' provisioning status is succeeded
VERBOSE: 5:31:58 PM - Resource Microsoft.Network/virtualNetworks 'tmgd1vnet' provisioning status is succeeded
VERBOSE: 5:31:58 PM - Resource Microsoft.Network/networkInterfaces 'tmgd0tmgdvNetworkInterface1' provisioning status is succeeded
VERBOSE: 5:32:02 PM - Resource Microsoft.Network/networkInterfaces 'tmgd1tmgdvNetworkInterface0' provisioning status is succeeded
VERBOSE: 5:32:05 PM - Resource Microsoft.Network/networkInterfaces 'tmgd1tmgdvNetworkInterface1' provisioning status is succeeded
VERBOSE: 5:32:17 PM - Resource Microsoft.Network/networkInterfaces 'tmgd0tmgdvNetworkInterface0' provisioning status is succeeded
VERBOSE: 5:51:59 PM - Resource Microsoft.Storage/storageAccounts 'tmgd2tmgdv' provisioning status is succeeded
VERBOSE: 5:51:59 PM - Resource Microsoft.Storage/storageAccounts 'tmgd0tmgdv' provisioning status is succeeded
VERBOSE: 5:52:03 PM - Resource Microsoft.Compute/virtualMachines 'tmgd2tmgdv0' provisioning status is running
VERBOSE: 5:52:03 PM - Resource Microsoft.Compute/virtualMachines 'tmgd0tmgdv0' provisioning status is running
VERBOSE: 5:52:07 PM - Resource Microsoft.Compute/virtualMachines 'tmgd0tmgdv1' provisioning status is running
VERBOSE: 5:52:11 PM - Resource Microsoft.Storage/storageAccounts 'tmgd1tmgdv' provisioning status is succeeded
VERBOSE: 5:52:11 PM - Resource Microsoft.Compute/virtualMachines 'tmgd2tmgdv1' provisioning status is running
VERBOSE: 5:52:15 PM - Resource Microsoft.Compute/virtualMachines 'tmgd1tmgdv1' provisioning status is running
VERBOSE: 5:52:15 PM - Resource Microsoft.Compute/virtualMachines 'tmgd1tmgdv0' provisioning status is running
VERBOSE: 5:53:42 PM - Resource Microsoft.Compute/virtualMachines 'tmgd0tmgdv1' provisioning status is succeeded
VERBOSE: 5:53:46 PM - Resource Microsoft.Compute/virtualMachines 'tmgd0tmgdv0' provisioning status is succeeded
VERBOSE: 5:54:01 PM - Resource Microsoft.Resources/deployments 'tmgd0' provisioning status is succeeded
VERBOSE: 5:54:41 PM - Resource Microsoft.Compute/virtualMachines 'tmgd1tmgdv0' provisioning status is succeeded
VERBOSE: 5:54:45 PM - Resource Microsoft.Compute/virtualMachines 'tmgd2tmgdv1' provisioning status is succeeded
VERBOSE: 5:54:49 PM - Resource Microsoft.Compute/virtualMachines 'tmgd1tmgdv1' provisioning status is succeeded
VERBOSE: 5:54:53 PM - Resource Microsoft.Compute/virtualMachines 'tmgd2tmgdv0' provisioning status is succeeded
VERBOSE: 5:55:01 PM - Resource Microsoft.Resources/deployments 'tmgd1' provisioning status is succeeded
VERBOSE: 5:55:01 PM - Resource Microsoft.Resources/deployments 'tmgd2' provisioning status is succeeded
VERBOSE: 5:55:05 PM - Resource Microsoft.Network/trafficManagerProfiles 'tmgdtm' provisioning status is succeeded

DeploymentName     : azuredeploy-multi-geo
CorrelationId      : 9fc7f950-845d-4e3f-a551-54250c89cce3
ResourceGroupName  : geoexperiment
ProvisioningState  : Succeeded
Timestamp          : 12/1/2015 10:55:12 PM
Mode               : Incremental
TemplateLink       : 
TemplateLinkString : 
Parameters         : {[locations, Microsoft.Azure.Commands.Resources.Models.DeploymentVariable], [userName, 
                     Microsoft.Azure.Commands.Resources.Models.DeploymentVariable], [adminPwd, 
                     Microsoft.Azure.Commands.Resources.Models.DeploymentVariable], [storageType, 
                     Microsoft.Azure.Commands.Resources.Models.DeploymentVariable]...}
ParametersString   : 
                     Name             Type                       Value     
                     ===============  =========================  ==========
                     locations        Array                      [
                       "eastus",
                       "centralus",
                       "westus"
                     ]
                     userName         String                     brisebois 
                     adminPwd         SecureString                         
                     storageType      String                     Standard_LRS
                     vmNamePrefix     String                     tmgdv     
                     environment      String                     tmgd      
                     osVersion        String                     OL70      
                     imagePublisher   String                     oracle    
                     imageOffer       String                     Oracle-Linux-7
                     tmDnsName        String                     geodemo   
                     
Outputs            : {}
OutputsString      : 

Template Parameter File

The template parameter file contains the parameters that where used in my experiment. It contains both parameters for the parent and child template deployments. And the interesting fact about this specific file, is that I used an array to describe the Azure regions where I wanted to deploy my solution.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "userName": { "value": "brisebois" },
    "adminPwd": { "value": "PaS$w0rD!" },
    "OSVersion": { "value": "OL70" },
    "imagePublisher": { "value": "oracle" },
    "imageOffer": { "value": "Oracle-Linux-7" },
    "environment": { "value": "tmgd" },
    "vmNamePrefix": { "value": "tmgdv" },
    "storageType": { "value": "Standard_LRS" },
    "tmDnsName": { "value": "geodemo" },
    "locations": {
      "value": [
        "eastus",
        "centralus",
        "westus"
      ]
    }
  }
}

Debugging ARM Deployments

As I tested various approaches and versions of my templates I came a cross a few exceptions. One of which came up from time to time, but didn’t stop me from deploying my template.
Troubleshooting resource group deployments in Azure is a great post that helped me throughout this experiment.

Azure PowerShell V1.0

If our template or parameter file is not in the correct format, the deployment will fail. Prior to executing a deployment, we can test the validity of the template and parameters files.

Test-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                    -TemplateFile 'azuredeploy-multi-geo.json' `
                                    -TemplateParameterFile 'azuredeploy.parameters.json'

Once a deployment fails, the first thing we need to do, is to list the Resource Group deployments.

Get-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment'

Then we need to inspect deployment operations for their status.

(Get-AzureRmResourceGroupDeploymentOperation -ResourceGroupName 'geoexperiment' `
                                             -DeploymentName 'azuredeploy-multi-geo').Properties


ProvisioningState : Running
Timestamp         : 2015-12-02T21:09:28.1229862Z
Duration          : PT14M11.0689068S
TrackingId        : 1c2b6fa2-a238-4cbd-8a18-d9a834b123d5
StatusCode        : Created
TargetResource    : @{Id=/subscriptions/64668628-d5e6-4c5b-9e577d5b8fd0/resourceGroups/geoexperiment/providers/Microsoft.Resources/deployments
                    /tmgd0; ResourceType=Microsoft.Resources/deployments; ResourceName=tmgd0}

ProvisioningState : Succeeded
Timestamp         : 2015-12-02T21:01:27.7296347Z
Duration          : PT6M10.6758962S
TrackingId        : b9d88dc3-c5b4-42b1-b99b-b59e43e84046
StatusCode        : OK
TargetResource    : @{Id=/subscriptions/64668628-d5e6-9e577d5b8fd0/resourceGroups/geoexperiment/providers/Microsoft.Resources/deployments
                    /tmgd2; ResourceType=Microsoft.Resources/deployments; ResourceName=tmgd2}

ProvisioningState : Running
Timestamp         : 2015-12-02T21:09:20.6638652Z
Duration          : PT14M3.5988443S
TrackingId        : fdcd8bdc-f32c-4266-bf30-c734778825e4
StatusCode        : Created
TargetResource    : @{Id=/subscriptions/64668628-d5e6-9e577d5b8fd0/resourceGroups/geoexperiment/providers/Microsoft.Resources/deployments
                    /tmgd1; ResourceType=Microsoft.Resources/deployments; ResourceName=tmgd1}

Pulling the Status message from the deployment operations can be done as follows:

(Get-AzureRmResourceGroupDeploymentOperation -ResourceGroupName 'geoexperiment' `
                                             -DeploymentName 'azuredeploy-multi-geo').Properties.StatusMessage

Code       : Conflict
Message    : Virtual Machine with given name tmgd0tmgdv1 already exists.
Target     :
Details    : {@{Message=Virtual Machine with given name tmgd0tmgdv1 already exists.}, @{Code=Conflict}, @{ErrorEntity=}}
Innererror :

Sometimes, stopping a deployment is required so that we can execute an updated version of our template. I used the following to stop my deployments.

Stop-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                    -Name 'tmgd0'
Stop-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                    -Name 'tmgd1'
Stop-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                    -Name 'tmgd2'

Stop-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                    -Name 'azuredeploy-multi-geo'

It’s also possible to remove a deployment. Keep in mind that this does not delete the Azure resources deployed by a deployment.

Remove-AzureRmResourceGroupDeployment -ResourceGroupName 'geoexperiment' `
                                      -Name 'azuredeploy-multi-geo'    

Building the Azure Resource Manager Templates

Using a nested template and a copy operation we are able to deploy our environments to multiple Azure regions in parallel. We then take a dependency on each of the nested deployment instances to make sure that our Traffic Manager is created once the load balanced Public IPs are created in each region.

azuredeploy-multi-geo.json

Reviewing the following top level template, you will notice that the nested deployment contains a copy operation. The “count” is set based on the number of elements present in the locations parameter array. The second thing that we notice is that we increment the “environment” nested deployment parameter. This is done so that each deployment has a unique value. We also increment the deployment “name” property to uniquely identify child deployments. Finally, in order to enumerate the “location” array we use the copyIndex.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "locations": {
      "type": "array",
      "metadata": {
        "description": "Locations"
      }
    },
    "userName": {
      "type": "string"
    },
    "adminPwd": {
      "type": "securestring"
    },
    "storageType": {
      "type": "string",
      "allowedValues": [
        "Standard_LRS",
        "Standard_ZRS",
        "Standard_GRS",
        "Standard_RAGRS",
        "Premium_LRS"
      ]
    },
    "vmNamePrefix": {
      "type": "string"
    },
    "environment": {
      "type": "string"
    },
    "OSVersion": {
      "type": "string"
    },
    "imagePublisher": {
      "type": "string"
    },
    "imageOffer": {
      "type": "string"
    },
    "tmDnsName": {
      "type": "string"
    }
  },
  "variables": {
  },
  "resources": [
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2015-01-01",
      "name": "[concat(parameters('environment'),copyIndex())]",
      "copy": {
        "count": "[length(parameters('locations'))]",
        "name": "loop"
      },
      "properties": {
        "mode": "Incremental",
        "templateLink": {
          "uri": "https://raw.githubusercontent.com/brisebois/multi-geo-arm-experiment/master/OnAndOff/Templates/azuredeploy.json",
          "contentVersion": "1.0.0.0"
        },
        "parameters": {
          "userName": { "value": "[parameters('userName')]" },
          "adminPwd": { "value": "[parameters('adminPwd')]" },
          "location": { "value": "[parameters('locations')[copyIndex()]]" },
          "OSVersion": { "value": "OL70" },
          "imagePublisher": { "value": "oracle" },
          "imageOffer": { "value": "Oracle-Linux-7" },
          "vmNamePrefix": { "value": "[parameters('vmNamePrefix')]" },
          "environment": { "value": "[concat(parameters('environment'), copyIndex())]" },
          "storageType": { "value": "[parameters('storageType')]" }
        }
      }
    },
    {
      "apiVersion": "2015-04-28-preview",
      "type": "Microsoft.Network/trafficManagerProfiles",
      "name": "[concat(parameters('environment'),'tm')]",
      "location": "global",
      "dependsOn": [
        "[concat('Microsoft.Resources/deployments/', concat(parameters('environment'),0))]",
        "[concat('Microsoft.Resources/deployments/', concat(parameters('environment'),1))]",
        "[concat('Microsoft.Resources/deployments/', concat(parameters('environment'),2))]"
      ],
      "properties": {
        "profileStatus": "Enabled",
        "trafficRoutingMethod": "Weighted",
        "dnsConfig": {
          "relativeName": "[parameters('tmDnsName')]",
          "ttl": 30
        },
        "monitorConfig": {
          "protocol": "http",
          "port": 80,
          "path": "/"
        },
        "endpoints": [
          {
            "name": "eastus",
            "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints",
            "properties": {
              "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('environment'),'0', parameters('vmNamePrefix')))]",
              "endpointStatus": "Enabled",
              "weight": 1
            }
          },
          {
            "name": "centralus",
            "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints",
            "properties": {
              "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('environment'),'1', parameters('vmNamePrefix')))]",
              "endpointStatus": "Enabled",
              "weight": 1
            }
          },
          {
            "name": "westus",
            "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints",
            "properties": {
              "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('environment'),'2', parameters('vmNamePrefix')))]",
              "endpointStatus": "Enabled",
              "weight": 1
            }
          }
        ]
      }
    }

  ],
  "outputs": {
  }
}

azuredeploy.json

Refactoring this template to be deployed in multiple environments took some effort, and paid off because it works like a charm. The first thing that was done, was to append the environment name to most of the resource names and references. This helped uniquely identify reach resource within the Resource Group. The second change, was to use variables to construct values used throughout the template. Obviously, much can still be done to make this template better, for example some resource could have been nested under the Virtual Machine resource. This would reduce the maintenance complexity introduced by having to synchronize multiple copy operations.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageType": {
      "type": "string",
      "allowedValues": [
        "Standard_LRS",
        "Standard_ZRS",
        "Standard_GRS",
        "Standard_RAGRS",
        "Premium_LRS"
      ]
    },
    "vmNamePrefix": {
      "type": "string"
    },
    "userName": {
      "type": "string"
    },
    "location": {
      "type": "string",
      "minLength": 1
    },
    "adminPwd": {
      "type": "securestring"
    },
    "OSVersion": {
      "type": "string"
    },
    "imagePublisher": {
      "type": "string"
    },
    "imageOffer": {
      "type": "string"
    },
    "environment": {
      "type": "string"
    }
  },
  "variables": {
    "availabilitySetName": "[concat(parameters('environment'),'as')]",
    "vmCount": 2,
    "vnetName": "[concat(parameters('environment'),'vnet')]",
    "vnetPrefix": "10.0.0.0/16",
    "subnet1Name": "subnet",
    "subnet1Prefix": "10.0.0.0/24",
    "networkSecurityGroupsName": "[concat(parameters('environment'),'ngs')]",
    "vmNamePrefix": "[concat(parameters('environment'),parameters('vmNamePrefix'))]",
    "OSDiskName": "OSDisk",
    "vmSize": "Standard_D1",
    "vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]",
    "subnetRef": "[concat(variables('vnetID'), '/subnets/', variables('subnet1Name'))]",
    "storageAccountContainerName": "vhds",
    "nicName": "[concat(parameters('environment'), parameters('vmNamePrefix'), 'NetworkInterface')]",
    "publicIPAddress": "[concat(parameters('environment'), parameters('vmNamePrefix'))]",
    "lbPublicIPAddressName": "[concat(parameters('environment'), parameters('vmNamePrefix'))]",
    "lbPublicIPAddressType": "Dynamic",
    "lbName": "[concat(parameters('environment'),parameters('vmNamePrefix'))]",
    "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses', variables('lbPublicIPAddressName'))]",
    "lbID": "[resourceId('Microsoft.Network/loadBalancers',variables('lbName'))]",
    "frontEndIPConfigID": "[concat(variables('lbID'),'/frontendIPConfigurations/loadBalancerFrontend')]"
  },
  "resources": [
    {
      "name": "[variables('vmNamePrefix')]",
      "type": "Microsoft.Storage/storageAccounts",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [ ],
      "tags": {
        "displayName": "Storage Account"
      },
      "properties": {
        "accountType": "[parameters('storageType')]"
      }
    },
    {
      "name": "[variables('availabilitySetName')]",
      "type": "Microsoft.Compute/availabilitySets",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [ ],
      "tags": {
        "displayName": "Availability Set"
      }
    },
    {
      "apiVersion": "2015-05-01-preview",
      "type": "Microsoft.Network/networkSecurityGroups",
      "name": "[variables('networkSecurityGroupsName')]",
      "location": "[parameters('location')]",
      "tags": {
        "displayName": "Network Security Groups"
      },
      "properties": {
        "securityRules": [
          {
            "name": "ssh_rule",
            "properties": {
              "description": "Allow SSH",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "22",
              "sourceAddressPrefix": "Internet",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 101,
              "direction": "Inbound"
            }
          },
          {
            "name": "web_rule",
            "properties": {
              "description": "Allow WEB",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "Internet",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          }
        ]
      }
    },
    {
      "name": "[variables('vnetName')]",
      "type": "Microsoft.Network/virtualNetworks",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupsName'))]"
      ],

      "tags": {
        "displayName": "VNet"
      },
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('vnetPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnet1Name')]",
            "properties": {
              "addressPrefix": "[variables('subnet1Prefix')]",
              "networkSecurityGroup": {
                   "id": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('networkSecurityGroupsName'))]"
              }
            }           
          }
        ]
      }
    },
    {
      "name": "[concat(variables('publicIPAddress'), copyIndex())]",
      "type": "Microsoft.Network/publicIPAddresses",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [ ],
      "tags": {
        "displayName": "Public IP"
      },
      "copy": {
        "count": "[variables('vmCount')]",
        "name": "pipCounter"
      },
      "properties": {
        "publicIPAllocationMethod": "Dynamic",
        "dnsSettings": {
          "domainNameLabel": "[concat(parameters('vmNamePrefix'), copyIndex())]"
        }
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[variables('lbPublicIPAddressName')]",
      "location": "[parameters('location')]",
      "tags": {
        "displayName": "LB Public IP"
      },
      "properties": {
        "publicIPAllocationMethod": "[variables('lbPublicIPAddressType')]",
        "dnsSettings": {
          "domainNameLabel": "[variables('lbPublicIPAddressName')]"
        }
      }
    },
    {
      "name": "[concat(variables('nicName'), copyIndex())]",
      "type": "Microsoft.Network/networkInterfaces",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]",
        "[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddress'), copyIndex()))]",
        "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]"
      ],
      "tags": {
        "displayName": "NIC"
      },
      "copy": {
        "count": "[variables('vmCount')]",
        "name": "nicCounter"
      },
      "properties": {
        "ipConfigurations": [
          {
            "name": "[concat('ipconfig', copyIndex())]",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "subnet": {
                "id": "[variables('subnetRef')]"
              },
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('publicIPAddress'), copyIndex()))]"
              },
              "loadBalancerBackendAddressPools": [
                {
                  "id": "[concat(variables('lbID'), '/backendAddressPools/LoadBalancerBackend')]"
                }
              ]
            }
          }
        ]
      }
    },
    {
      "name": "[concat(variables('vmNamePrefix'), copyIndex())]",
      "type": "Microsoft.Compute/virtualMachines",
      "location": "[parameters('location')]",
      "apiVersion": "2015-05-01-preview",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('vmNamePrefix'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'), copyIndex())]",
        "[concat('Microsoft.Compute/availabilitySets/', variables('availabilitySetName'))]"
      ],
      "copy": {
        "count": "[variables('vmCount')]",
        "name": "vmCounter"
      },
      "tags": {
        "displayName": "Oracle VMs"
      },
      "properties": {
        "availabilitySet": {
          "id": "[resourceId('Microsoft.Compute/availabilitySets',variables('availabilitySetName'))]"
        },
        "hardwareProfile": {
          "vmSize": "[variables('vmSize')]"
        },
        "osProfile": {
          "computerName": "[concat(variables('vmNamePrefix'), copyIndex())]",
          "adminUsername": "[parameters('userName')]",
          "adminPassword": "[parameters('adminPwd')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[parameters('imagePublisher')]",
            "offer": "[parameters('imageOffer')]",
            "sku": "[parameters('OSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "OSDisk",
            "vhd": {
              "uri": "[concat('http://', variables('vmNamePrefix'), '.blob.core.windows.net/', variables('storageAccountContainerName'), '/', concat(variables('OSDiskName'), copyIndex()), '.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          },
          "dataDisks": [
            {
              "createOption": "Empty",
              "lun": 0,
              "diskSizeGB": "100",
              "name": "DATA",
              "vhd": { "uri": "[concat('http://', variables('vmNamePrefix'), '.blob.core.windows.net/', variables('storageAccountContainerName'), '/', concat('DataDisk', copyIndex()), '.vhd')]" }
            }
          ]
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('nicName'), copyIndex()))]"
            }
          ]
        }
      }
    },
    {
      "apiVersion": "2015-06-15",
      "name": "[variables('lbName')]",
      "type": "Microsoft.Network/loadBalancers",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('lbPublicIPAddressName'))]"
      ],
      "tags": {
        "displayName": "Public Load Balancer"
      },
      "properties": {
        "frontendIPConfigurations": [
          {
            "name": "LoadBalancerFrontend",
            "properties": {
              "publicIPAddress": {
                "id": "[variables('publicIPAddressID')]"
              }
            }
          }
        ],
        "backendAddressPools": [
          {
            "name": "LoadBalancerBackend"
          }
        ],
        "loadBalancingRules": [
          {
            "properties": {
              "frontendIPConfiguration": {
                "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '/frontendIpConfigurations/LoadBalancerFrontend')]"
              },
              "backendAddressPool": {
                "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '/backendAddressPools/LoadBalancerBackend')]"
              },
              "probe": {
                "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '/probes/lbprobe')]"
              },
              "protocol": "Tcp",
              "frontendPort": 80,
              "backendPort": 80,
              "idleTimeoutInMinutes": 15
            },
            "Name": "lbruleport80"
          }
        ],
        "probes": [
          {
            "properties": {
              "protocol": "Tcp",
              "port": 80,
              "intervalInSeconds": 15,
              "numberOfProbes": 2
            },
            "name": "lbprobe"
          }
        ]
      }
    }
  ],
  "outputs": {
  }
}

Finding an Oracle Linux VM Image

Since we’re using Resource Manager, we need to find the details that identify the VM Image that we will use to create our demo environment.

Azure PowerShell V1.0

# Login to your Azure Account
Login-AzureRmAccount

$location = 'eastus'
  
Get-AzureRmVMImagePublisher -Location $location `
    | Where-Object -Property PublisherName -Like oracle

$publisherName = 'oracle'
  
Get-AzureRmVMImageOffer -Location $location `
                      -PublisherName $publisherName
 
$offer = 'Oracle-Linux-7'
  
Get-AzureRmVMImageSku -Location $location `
                      -PublisherName $publisherName `
                      -Offer $offer `
      | Select-Object -Property 'Skus'

Cleanup

Once we’re done experimenting and we no longer require the environment, it’s time to destroy it. The following is great way to cleanup. It destroys everything that is created within the resource group. One thing to keep in mind though… this cannot be reversed.

Azure PowerShell V1.0

# Login to your Azure Account
Login-AzureRmAccount

Remove-AzureRmResourceGroup -Name 'geoexperiment'

Feedback? Share in the comments below.

No Comments

Be the first to start the conversation!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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