Saturday 25 July 2020

Controlling Your Costs With Azure Policy

I made a mistake. I've been trying to figure something out in Azure, and I needed a website to do it with, so I spun one up on a new App Service Plan. I missed that the default plan is a premium plan, and I only noticed this the other day. In the meantime, my site has been racking up charges, and as a result my Azure bill for July is going to be ... about £100. 

Which is (kind of) fine, experience is the best teacher, it's my mistake and I'm going to own it. It's not a problem-causing amount of money for me. And I'm not averse to spending that kind of money on Azure services on purpose, it's just not the sort of thing I want to be doing again by accident.

Fortunately, I know by using Azure Policy that you can put all kinds of controls in Azure to stop people doing this sort of thing and ending up costing your company lots of money, so I've put the same kind of control in for myself.

What I need is a policy that stops me creating any more premium App Service Plans, and this is not that difficult to achieve.

Policies work on an 'if-then' model, you give the 'if' clause a set of conditions and if the set of conditions matches, then the 'then' clause fires. So in my case when I'm creating a new Azure resource my conditions will be: the resource type is an App Service Plan, and the SKU name is not set to 'F1', and my action will be to stop the action i.e. prevent the resource from being created.

Policies are written in JSON, you can write them directly in the Azure Portal, there is also a VSCode extension to help you with creating them. Here's a skeleton policy:

"policyRule": {
    "if": {
      }
    },
    "then": {
      "effect"""
    }
  }

Effects can be any of a number of values, including "deny" which disallows the action completely, "audit" which allows the action but logs that your policy is being violated, and a number of others including some that will change your action to make it comply with your policy.

So clearly the effect in my then clause needs to be "deny", so that it correctly prevents the action. Which leaves me to work out what the correct if clause is. As I said above, I have two conditions, so I'll be using the allof condition which translates to an AND operator (there is also anyof which is the equivalent of OR). My first condition is that I want the policy to act on App Service Plans, so I'll need to be looking at the type of thing that's being created. Policies use a system of aliases for types (with namespaces), there are two ways to find the alias you want. The hard(er) way is to use the Azure command-line tool to query a list of available aliases as suggested in the documentation e.g.

az provider show --namespace Microsoft.Web --expand "resourceTypes/aliases" --query "resourceTypes[].aliases[].name"

The easy way is to go through the resource creation process in the Azure portal, through to the 'Review and Create' step. Once there, don't create the resource, but download the ARM template, and then search through it for the 'resources' element; inside the resources element, look for a 'type' property, and the value of the property is the alias you want. Which tells me that for an App Service Plan, the alias I should be using is 'Microsoft.Web/serverfarms'. On to the SKU value! I need to look at a property of a serverfarms object and check whether or not the value is 'F1' (the name of the free tier). And if we go back to the command-line query and browse the results, we can see that under Microsoft.Web/serverfarms there is a sku.name property; we can use that with a 'notEquals' operator to check the value.

So our whole policy looks like this:

"policyRule": {
      "if": {
        "allof": [
          {
            "field": "type",
            "equals": "Microsoft.Web/serverfarms"
          },
          {
            "field": "Microsoft.Web/serverfarms/sku.name",
            "notEquals": "F1"
          }
        ]
      },
      "then": {
        "effect": "deny"
      }
    }

I applied this policy to my subscription, and here's what happens if I now try to create a premium service plan.

Note that the policy evaluation happens in the review stage, before the resource is created.

We've looked here at creating a basic (but useful!) Azure policy to restrict creation of a premium Azure resource, and you can apply that to your own subscriptions to help keep you from making the same mistake I did. And this is only a taste of what you can achieve with Azure Policy.

(And now I'm protected from accidentally creating any more premium App Service Plans - but I could create a premium database, or a storage account, or a VM... Hmm, maybe I need to go write some more policies...)