In the early days of Microsoft Azure the Portal was the primary tool to go in and configure your cloud components. After some time the Azure Service Manager API’s were introduced as a set of both PowerShell and Command-Line tools (X-Plat CLI). These tools allowed for Azure Automation to be scripted, however they were still a bit cumbersome as they were procedural based. More recently Microsoft overhauled the entire Azure Portal that exists today as well as a brand new set of Azure Resource Manager API’s. The purpose of Azure Resource Manager is more than just replacing Azure Service Manager. It’s real purpose is a story about automation and DevOps.

Azure Resource Manager

The most easily visible change to Microsoft Azure that came with the migration to Azure Resource Manager is the concept of Resource Groups. Resource Groups allow for various cloud components / entities (now referred to as Resources) deployed into Azure to be placed into logical groupings. The Resource Groups allow for a vastly improved experience for managing Azure Resources.

All the various Azure Resources (Web App, SQL Database, Storage, VM’s, etc) that make up a single Application or System can be grouped together as a logical unit. This allows for all the Resource Groups to be listed and navigated within the Azure Portal in a fashion that brings a high level of clarity as to what resources go together to form a complete application or system.

In the early days of Microsoft Azure it wasn’t difficult to manage all the Azure Resources since most companies didn’t really have that much deployed into a single Azure Subscription. As companies started adopting Azure more and migrating systems over to the cloud there became an increasing need to be able to more easily organize and manage the huge number of Azure Resources making up a dozen or even hundreds of applications. This lead to the natural evolution of Azure to implementing Azure Resource Groups.

While the “Groups” feature of Azure Resource Groups is the most visible there are a number of additional features and benefits to Azure Resource Groups. Here’s a list of a few of these:

  • Organize all Azure Resources of a single application into a single logical Azure Resource Group
  • Manage, Deploy and Monitor Azure Resources within a group together to treat them as building blocks of an application, rather than as stand-along components
  • Ability to use declarative ARM Templates to define deployments
  • Role Based Access Control (RBAC) for securing and controlling access to all resources within a group
  • Ability to add Tags to each resource within a Resource Group so that additional metadata can be associated with Azure Resources
  • More transparent billing by allowing for the costs of an entire Resource Group or resources with a specific Tag to be viewed

The migration of Azure Management to using Azure Resource Manager is a progression towards a more easily managed and organized Microsoft Azure Cloud.

Azure Automation

Using the Azure PowerShell and Azure CLI (Cross-Platform Command-Line Interface) Tools allow for scriptable management and deployment of Azure resources in a procedural manner. This is the method for Azure Automation that’s been supported since the introduction of Azure Service Management. The shift to Azure Resource Management still supports Azure PowerShell and Xplat-CLI with an additional set of commands for each tool that support Azure Resource Management.

The procedural method of using scripts to automate Azure Resource management and deployment is still fully supported by using the Azure PowerShell and Azure CLI tools in addition to the older Azure Service Management. Procedural automation allows for building scripts that are run line by line, from start to finish. This is a perfectly fine method of implementing automation and has pretty much been an industry standard for a really long time. This can also be an extremely quick method of implementing automation.

In addition to Automation, the Azure PowerShell and Azure CLI allow for quick command-line interface for interacting, managing and deploying Azure Resources that provides administrators an alternative to using the Azure Portal within a web browser.

ARM Template Deployment

One of the brand new features introduced by Azure Resource Management is ARM Templates (Azure Resource Management Templates). These Templates are built as a JSON file that declaratively defines the deployment and configuration of all the Azure Resources within a single Azure Resource Group. This allow for the definition of the Azure Resources for an application to be more simply defined while allowing Azure to manage the order in which everything needs to be deployed based on the necessary dependencies.

Being a JSON file, ARM Templates provide a declarative method for defining Azure deployments. Defining deployments declaratively in this manner is known as Infrastructure as Code (IaC). Infrastructure as Code not only provides a nice way to be able to upload an ARM Template to the Azure Portal for deployment, but also allows for more easily using it in Automated Build and Deployment scenarios. The ARM Template for an application can be checked into a Source Code Repository (TFS, Git, Github, etc) along-side the application code itself, or the ARM Template code can be maintained within a repository by itself for purely Infrastructure deployments.

Storing ARM Templates within a Source Code Repository allows for versioning and change tracking to more easily be managed and rollbacked when necessary without requiring additional documentation that can be easily neglected over time.

Want to see what an ARM Template’s JSON looks like? Here’s an ARM Template from the Azure QuickStart Templates repository full of community contributed ARM Templates that deploys an Azure Resource Group with a Web App and SQL Database:

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "skuName": {
      "type": "string",
      "defaultValue": "F1",
      "allowedValues": [
        "F1",
        "D1",
        "B1",
        "B2",
        "B3",
        "S1",
        "S2",
        "S3",
        "P1",
        "P2",
        "P3",
        "P4"
      ],
      "metadata": {
        "description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
      }
    },
    "skuCapacity": {
      "type": "int",
      "defaultValue": 1,
      "minValue": 1,
      "metadata": {
        "description": "Describes plan's instance count"
      }
    },
    "administratorLogin": {
      "type": "string",
      "metadata": {
        "description": "The admin user of the SQL Server"
      }
    },
    "administratorLoginPassword": {
      "type": "securestring",
      "metadata": {
        "description": "The password of the admin user of the SQL Server"
      }

    },
    "databaseName": {
      "type": "string",
      "defaultValue": "sampledb",
      "metadata": {
        "description": "The name of the new database to create."
      }
    },
    "collation": {
      "type": "string",
      "defaultValue": "SQL_Latin1_General_CP1_CI_AS",
      "metadata": {
        "description": "The database collation for governing the proper use of characters."
      }
    },
    "edition": {
      "type": "string",
      "defaultValue": "Basic",
      "allowedValues": [
        "Basic",
        "Standard",
        "Premium"
      ],
      "metadata": {
        "description": "The type of database to create."
      }
    },
    "maxSizeBytes": {
      "type": "string",
      "defaultValue": "1073741824",
      "metadata": {
        "description": "The maximum size, in bytes, for the database"
      }
    },
    "requestedServiceObjectiveName": {
      "type": "string",
      "defaultValue": "Basic",
      "allowedValues": [
        "Basic",
        "S0",
        "S1",
        "S2",
        "P1",
        "P2",
        "P3"
      ],
      "metadata": {
        "description": "Describes the performance level for Edition"
      }
    }
  },
  "variables": {
    "hostingPlanName": "[concat('hostingplan', uniqueString(resourceGroup().id))]",
    "webSiteName": "[concat('webSite', uniqueString(resourceGroup().id))]",
    "sqlserverName": "[concat('sqlserver', uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "name": "[variables('sqlserverName')]",
      "type": "Microsoft.Sql/servers",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "SqlServer"
      },
      "apiVersion": "2014-04-01-preview",
      "properties": {
        "administratorLogin": "[parameters('administratorLogin')]",
        "administratorLoginPassword": "[parameters('administratorLoginPassword')]"
      },
      "resources": [
        {
          "name": "[parameters('databaseName')]",
          "type": "databases",
          "location": "[resourceGroup().location]",
          "tags": {
            "displayName": "Database"
          },
          "apiVersion": "2014-04-01-preview",
          "dependsOn": [
            "[variables('sqlserverName')]"
          ],
          "properties": {
            "edition": "[parameters('edition')]",
            "collation": "[parameters('collation')]",
            "maxSizeBytes": "[parameters('maxSizeBytes')]",
            "requestedServiceObjectiveName": "[parameters('requestedServiceObjectiveName')]"
          }
        },
        {
          "type": "firewallrules",
          "apiVersion": "2014-04-01-preview",
          "dependsOn": [
            "[variables('sqlserverName')]"
          ],
          "location": "[resourceGroup().location]",
          "name": "AllowAllWindowsAzureIps",
          "properties": {
            "endIpAddress": "0.0.0.0",
            "startIpAddress": "0.0.0.0"
          }
        }
      ]
    },
    {
      "apiVersion": "2015-08-01",
      "name": "[variables('hostingPlanName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "HostingPlan"
      },
      "sku": {
        "name": "[parameters('skuName')]",
        "capacity": "[parameters('skuCapacity')]"
      },
      "properties": {
        "name": "[variables('hostingPlanName')]"
      }
    },
    {
      "apiVersion": "2015-08-01",
      "name": "[variables('webSiteName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[variables('hostingPlanName')]"
      ],
      "tags": {
        "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "empty",
        "displayName": "Website"
      },
      "properties": {
        "name": "[variables('webSiteName')]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
      },
      "resources": [
        {
          "apiVersion": "2015-08-01",
          "type": "config",
          "name": "connectionstrings",
          "dependsOn": [
            "[variables('webSiteName')]"
          ],
          "properties": {
            "DefaultConnection": {
              "value": "[concat('Data Source=tcp:', reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName, ',1433;Initial Catalog=', parameters('databaseName'), ';User Id=', parameters('administratorLogin'), '@', variables('sqlserverName'), ';Password=', parameters('administratorLoginPassword'), ';')]",
              "type": "SQLServer"
            }
          }
        }
      ]
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat(variables('hostingPlanName'), '-', resourceGroup().name)]",
      "type": "Microsoft.Insights/autoscalesettings",
      "location": "[resourceGroup().location]",
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "Resource",
        "displayName": "AutoScaleSettings"
      },
      "dependsOn": [
        "[variables('hostingPlanName')]"
      ],
      "properties": {
        "profiles": [
          {
            "name": "Default",
            "capacity": {
              "minimum": 1,
              "maximum": 2,
              "default": 1
            },
            "rules": [
              {
                "metricTrigger": {
                  "metricName": "CpuPercentage",
                  "metricResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                  "timeGrain": "PT1M",
                  "statistic": "Average",
                  "timeWindow": "PT10M",
                  "timeAggregation": "Average",
                  "operator": "GreaterThan",
                  "threshold": 80.0
                },
                "scaleAction": {
                  "direction": "Increase",
                  "type": "ChangeCount",
                  "value": 1,
                  "cooldown": "PT10M"
                }
              },
              {
                "metricTrigger": {
                  "metricName": "CpuPercentage",
                  "metricResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                  "timeGrain": "PT1M",
                  "statistic": "Average",
                  "timeWindow": "PT1H",
                  "timeAggregation": "Average",
                  "operator": "LessThan",
                  "threshold": 60.0
                },
                "scaleAction": {
                  "direction": "Decrease",
                  "type": "ChangeCount",
                  "value": 1,
                  "cooldown": "PT1H"
                }
              }
            ]
          }
        ],
        "enabled": false,
        "name": "[concat(variables('hostingPlanName'), '-', resourceGroup().name)]",
        "targetResourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
      }
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat('ServerErrors ', variables('webSiteName'))]",
      "type": "Microsoft.Insights/alertrules",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[variables('webSiteName')]"
      ],
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('webSiteName')))]": "Resource",
        "displayName": "ServerErrorsAlertRule"
      },
      "properties": {
        "name": "[concat('ServerErrors ', variables('webSiteName'))]",
        "description": "[concat(variables('webSiteName'), ' has some server errors, status code 5xx.')]",
        "isEnabled": false,
        "condition": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
          "dataSource": {
            "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
            "resourceUri": "[resourceId('Microsoft.Web/sites', variables('webSiteName'))]",
            "metricName": "Http5xx"
          },
          "operator": "GreaterThan",
          "threshold": 0.0,
          "windowSize": "PT5M"
        },
        "action": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
          "sendToServiceOwners": true,
          "customEmails": [ ]
        }
      }
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat('ForbiddenRequests ', variables('webSiteName'))]",
      "type": "Microsoft.Insights/alertrules",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[variables('webSiteName')]"
      ],
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('webSiteName')))]": "Resource",
        "displayName": "ForbiddenRequestsAlertRule"
      },
      "properties": {
        "name": "[concat('ForbiddenRequests ', variables('webSiteName'))]",
        "description": "[concat(variables('webSiteName'), ' has some requests that are forbidden, status code 403.')]",
        "isEnabled": false,
        "condition": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
          "dataSource": {
            "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
            "resourceUri": "[resourceId('Microsoft.Web/sites', variables('webSiteName'))]",
            "metricName": "Http403"
          },
          "operator": "GreaterThan",
          "threshold": 0,
          "windowSize": "PT5M"
        },
        "action": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
          "sendToServiceOwners": true,
          "customEmails": [ ]
        }
      }
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat('CPUHigh ', variables('hostingPlanName'))]",
      "type": "Microsoft.Insights/alertrules",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[variables('hostingPlanName')]"
      ],
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "Resource",
        "displayName": "CPUHighAlertRule"
      },
      "properties": {
        "name": "[concat('CPUHigh ', variables('hostingPlanName'))]",
        "description": "[concat('The average CPU is high across all the instances of ', variables('hostingPlanName'))]",
        "isEnabled": false,
        "condition": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
          "dataSource": {
            "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
            "resourceUri": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
            "metricName": "CpuPercentage"
          },
          "operator": "GreaterThan",
          "threshold": 90,
          "windowSize": "PT15M"
        },
        "action": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
          "sendToServiceOwners": true,
          "customEmails": [ ]
        }
      }
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat('LongHttpQueue ', variables('hostingPlanName'))]",
      "type": "Microsoft.Insights/alertrules",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[variables('hostingPlanName')]"
      ],
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "Resource",
        "displayName": "AutoScaleSettings"
      },
      "properties": {
        "name": "[concat('LongHttpQueue ', variables('hostingPlanName'))]",
        "description": "[concat('The HTTP queue for the instances of ', variables('hostingPlanName'), ' has a large number of pending requests.')]",
        "isEnabled": false,
        "condition": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition",
          "dataSource": {
            "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleMetricDataSource",
            "resourceUri": "[concat(resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
            "metricName": "HttpQueueLength"
          },
          "operator": "GreaterThan",
          "threshold": 100.0,
          "windowSize": "PT5M"
        },
        "action": {
          "odata.type": "Microsoft.Azure.Management.Insights.Models.RuleEmailAction",
          "sendToServiceOwners": true,
          "customEmails": [ ]
        }
      }
    },
    {
      "apiVersion": "2014-04-01",
      "name": "[concat('AppInsights', variables('webSiteName'))]",
      "type": "Microsoft.Insights/components",
      "location": "Central US",
      "dependsOn": [
        "[variables('webSiteName')]"
      ],
      "tags": {
        "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('webSiteName')))]": "Resource",
        "displayName": "AppInsightsComponent"
      },
      "properties": {
        "ApplicationId": "[variables('webSiteName')]"
      }
    }
  ],
  "outputs": {
    "siteUri": {
      "type": "string",
      "value": "[reference(concat('Microsoft.Web/sites/', variables('webSiteName')), '2015-08-01').hostnames[0]]"
    },
    "sqlSvrFqdn": {
      "type": "string",
      "value": "[reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName]"
    }
  }

}

As is visible by the above example ARM Template, the file uses the JSON syntax which has become an industry standard for defining both data and configurations on every development platform. One of the reasons for this is that JSON provides more clarity and brevity that make things both easier to read by a person, as well as making the files smaller and quicker to transmit over the Internet. Using JSON for ARM Templates was a good choice by the Microsoft Azure Team rather than using XML or some other proprietary file format instead.

One of the concerns many Developers and IT Pros have with ARM Templates is that it’s not easy to know what to enter where within a template file to configure specific resources. To help with this Microsoft has a few different tooling support options available. The Azure SDK gives Visual Studio 2015 a new Azure Resource Group Project type that includes some GUI for editing ARM Templates. Also, in addition to the Azure QuickStart Templates repository, the Azure Portal and Azure PowerShell have been given support to Export a Resource Group to an ARM Template.

Azure + DevOps

Lastly in this article, but perhaps the most important point to note about Azure Resource Manager is the improved DevOps integration possibilities enabled by all the above mentioned features of ARM. The root of DevOps is Communication, and there is no better way to communicate the setup and deployment of a hosting environment than Automation.

The migration Azure management to Azure Resource Manager, combined with ARM Templates, allows for a HUGE improvement in the overall DevOps story of Azure. Microsoft Azure has always offered tremendous benefits that lend themselves naturally to DevOps, but Azure Resource Manager really completes the Azure + DevOps story in a comprehensive way!

If managing Azure Resources has been an issue for you in the past then you should get a breathe of fresh air when you adopt Azure Resource Manager, Resource Groups, and ARM Templates. The Microsoft Azure platform is becoming even more awesome!

Posted by Chris Pietschmann

Chris is a Microsoft MVP and has 15+ years of experience building enterprise systems both in the cloud and on-premises. He is also a Microsoft Certified (MCSD) Azure Solutions Architect. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.

One Comment

  1. […] What is Azure Resource Manager? (Chris Pietschmann) […]

    Like

    Reply

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 )

Google+ photo

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

Connecting to %s