Using ARM (Azure Resource Manager) templates is actually a common way to deploy Azure Infrastructure. When a cloud infrastructure is created from an ARM template, the infrastructure is “fixed” in the template, however it can be useful to modify it according to the environment target.

A new ARM « function helper » permits to declare conditions in deployment templates. The goal of a condition is to evaluate if a resource has to de be deployed or not. We are going to discover this new possibility throughout this blog post.

 

Cost analysis and setup of the cloud infrastructure

The ARM template purpose is to define an Azure Infrastructure in a json format.The template is sent to Azure API which deploys the azure resources defined. A deployment template is usually linked to several parameters templates : one for each target environment, for example : dev, staging and prod.

 

Example:

We define a deployment template named deploy.json. It defines the following architecture:

  • A web app named « back »
  • A service plan « back-sp » which hosts the web app « back »
  • A web app named « front »
  • A service plan « front-sp » which hosts the web app « front »

 

This architecture must be deployable for 3 targets environments « dev », « staging » and « prod », that’s why we define a parameter template for each environment :

parameters.dev.json : Define a pricing tier « Basic » (B1) for both service plan

parameters.staging.json : Define a pricing tier « Standard » (S0) for both service plan

parameters.prod.json : Define a pricing tier « Standard » (S2) for both service plan

Deployment and parameters templates are available on my GitHub.

 

Deployment template details

In the deployment template, we add a parameter named « env », its value will be set directly by the value (« dev », « staging » or « prod ») defined in parameters files. Its value will be concatenate to azure resources name. The parameter « prefix » permits to specify a text string which will prefix each azure resource name using the following pattern: {prefix}-resourceName-{env} :


"parameters": {
   "sku": {
     "type": "string",
     "defaultValue": "F1",
     "allowedValues": [
       "F1",
       "B1",
       "B2",
       "S1",
       "S2",
     ]
  },
   "env": {
    "type": "string",
     "allowedValues": [
       "dev",
       "staging",
       "prod"
     ]
  }
 },
  "variables": {
    "prefix": "tra",
    "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]",
    "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]",
    "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]",
    "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]"
 }

 
 

Once the deployment template deploy.json deployed with the parameter file parameters.dev.json, resources are successfully deployed in Azure :

 

Cloud infrastructure cost

According to the environment, service plans capacity must be different. In fact, « dev » and « staging » environment don’t need powerful servers, moreover using powerful servers is more expensive :

  • Service plan Standard Basic (B1) = 47 € / month
  • Service plan Standard (S0) = 74 € / month
  • Service plan Standard (S2) = 150 € / month

Our infrastructure use a service plan for each web application, here is the detailed pricing:

  • dev = 2 x B1 = 94 € / month
  • staging = 2 x S0 = 154 €/ month
  • premium = 2 x S2 = 300 € / month

The total is around 548 € per month.

 

Optimizing the cloud infrastructure cost

The production infrastructure must not be impacted with the need of saving money. Therefore it must be sized to provide the best quality of service. By using a service plan per web app, it permits to easily manage scale needs for each web app independently.

However « dev » and « staging » environments don’t have scaling needs, that’s why they can be gathered on a unique service plan. This operation won’t have any business impact and it will helps to save money.

 

By using only one service plan for « dev », and « staging » environment, the total cost is cheaper:

  • dev = 1 x B1 = 47 € / month
  • staging = 1 x S0 = 74 € / month
  • premium = 2 x S2 = 300 € / month

The total is now around 421 € per month.

 

To realize this, we need to modify the azure infrastructure defined in the deployment template according to the target environment.

We can achieve it in 3 steps:

  • Add a new service plan
  • Create a resource catalog
  • Use the « condition » function helper

 

1. Add the new service plan

First, it’s necessary to add a new service plan in the deployment template. This new resource will be a shared one, and it will be deployed only if the environement target is « dev » or « staging ». A variable called spSharedName is added in the deployment template too. It will help to name the new service plan using the existing naming strategy :

 
 "variables": {
     "prefix": "tra",
     "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]",
     "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]",
     "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]",
     "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]",
     "spSharedName": "[Concat(variables('prefix'), '-shared-sp-', parameters('env'))]",
 },
 "resources": [
  {
    "apiVersion": "2015-08-01",
    "name": "[variables('spSharedName')]",
    "type": "Microsoft.Web/serverfarms",
    "location": "[resourceGroup().location]",
    "tags": {
       "displayName": "sp shared"
    },
    "sku": {
    "name": "[parameters('sku')]"
  },
 "properties": {
   "name": "[variables('spSharedName')]"
  }
 }

This new service plan is not used yet, it’s time to implement the logic! 🙂

 

2. Resources catalog

In ARM templates, variables can be modelized as complex objects. That’s why we are going to create a new variable named spConfig : it will store a configuration defining web app hostings according to the target environment. This variable will helps us to manage easily the service plan assignment for each web app :

"variables": {
    "prefix": "tra",
    "webSiteBackName": "[Concat(variables('prefix'), '-back-', parameters('env'))]",
    "webSiteFrontName": "[Concat(variables('prefix'), '-front-', parameters('env'))]",
    "spBackName": "[Concat(variables('prefix'), '-back-sp-', parameters('env'))]",
    "spFrontName": "[Concat(variables('prefix'), '-front-sp-', parameters('env'))]",
    "spSharedName": "[Concat(variables('prefix'), '-shared-sp-', parameters('env'))]",
    "spConfig": {
      "dev": {
          "spBack": "[variables('spSharedName')]",
          "spFront": "[variables('spSharedName')]"
      },
      "staging": {
          "spBack": "[variables('spSharedName')]",
          "spFront": "[variables('spSharedName')]"
      },
      "prod": {
         "spBack": "[variables('spBackName')]",
         "spFront": "[variables('spFrontName')]"
     }
   },
   "currentEnvConfig": "[variables('spConfig')[parameters('env')]]"
 }

The spConfig is the resource catalog. Another variable named currentEventConfig loads the configuration using the current environment, it’s directly used by « back » and « front » web apps to define their target service plan :

    {
      "apiVersion": "2015-08-01",
      "name": "[variables('webSiteBackName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "tags": {
        "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('currentEnvConfig').spBack)]": "Resource",
        "displayName": "back"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms/', variables('currentEnvConfig').spBack)]"
      ],
      "properties": {
        "name": "[variables('webSiteBackName')]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('currentEnvConfig').spBack)]"
      }
    },
    {
      "apiVersion": "2015-08-01",
      "name": "[variables('webSiteFrontName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "tags": {
        "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('currentEnvConfig').spFront)]": "Resource",
        "displayName": "front"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms/', variables('currentEnvConfig').spFront)]"
      ],
      "properties": {
        "name": "[variables('webSiteFrontName')]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('currentEnvConfig').spFront)]"
      }
    }

 

3. The « condition » function helper

In the resources catalog we just created we can see that web apps (« back » and « front ») are hosted on the same service plan (« spShared ») for staging and dev environments. When theses environment are used it’s useless to deploy service plans (« spBack ») and (« spFront ») defined in the template too.

We want to implement the following logic in the deployment template :

  1. If we target « dev » and « staging » environment, we have to deploy only the service plan « spShared » and web applications will use this service plan
  2. If we target « prod » environment, we have to deploy a service plan per web app

 

To implement this logic, we are going to use the function helper « condition » in each service plan definition. This function will check the current environment value to decide if the service plan has to be deployed or not.

Services plan « spBack » and « spFront » will be deployed only if the following condition is verified :

« condition » : « [equals(parameters(‘env’),’prod’)] »

The  « spShared » service plan will be deployed only if the following condition is verified :

« condition » : « [not(equals(parameters(‘env’),prod))] »

The « condition » function can be used directly in resources nodes in the deployment template.  Here is a example of using this function for each service plan defined in the deployment template:

   {
      "condition": "[not(equals(parameters('env'),'prod'))]",
      "apiVersion": "2015-08-01",
      "name": "[variables('spSharedName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "sp shared"
      },
      "sku": {
        "name": "[parameters('sku')]"
      },
      "properties": {
        "name": "[variables('spSharedName')]"
      }
    },
    {
      "condition": "[equals(parameters('env'),'prod')]",
      "apiVersion": "2015-08-01",
      "name": "[variables('spBackName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "sp back"
      },
      "sku": {
        "name": "[parameters('sku')]"
      },
      "properties": {
        "name": "[variables('spBackName')]"
      }
    },
    {
      "condition": "[equals(parameters('env'),'prod')]",
      "apiVersion": "2015-08-01",
      "name": "[variables('spFrontName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "sp front"
      },
      "sku": {
        "name": "[parameters('sku')]"
      },
      "properties": {
        "name": "[variables('spFrontName')]"
      }
    }

 

Once theses 3 steps done, using the deployment template with “dev” and “staging” parameter files will deploy a lightweight Azure architecture.

Example of resources deployed in Azure using the parameters.dev.json parameter file:

 
Happy coding ! 🙂


Introduction to Azure Cloud Shell (focusing on PowerShell) Introduction to Azure Elastic Db pool

Leave a Reply

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *