When your first starting with Microsoft Azure for straightforward projects or proof of concept designs, the Portal is your go-to destination for reviewing, managing, updating and creating resources. Even for larger scale deployments, you will typically find yourself within there most of the time; what may have changed, in this scenario, is the mechanism through which you deploy new resources or any changes to existing ones. Developers have a range of options at their disposal to programmatically carry out these types of activities:
- Via PowerShell, through the Az or the older AzureRM module.
- Through CLI, utilising syntax that traditional Linux developers would be more familiar with.
- By using predefined templates, built out using JSON, that can then be deployed using either the Portal, PowerShell or CLI.
The last of these options can be of most benefit if you are already using Git source control for your projects (via GitHub, Azure DevOps, BitBucket etc.) and you have a desire to implement Continuous Integration (CI) and automated release management for your Azure templates. Both of these options enable you to validate your templates before deploying, to ensure no obvious errors occur, and to reduce the risk of human error as part of frequent software deployments. Making a move from managing your Azure resources from within the portal to Azure Templates is relatively straightforward, thanks to the options available to us to export our existing resources as templates. Notwithstanding this fact, there will still be times where you find yourself hitting a few stumbling blocks as you begin to fully understand the behaviour when deploying resources out in this manner.
An example better illustrates this issue. Let’s assume we have deployed out an App Service resource manually via the Azure Portal and, over time, we have assigned it the following Application settings:
We now decide it would be desirable to move towards utilising Azure Resource Manager templates and, as such, define the following JSON template for this resource:
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"Parameters": {
"name": {
"type": "String"
},
"hostingPlanName": {
"type": "String"
},
"location": {
"type": "String"
},
"sku": {
"type": "String"
},
"serverFarmResourceGroup": {
"type": "String"
},
"subscriptionId": {
"type": "String"
}
},
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-03-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": []
},
"serverFarmId": "[concat('/subscriptions/', parameters('subscriptionId'),'/resourcegroups/', parameters('serverFarmResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2016-09-01",
"name": "[parameters('hostingPlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('sku')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]",
"numberOfWorkers": "1"
}
}
]
}
And, within Azure DevOps, we have the following Release Pipeline task created:
Note in particular the selection of the Incremental option, recommended if you want to ensure that your deployment does not accidentally delete any resources not defined in the template.
After using the template below as part of a release and upon navigating back to our Application settings for the App Service, we notice that all of them have vanished completely:
With the Incremental option specified above, you would be forgiven for thinking that the template deployment is “broken”, as it would appear to have done the complete opposite of what the setting implies it will do. The fault here lies with the JSON template itself, which has not been updated to include all of the Application settings needed for the App Service. During the deployment step, Azure will compare your template against any existing App Service resource and, if the Application settings are not there, they are permanently deleted. We can observe this behaviour in practice by adding our Application settings back on manually and by only specifying a single setting on our Microsoft.Web/sites resource within our JSON template:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-03-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [
{
"name": "MyAppSetting2",
"value": "value456"
}
]
},
"serverFarmId": "[concat('/subscriptions/', parameters('subscriptionId'),'/resourcegroups/', parameters('serverFarmResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
}
}
Post-deployment, we can observe the following on the App Service:
So the answer, at this point, is pretty clear; update the entire JSON template to include all required Application Settings as part of your App Service:
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"Parameters": {
"name": {
"type": "String"
},
"hostingPlanName": {
"type": "String"
},
"location": {
"type": "String"
},
"sku": {
"type": "String"
},
"serverFarmResourceGroup": {
"type": "String"
},
"subscriptionId": {
"type": "String"
}
},
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-03-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [
{
"name": "MyAppSetting1",
"value": "value123"
},
{
"name": "MyAppSetting2",
"value": "value456"
},
{
"name": "MyAppSetting3",
"value": "value789"
}
]
},
"serverFarmId": "[concat('/subscriptions/', parameters('subscriptionId'),'/resourcegroups/', parameters('serverFarmResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2016-09-01",
"name": "[parameters('hostingPlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('sku')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]",
"numberOfWorkers": "1"
}
}
]
}
As alluded to already, the Incremental deployment option can be a little bit misleading in this context, as you may naturally assume that Azure would take no action to remove anything if this option is specified. The example outlined in this post clearly illustrates that this does not apply to resourceĀ properties, which can be subject to unintended alterations if your Azure Template does not specify them explicitly. Take care when migrating across to Azure RM templates to ensure thatĀ every single resource setting that you need is copied through and, also, carry out test deployments into dedicated testing/UAT environments to verify the exact behaviour that your templates have on your existing Azure resources.