Exploring Modules in Azure Bicep

Exploring Modules in Azure Bicep

In the article Getting Started with Azure Bicep, we discussed the fundamental concepts required to start using it. Today, we will explore the topic of modules.

What are modules?

Modules are reusable building blocks of code that enable us to create, manage, and deploy Azure resources in a more organized and efficient manner. A module is a regular Bicep file that enables us to bundle multiple resources, parameters, variables, and outputs into a single package, which can then be invoked from other Bicep files.

Why use modules?

These modules help to reduce code duplication, enhance maintainability, and simplify complex infrastructure deployments by encapsulating resource configurations.

Anatomy

The syntax for defining a module is:

module <symbolic-name> '<path-to-file>' = {
  name: '<linked-deployment-name>'
  params: {
    <parameter-names-and-values>
  }
}

The name property is required. It becomes the name of the nested deployment resource in the generated template.

When creating a module, the most crucial aspect is defining the interface, the inputs it requires, and what outputs it provides:

  • Inputs: The params specified in the module definition must correspond to the parameters in the Bicep file.

  • Outputs: We can get values from a module and use them in the main Bicep file using the syntax <symbolic-name>.outputs.<output-name>.

My first module

To illustrate the usage and benefits, we will refactor the following script (main.bicep) using modules:

param prefix string = 'myapp'
param location string = resourceGroup().location
var appServicePlanName = '${toLower(prefix)}-app-${uniqueString(resourceGroup().id)}'
var webSiteName = '${toLower(prefix)}-web-${uniqueString(resourceGroup().id)}'
var cpuMetricAlertName = '${appServicePlanName}-cpu-alert'
var actionGroupName = '${toLower(prefix)}-action-group-${uniqueString(resourceGroup().id)}'

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: appServicePlanName
  location: location
  properties: {
    reserved: true
  }
  sku: {
    name: 'B1'
    capacity: 1
  }
  kind: 'linux'
}

resource appService 'Microsoft.Web/sites@2020-06-01' = {
  name: webSiteName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|7.0'
      alwaysOn: true
    }
  }
}

resource developmentTeamActionGroup 'Microsoft.Insights/actionGroups@2022-06-01' = {
  name: actionGroupName
  location: 'Global'
  properties: {
    groupShortName: 'devteam'
    enabled: true
    emailReceivers:[
      {
        name:'developer1'
        emailAddress:'raulnq@gmail.com'
      }
    ]
  }
}

resource cpuMetricAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  location: 'Global'
  name: cpuMetricAlertName
  properties:{
    severity: 2
    enabled: true
    evaluationFrequency: 'PT1H'
    windowSize: 'PT1H'
    autoMitigate: true
    targetResourceType: 'Microsoft.Web/serverFarms'
    targetResourceRegion: location
    scopes: [
      appServicePlan.id
    ]
    criteria: {
      allOf: [
          {
              threshold: 75
              name: 'Metric1'
              metricNamespace: 'Microsoft.Web/serverFarms'
              metricName: 'CpuPercentage'
              operator: 'GreaterThan'
              timeAggregation: 'Average'
              criterionType: 'StaticThresholdCriterion'
          }
      ]
      'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
    }
    actions: [
      {
          actionGroupId: developmentTeamActionGroup.id
      }
    ]
  }
}

The script creates an App Service and an Alert associated with its CPU usage. Based on this, we will create two modules: one for the App Service and another for the Alert. Let's create an appService.bicep file with the following content:

param appServicePlanName string
param location string
param webSiteName string

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: appServicePlanName
  location: location
  properties: {
    reserved: true
  }
  sku: {
    name: 'B1'
    capacity: 1
  }
  kind: 'linux'
}

resource appService 'Microsoft.Web/sites@2020-06-01' = {
  name: webSiteName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|7.0'
      alwaysOn: true
    }
  }
}

output appServicePlanId string = appServicePlan.id

And an alert.bicep file as follows:

param cpuMetricAlertName string
param location string
param threshold int
param appServicePlanId string
param actionGroupId string

resource cpuMetricAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  location: 'Global'
  name: cpuMetricAlertName
  properties:{
    severity: 2
    enabled: true
    evaluationFrequency: 'PT1H'
    windowSize: 'PT1H'
    autoMitigate: true
    targetResourceType: 'Microsoft.Web/serverFarms'
    targetResourceRegion: location
    scopes: [
      appServicePlanId
    ]
    criteria: {
      allOf: [
          {
              threshold: threshold
              name: 'Metric1'
              metricNamespace: 'Microsoft.Web/serverFarms'
              metricName: 'CpuPercentage'
              operator: 'GreaterThan'
              timeAggregation: 'Average'
              criterionType: 'StaticThresholdCriterion'
          }
      ]
      'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
    }
    actions: [
      {
          actionGroupId: actionGroupId
      }
    ]
  }
}

Then modify the original main.bicep file as follows:

param prefix string = 'myapp'
param location string = resourceGroup().location
var appServicePlanName = '${toLower(prefix)}-app-${uniqueString(resourceGroup().id)}'
var webSiteName = '${toLower(prefix)}-web-${uniqueString(resourceGroup().id)}'
var cpuMetricAlertName = '${appServicePlanName}-cpu-alert'
var actionGroupName = '${toLower(prefix)}-action-group-${uniqueString(resourceGroup().id)}'

module webApp './appService.bicep' = {
  name: '${webSiteName}-deployment'
  params: {
    appServicePlanName: appServicePlanName
    location : location
    webSiteName : webSiteName
  }
}

resource developmentTeamActionGroup 'Microsoft.Insights/actionGroups@2022-06-01' = {
  name: actionGroupName
  location: 'Global'
  properties: {
    groupShortName: 'devteam'
    enabled: true
    emailReceivers:[
      {
        name:'developer1'
        emailAddress:'raulnq@gmail.com'
      }
    ]
  }
}

module cpuMetricAlert './alert.bicep' = {
  name: '${cpuMetricAlertName}-deployment'
  params: {
    appServicePlanId: webApp.outputs.appServicePlanId
    cpuMetricAlertName: cpuMetricAlertName
    location : location
    actionGroupId : developmentTeamActionGroup.id
    threshold: 75
  }
}

This new main.bicep file will generate the same resources, but it will be much easier to add a new App Service and its corresponding Alert. We hope this article helps in your Azure Bicep journey! Thanks, and happy coding.