How to model Azure infrastructure creation in a microservice environment with Bicep Modules?

Background

Are you operating in a complex Azure environment that contains multiple independent microservices and you're wondering how to model the environment with infrastructure as a code? This blog post is for you.

This blog post shows how to structure/model infrastructure creation effectively with Bicep Modules in an environment that contains multiple independent microservices. This presented approach assumes that the whole Azure infrastructure is created via a single centralized Azure DevOps pipeline. Microservice-specific CI/CD pipelines are only responsible for deploying the application, not Azure infrastructure.

Centralized Azure infrastructure creation pipeline enables easy re-usability of Bicep Modules and makes microservice-specific pipelines light and faster to execute.

Another option is to create Azure infrastructure in multiple smaller pieces in microservice-specific Azure DevOps pipelines. That approach requires some extra work and basically Azure Container Registry to effectively share Bicep Modules between multiple Azure DevOps pipelines/repositories. You can read more about this from my previous blog post.

Bicep module hierarchy

I have divided Bicep modules into four hierarchy levels to enable powerful re-usability and modularity. I'll go through these hierarchy levels from down to top in the below sections.

As said earlier this presented approach assumes that the whole Azure infrastructure is created via a single centralized Azure DevOps pipeline and all Bicep Modules are in the same repository.

Pre-configured Resources

Pre-configured Resources (Bicep Modules) are prepared and configured according to the company's security and compliance requirements. For example, a pre-configured App Service Module can force that the FTP state is disabled and HTTP 2.0 & HTTPS are always enabled. Each module is independent and Azure resource-specific. This model also enables to you force specific resource naming policy efficiently. Pre-configured Resource modules are the foundation for Productized Resource Collections which I explain next. 

Sample Pre-configured App Service Module

This App Service Module forces automatically that the FTP state is disabled and HTTP 2.0 & HTTPS are enabled.

param location string
param tags object
param servicePrefix string
param abbreviation string
param appServicePlanId string
param applicationInsightsConnectionString string
param resourceToken string

resource appService 'Microsoft.Web/sites@2022-03-01' = {
  name: '${abbreviation}${servicePrefix}-${location}-${resourceToken}'
  location: location
  tags: tags
  properties: {
    serverFarmId: appServicePlanId
    siteConfig: {
      ftpsState: 'Disabled'
      http20Enabled: true
      netFrameworkVersion: 'v6.0'
    }
    httpsOnly: true
  }

  identity: {
    type: 'SystemAssigned'
  }

  resource appSettings 'config' = {
    name: 'appsettings'
    properties: {
      SCM_DO_BUILD_DURING_DEPLOYMENT: 'false'
      APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsightsConnectionString
      ASPNETCORE_ENVIRONMENT: 'Development'
    }
  }
}

output appServiceId string = appService.id
output defaultHostName string = appService.properties.defaultHostName

Productized Resource Collections

From the Bicep Module hierarchy point of view, Productized Resource Collection modules are built on top of Pre-configured Resource modules. 

Productized Resource Collection modules are always created for a specific use case. If your environment has multiple microservices that contain API+Database, it's beneficial to create a Productized Resource Collection module for this purpose. With this module template, you can easily create new microservices to your environment in the future which increases productivity and makes maintenance more straightforward. 

Typical resource collections that can be productized:

  • Microservice 1: App Service API + Application Insights + App Service Plan + Azure SQL database
  • Microservice 2: App Service API + Application Insights + App Service Plan + CosmosDB database
  • Publish Subscribe: Azure Service Bus + Azure Function

Sample Productized Resource Collection for Publish-Subscribe type of scenario

targetScope='resourceGroup'

param location string = resourceGroup().location
param tags object
param servicePrefix string
param resourceToken string

module appServicePlan '../../preconfigured/appservice/appServicePlan.bicep' = {
  name: 'appServicePlan-resources'
  params: {
    location: location
    tags: tags
    servicePrefix: servicePrefix
    sku: 'B1'
    kind: 'app,windows'
    resourceToken: resourceToken
  }
}

module logAnalyticsWorkspace '../../preconfigured/logging/logAnalyticsWorkspace.bicep' = {
  name: 'logAnalyticsWorkspace-resources'
  params: {
    location: location
    tags: tags
    servicePrefix: servicePrefix
    resourceToken: resourceToken
  }
}

module applicationInsightsResources '../../preconfigured/logging/applicationInsights.bicep' = {
  name: 'applicationinsights-resources'
  params: {
    location: location
    tags: tags
    workspaceId: logAnalyticsWorkspace.outputs.logAnalyticsWorkspaceId
    servicePrefix: servicePrefix
    resourceToken: resourceToken
  }
}

module storageAccount '../../preconfigured/storage/storageAccount.bicep' = {
  name: 'storageAccount-resources'
  params: {
    location: location
    tags: tags
    servicePrefix: servicePrefix
    resourceToken: resourceToken
    storageAccountType: 'Standard_LRS'
  }
}

module azureFunction '../../preconfigured/functions/azureFunction.bicep' = {
  name: 'azureFunction-resources'
  dependsOn:[
    appServicePlan
    logAnalyticsWorkspace
    applicationInsightsResources
    storageAccount
  ]
  params: {
    location: location
    tags: tags
    servicePrefix: servicePrefix
    resourceToken: resourceToken
    serverfarmId: appServicePlan.outputs.appServicePlanId
    instrumentationKey: applicationInsightsResources.outputs.applicationInsightsInstrumentationKey
  }
}

module serviceBus '../../preconfigured/messaging/serviceBus.bicep' = {
  name: 'serviceBus-resources'
  dependsOn:[
    azureFunction
  ]
  params: {
    location: location
    tags: tags
    servicePrefix: servicePrefix
    resourceToken: resourceToken
    sku: 'Standard'
  }
}

Core Infra

Core Infra determines each microservice and what Productized Resource Collection or Pre-Configured Resource modules are used to create that specific microservice. Core Infra is not only for microservices and it should determine also other shared resources.

Typically Core Infra contains multiple service-specific Bicep Modules. From a maintenance point of view, it's easier to separate each service to its own Bicep module file.

├── core
│   ├── microservices
│   │    ├── invoicing.bicep
│   │    ├── ordering.bicep
│   │    ├── products.bicep
│   ├── shared
│   │    ├── configuration.bicep

Sample Core Infra

This sample determines a Products microservice with SQL database and adds also Publish Subscribe capabilities. This approach enables to utilization of multiple different Productized Resource Collections easily.

targetScope='resourceGroup'

param tags object
param location string

var microserviceName = 'products'

module microservicesWithSqlDatabase '../../modules/productized/microservices/microserviceSqlDatabase.bicep' = { 
  name:'microservicesWithSqlDatabase'
  params:{
    location: location
    resourceToken: toLower(uniqueString(subscription().id, microserviceName, location))
    servicePrefix: microserviceName
    tags: tags
  }
}

module microservicesWithPubSub '../../modules/productized/messaging/publishSubscribeServicebus.bicep' = { 
  name:'microservicesWithPubSub'
  dependsOn:[
    microservicesWithSqlDatabase
  ]
  params:{
    location: location
    resourceToken: toLower(uniqueString(subscription().id, microserviceName, location))
    servicePrefix: microserviceName
    tags: tags
  }
}

Main Bicep

Main Bicep orchestrates the creation of the Azure infrastructure which contains multiple microservices and shared resources.

//This Main Bicep Module orchestrates creation of the Azure infrastructure which contains multiple microservices

targetScope='subscription'

param tags object
param location string
@secure()
param sqlAdminLogin string
@secure()
param sqlAdminPassword string

var resourceGroupNames = [
  'invoicing'
  'ordering'
  'products'
  'config'
]

//This creates resource groups for each microservice
module resourceGroups './modules/preconfigured/resourceGroup/resourceGroup.bicep' = [for resourceGroup in resourceGroupNames: { 
  name: resourceGroup
    params:{
    location: location
    name: 'rg-${resourceGroup}'
    tags: tags
  }
}]

//Shared services
module configuration './modules/core/shared/configuration.bicep' = {
  dependsOn:[
    resourceGroups
  ]
  scope: resourceGroup('rg-config') 
  name: 'configuration'
    params:{
    location: location
    tags: tags
  }
}

//Invoicing Microservice
module invoicing './modules/core/microservices/invoicing.bicep' = {
  dependsOn:[
    resourceGroups
  ]
  scope: resourceGroup('rg-invoicing') 
  name: 'invoicing'
    params:{
    location: location
    tags: tags
    sqlAdminLogin:sqlAdminLogin
    sqlAdminPassword:sqlAdminPassword
  }
}

//Products Microservice
module products './modules/core/microservices/products.bicep' = {
  dependsOn:[
    resourceGroups
  ]
  scope: resourceGroup('rg-products') 
  name: 'products'
    params:{
    location: location
    tags: tags
    sqlAdminLogin:sqlAdminLogin
    sqlAdminPassword:sqlAdminPassword
  }
}

//Ordering Microservice
module ordering './modules/core/microservices/ordering.bicep' = {
  dependsOn:[
    resourceGroups
  ]
  scope: resourceGroup('rg-ordering') 
  name: 'ordering'
    params:{
    location: location
    tags: tags
    sqlAdminLogin:sqlAdminLogin
    sqlAdminPassword:sqlAdminPassword
  }
}

Full Bicep Module Hierarchy

Sample Azure infrastructure creation with Bicep modules can be found on my GitHub. Each Bicep Module hierarchy level has its own folder in the sample solution.

├── preconfigured
│   ├── appservice
│   │    ├── appservice.bicep
│   ├── configuration
│   │    ├── keyvault.bicep
│   ├── messaging
│   │    ├── servicebus.bicep
├── productized
│   ├── microservices
│   │    ├── microserviceSqlDatabase.bicep
│   ├── messaging
│   │    ├── publishSubscribeServicebus.bicep
├── core
│   ├── microservices
│   │    ├── invoicing.bicep
│   │    ├── ordering.bicep
│   │    ├── products.bicep
│   ├── shared
│   │    ├── configuration.bicep

Comments