Working with Azure Resource Manager (ARM) templates can sometimes feel like a pure grind. You could say the experience is akin to spending many hours wandering around a JRPG video game, killing enemies and hoping to get that super rare drop you’ve got your heart set on. No matter how much you try, your prize can often not surface until completing many hours of tedious, repetitive tasks. Whether we are looking at a real IT or fantasy world situation, the experience can feel startlingly similar. What can then compound IT issues even further is when you bring other tools into the equation, such as Azure Pipelines. In the case of ARM related deployments, they are usually happy bedfellows and, arguably, an incredibly potent combination in streamlining your cloud deployments and removing human intervention from the process. That is, of course, unless they start throwing pesky little errors like this:
At this point, it’s probably useful to step back for a few moments and explain the situation. I was attempting to use Azure Pipelines to deploy out a single Linux App Service and its corresponding .NET Core 3.1 web application. This process involves two-stages in Azure - we first must create an App Service Plan. Think of this as your physical web server, with a defined specification, and the one that costs you money each month. Next, we then create our Web Apps (or App Services). Depending on the pricing tier of our App Service Plan, we can have several of these hosted on a single App Service Plan. All of this can be created from the Azure Portal or in an Azure template like the one below:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"Parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
"name": "MyLinuxAppServicePlan",
"location": "UK South",
"sku": {
"name": "B1"
},
"kind": "linux"
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "MyDOTNETCoreApp",
"location": "UK South",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', 'MyLinuxAppServicePlan')]"
],
"Identity": {
"type": "SystemAssigned"
},
"properties": {
"name": "MyDOTNETCoreApp",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', 'MyLinuxAppServicePlan')]",
"siteConfig": {
"metadata": [
{
"name": "CURRENT_STACK",
"value": "dotnetcore"
}
]
}
},
"resources": []
}
],
"outputs": {}
}
For this example, we are explicitly creating a Linux App Service Plan - primarily because its cheaper and .NET Core apps can run on it without issue - so, as such, we tag on two specific properties:
- For the App Service Plan (Microsoft.Web/serverfarms), we populate the kind value with linux.
- Next, for our Web App (Microsoft.Web/sites), we tell the web app to expect a .NET Core app, via the CURRENT_STACK property.
And this template does work from a deployment standpoint - try it yourself if you don’t believe me. The result is an apparently “working” Linux web app, that is ready to have its corresponding web application deployed out to using the appropriate DevOps task. Or not, because of the error message we get above. Attempting to do a local deployment of the application via Visual Studio was a no go too. The Linux Web App was not even visible when trying to select it from the correct Subscription.
After performing painstaking research using tools so far undiscovered by the average IT professional, I stumbled across a Stack Overflow post, with a secondary answer that seemed to have some merit, particularly given that Visual Studio was having difficulty locating the web app. It turns out that, because of a missing property at the App Service Plan level, the resource had technically been created as a Windows App Service Plan, not Linux as the Azure Portal seemed to indicate. Thankfully, it’s an easy fix - just add on a new property called reserved to the App Service Plan and set it’s value to true. The updated template would, therefore, look like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"Parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
"name": "MyLinuxAppServicePlan",
"location": "UK South",
"sku": {
"name": "B1"
},
"kind": "linux",
"properties": {
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"name": "MyDOTNETCoreApp",
"location": "UK South",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', 'MyLinuxAppServicePlan')]"
],
"Identity": {
"type": "SystemAssigned"
},
"properties": {
"name": "MyDOTNETCoreApp",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', 'MyLinuxAppServicePlan')]",
"siteConfig": {
"metadata": [
{
"name": "CURRENT_STACK",
"value": "dotnetcore"
}
]
}
},
"resources": []
}
],
"outputs": {}
}
Running this updated template through your pipeline and then re-attempting the App Service update will cause it to complete without issue - hooray!
Perhaps I have become jaded in my experience, but these types of bitty issues are, regrettably, commonplace when attempting to work with ARM templates. Often, merely copying and pasting an example or exporting an existing resource a template will be sufficient, and there will be a degree of fiddling needed to get things working nicely within your deployments. That’s not to say that I hate ARM templates with a burning passion, far from it. They are the best tool in your arsenal when embracing a truly DevOps culture within your organisation, with a prize - fully functioning, automated deployments - that, I think, more than exceeds that same rush after getting your rare drop in a JRPG đŸ™‚